运算符作用于
类类型对象 时,可以通过运算符重载重新定义运算符含义。
1重载运算符基本概念
1.1定义
重载运算符是具有特殊名字的函数:operator+运算符 重载运算符参数:
1.重载运算符参数数目和运算对象数目一样多,左侧第一个右侧第二个。 2.当运算符 函数是成员函数时,左侧绑定到this上。此时重载运算符参数数目比运算对象数目少一个。 3.重载运算符不含有默认实参 。
运算符函数或者是类的成员,或者至少含有一个类类型参数 。当运算符作用于内置类型运算对象时,无法改变运算符含义。 可重载运算符:
+ - * / % ^ & | ~ ! , = < > <= >= ++ - - << >> == != && || += -= /= %= ^= &= |= *= <<= >>= [] () -> ->* new new[] delete delete[]
通常情况下,不重载",“,”&“,”&&“,”||"运算符。 重载运算符的优先级和结合律与对应内置运算符保持一致。
1.2调用
data1 += data2;
operator += ( data1, data2) ;
data1. operator += ( data2) ;
1.3注意
使用与内置类型一致的含义,逻辑上与运算符相关。 当重载了一个运算符,其相关运算符可能也需要重载,比如重载了operator=,相应也要有operator!=。 重载运算符返回类型应与其内置版本兼容。
1.4作为成员函数还是非成员函数的选择
当把运算符定义成成员函数时,左侧运算对象必须是运算符所属类 的一个对象。其中"=“,”[]“,”()“和”->“必须是成员。对于改变对象状态或与给定类型密切相关的运算符,通常是成员,比如复合赋值(”+=“等),”–“,”++“,”&"等。 具有对称性的运算符(可以转换任意一端的运算对象,类型上有混用),应该是普通的非成员对象,如算数、相等性、关系和位运算等。
2运算符重载实现
2.1输入输出运算符
IO标准库使用>>和<<执行输入输出操作。 输入输出运算符必须是非成员函数。因为左侧运算对象是istream&/ostream&。 考虑IO运算符需要对写非公有数据成员,IO运算符一般声明为友元对象。
2.1.1重载输出运算符<<
friend std:: ostream& operator << ( std:: ostream& , const hhdy& ) ;
std:: ostream& operator << ( std:: ostream& os, const hhdy& h) {
os << h. grades << h. item;
return os;
}
hhdy hh ( 60 , "xx" ) ;
std:: cout << hh<< std:: endl;
输出运算符主要负责打印对象内容,但不会考虑格式化操作(比如换行符),使用户能控制输出的细节。
2.1.2重载输入运算符>>
friend std:: istream& operator >> ( std:: istream& , hhdy& ) ;
std:: istream& operator >> ( std:: istream& is, hhdy& h) {
is >> h. grades >> h. item;
if ( ! is) {
h = hhdy ( ) ;
}
return is;
}
do {
std:: cin >> hh;
if ( ! hh. empty ( ) ) {
hvec. push_back ( hh) ;
}
} while ( ! hh. empty ( ) ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
输入运算符需要处理输入失败的情况。 当读取发生错误时,输入运算符应从错误中恢复 最好能通过里流标识符表示出失败信息。
2.2算术和关系运算符
定义为非成员函数,形参都是常量引用 一般有算术运算符也会有复合赋值运算符,则通常使用复合赋值来定义算术运算符
friend hhdy operator + ( const hhdy& a, const hhdy& b) ;
hhdy operator + ( const hhdy& a, const hhdy& b) {
hhdy sumh = a;
sumh. grades = a. grades + b. grades;
return sumh;
}
hhdy h1 ( 60 , "xx" ) ;
hhdy h2 ( 80 , "xx" ) ;
hhdy h3 = h1 + h2;
std:: cout << h3 << std:: endl;
2.3相等运算符
非成员 检验两个对象是否相等,有相等就有不相等运算符 operator==和operator!=
friend bool operator == ( const hhdy& a, const hhdy& b) ;
friend bool operator != ( const hhdy& a, const hhdy& b) ;
bool operator == ( const hhdy& a, const hhdy& b) {
return a. grades == b. grades && a. item == b. item;
}
bool operator != ( const hhdy& a, const hhdy& b) {
return ! ( a == b) ;
}
std:: cout << ( h1 == h3) << std:: endl;
std:: cout << ( h1 != h3) << std:: endl;
2.4关系运算符
非成员 operator<、operator>、operator<=、operator>=关系运算符需满足以下要求:
1.定义顺序关系 2.关系应当与"“运算符相一致 3.当只存在唯一一种逻辑可靠的”<“定义,或者”<“定义与” "运算符一致时才定义相关关系运算符
2.5赋值运算符
不是必须定义为成员函数,倾向于定义为成员函数 列表赋值运算符:以花括号元素列表为参数的赋值运算符 复合赋值运算符:"operator+="等 注意赋值和赋值初始化区别
# include
hhdy& operator = ( std:: initializer_list< std:: string> il)
hhdy& operator = ( std:: initializer_list< std:: string> il) {
grades = 100 ;
std:: string str;
for ( auto & s : il) {
str = str + s;
}
item = str;
return * this ;
}
hhdy h4;
h4 = { "aaa" , "bbb" } ;
hhdy h5 = { "aaa" , "bbb" } ;
hhdy& operator += ( const hhdy& h)
hhdy& operator += ( const hhdy& h) {
this -> grades = this -> grades + h. grades;
return * this ;
}
hhdy operator + ( const hhdy& h1, const hhdy& h2) {
hhdy hsum = h1;
hsum += h2;
return hsum;
}
hhdy gradesum ( 0 , "sum" ) ;
gradesum += h1;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
2.6下标运算符
必须是成员函数 通过元素在容器中的位置访问元素operator[] 返回值是所访问元素的引用 通常会同时定义下标运算符的常量和非常量版本 。
int & operator [ ] ( std:: size_t n) ;
const int & operator [ ] ( std:: size_t n) const ;
int & operator [ ] ( std:: size_t n) {
return number[ n] ;
}
const int & operator [ ] ( std:: size_t n) const {
return number[ n] ;
}
std:: vector< int > vi = { 1 , 2 , 3 , 4 } ;
zwhy xx ( vi, "xx" ) ;
std:: cout << xx[ 2 ] << std:: endl;
2.7递增递减运算符
不必要但建议设定为成员函数 需要同时定义前置版本和后置版本,相比前置版本,后置版本多了一个int类型形参但不使用。 后置版本递增之前需要先记录对象状态
# include
hhdyzwhy& operator ++ ( ) ;
hhdyzwhy& operator -- ( ) ;
hhdyzwhy& operator ++ ( ) {
if ( check ( curr+ 1 , "increment past end of StrBlobPtr" ) ) {
++ p;
++ curr;
}
return * this ;
}
hhdyzwhy& operator -- ( ) {
if ( check ( curr- 1 , "decrement past begin of StrBlobPtr" ) ) {
-- p;
-- curr;
}
return * this ;
}
std:: vector< int > vi = { 1 , 2 , 3 , 4 } ;
hhdyzwhy hhxx ( vi) ;
std:: cout << hhxx. getval ( ) << std:: endl;
++ hhxx;
std:: cout << hhxx. getval ( ) << std:: endl;
hhxx. operator ++ ( ) ;
# include
hhdyzwhy& operator ++ ( int ) ;
hhdyzwhy& operator -- ( int ) ;
hhdyzwhy operator ++ ( int ) {
hhdyzwhy temp = * this ;
if ( check ( curr + 1 , "increment past end of StrBlobPtr" ) ) {
++ p;
++ curr;
}
return temp;
}
hhdyzwhy operator -- ( int ) {
hhdyzwhy temp = * this ;
if ( check ( curr - 1 , "increment past end of StrBlobPtr" ) ) {
-- p;
-- curr;
}
return temp;
}
std:: vector< int > vi = { 1 , 2 , 3 , 4 } ;
hhdyzwhy hhxx ( vi) ;
std:: cout << hhxx++ . getval ( ) << std:: endl;
std:: cout << hhxx. getval ( ) << std:: endl;
hhxx. operator ++ ( 0 ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
2.8成员访问运算符
解引用运算符(*)和箭头运算符(->) p->size等价于(*p).size相比于解引用直接返回成员对象对应内容,箭头运算符使用时必须通过"->"运算符获取成员,在访问成员中内容。
解引用运算符 箭头运算符 建议是类成员,可以不是 必须是类成员 返回所指元素的引用 调用解引用运算符返回解引用结果元素地址
箭头运算符必须返回类的指针或者自定义箭头运算符的某个类对象。 1.如果返回类指针,使用内置箭头运算符。 2.如果返回某个类对象,使用"point.operator->()"来的结果来获取类A中类B的对象,如果是指针,则进行第一步。如果类中还有重载箭头运算符,则继续调用该运算符。
hhdyzwhy& operator ++ ( int ) ;
hhdyzwhy& operator -- ( int ) ;
std:: string& operator * ( ) const ;
std:: string* operator -> ( ) const ;
std:: string& operator * ( ) const {
return ( * p) ;
}
std:: string* operator -> ( ) const {
return & ( this -> operator * ( ) ) ;
}
std:: vector< std:: string> vstr = { "xx" , "hh" , "zwhy" , "hy" } ;
std:: cout << hhxx. getval ( ) << std:: endl;
std:: cout << * hhxx << std:: endl;
std:: cout << hhxx-> size ( ) << std:: endl;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
2.9函数调用运算符
必须是成员函数 可以像使用函数一样使用该类对象。一个类可以定义多个不同版本的调用运算符,相互之间在参数数量和类型上应有所区别。
class absInt {
public :
int operator ( ) ( int a) {
return a > 0 ? a : - a;
}
} ;
int a = - 5 ;
absInt getabs;
int b = getabs ( a) ;
std:: cout << a << "," << b << std:: endl;
class printstring {
public :
printstring ( ostream& s = cout, string str = " " ) : os ( s) , sep ( str) { } ;
void operator ( ) ( const string& s) const { os << s << sep; }
private :
ostream& os;
string sep;
} ;
printstring printstr ( cout, " " ) ;
vector< string> vs = { "ss" , "xx" , "hh" , "zwhy" } ;
printstr ( vs[ 1 ] ) ;
cout << endl;
for_each ( vs. begin ( ) , vs. end ( ) , printstring ( cout, "," ) ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
lamda表达式的实现
编译器会将lambda表达式翻译成一个未命名类的未命名对象,此类中含有一个重载的函数调用表达式。 默认情况下,lambda不能改变它捕获的对象,所以是const成员函数。当lambda声明为可变时(mutable),调用运算符就不是const得了。 当lambda表达式通过引用捕获变量 时,编译器可以直接使用,无需在lambda产生的类中将其存储为数据成员。当lambda表达式通过值拷贝到lambda中时,需要建立对应的数据成员。 lambda表达式产生的类不含默认构造函数、赋值运算符以及默认析构函数,是否默认拷贝/移动是捕获数据成员类型而定
vector< string> vs = { "ss" , "xx" , "hh" , "zwhy" } ;
size_t sz = 3 ;
auto wc = find_if ( vs. begin ( ) , vs. end ( ) , [ sz] ( const string& a) { return a. size ( ) > sz; } ) ;
cout << * wc << endl;
class sizecomp {
public :
sizecomp ( size_t n) : sz ( n) { }
bool operator ( ) ( const string& a) const {
return a. size ( ) > sz;
}
private :
size_t sz;
} ;
auto sc = find_if ( vs. begin ( ) , vs. end ( ) , sizecomp ( sz) ) ;
cout << * sc << endl;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
标准库定义的函数对象
标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类中定义了对应命名的调用运算符。 头文件#include
算术 关系 逻辑 plusequal_tological_andminusnot_equal_tological_ormultipliesgreaterlogical_notdividesgreater_equalmodules % lessnegate*-1 less_equal
函数对象对于指针同样适用。对于关联容器可以使用function进行元素排序。
int a1 = 10 , b1 = 20 ;
plus< int > intsum;
int c = intsum ( a1, b1) ;
cout << c<< endl;
sort ( vs. begin ( ) , vs. end ( ) , greater < string> ( ) ) ;
for_each ( vs. begin ( ) , vs. end ( ) , [ ] ( const string& a) { cout << a << " " ; } ) ;
cout << endl;
vector< int > s = { 1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 } ;
sort ( s. begin ( ) , s. end ( ) , greater < int > ( ) ) ;
cout << count_if ( s. begin ( ) , s. end ( ) , bind2nd ( greater < int > ( ) , 3 ) ) << endl;
cout << count_if ( s. begin ( ) , s. end ( ) , bind1st ( modulus < int > ( ) , 60 ) ) << endl;
可调用对象和lambda
可调用对象:调用对象包括函数、函数指针、lambda表达式、bind对象、重载函数调用运算符的类等。调用对象之间可能共享同一种调用形式(调用返回类型以及传递给调用的实参类型),比如int(int,int):
int add ( int i, int j) { return i+ j; }
auto mod = [ ] ( int i, int j) { return i% j; } ;
struct divide {
int operator ( ) ( int i, int j) { returni/ j; }
} ;
某些情况下可能想制作可调用函数的函数表,存储可调用对象的“指针”,查找并使用对应函数。
map实现 function类型实现
map实现 :通过map形成string-可调用对象指针键值对。调用string索引map。不能将lambda表达式或重载可调用函数对象的类存入map中,因为他们有自己的类类型。(测试lambda表示能用)
map< string, int ( * ) ( int , int ) > binops;
binops. insert ( { "+" , add } ) ;
cout << binops[ "+" ] ( 1 , 2 ) << endl;
binops. insert ( { "%" , mod } ) ;
divide div;
binops. insert ( { "/" , div } ) ;
function类型 :未解决以上问题,可以使用function类型,头文件#include
操作 说明 functionalf; 用来存储可调用对象的空function functional(nullptr); 显式构造一个空function functional(obj); 存储可调用对象obj的副本 f f作为条件 f(args) 调用函数对象 定义为function成员类型 result_type function类型可调用对象返回类型 argument_type 一个实参时实参类型 first_argument_type 两个实参时,第一个实参类型 second_argument_type 两个实参时,第二个实参类型
function< int ( int , int ) > f1 = add;
function< int ( int , int ) > f2 = divide ( ) ;
function< int ( int , int ) > f3 = [ ] ( int i, int j) { return i * j; } ;
cout << f1 ( 4 , 2 ) << endl;
cout << f2 ( 4 , 2 ) << endl;
cout << f3 ( 4 , 2 ) << endl;
使用function定义map 对于重载的函数,可以存储函数指针再加入到map中。或者使用lambda消除二义性。
int ( * fp) ( int , int ) = add;
map< string, function< int ( int , int ) >> binops2 = {
{ "+" , fp} , { "-" , minus < int > ( ) } , { "*" , [ ] ( int i, int j) { return i * j; } } ,
{ "/" , divide ( ) } , { "%" , mod}
} ;
cout << binops2[ "+" ] ( 4 , 2 ) << endl;
cout << binops2[ "-" ] ( 4 , 2 ) << endl;
cout << binops2[ "*" ] ( 4 , 2 ) << endl;
cout << binops2[ "/" ] ( 4 , 2 ) << endl;
cout << binops2[ "%" ] ( 4 , 2 ) << endl;
可以定义对类类型的类型转换,这是一种特殊的成员函数 :operatorconst; 。Type为函数的返回类型,不允许转换为数组或者函数类型,但可以转换为指针或者引用。类型转换运算符不能声明返回类型,形参列表也为空,通常不应该改变转换对象的内容,一般会被定义为const成员。 当类型转换存在多种语义时,建议通过普通成员函数提取,而不是通过类型转换。
class smallint {
public :
smallint ( int i = 0 ) : val ( i) {
if ( i < 0 || i> 255 )
throw out_of_range ( "out of smallint range" ) ;
}
operator int ( ) {
return val;
}
private :
size_t val;
} ;
int main ( ) {
smallint a = 4 ;
cout << a + 2 << endl;
smallint b = 3.14 ;
cout << b + 3.14 << endl;
return 0 ;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
int i = 42 ;
cin << i;
显式类型转换运算符
为避免意外结果的发生,可以使用显式的类型转换运算符 :
explicit operator int ( ) {
return val;
}
smallint a = 4 ;
a+ 2 ;
static_cast < int > ( a) + 2 ;
如果表达式被用作条件 ,编译器会将显式类型转换自动应用于它,对于显式用做条件必须有bool类型转换符 ,否则两次转换会报错:
if、while及do语句条件部分 for语句头条件表达式 逻辑与或非运算符运算对象 条件运算符(?:)
if ( b) { cout << "true" << endl; }
对于IO类型:早期版本定义了向void*转换规则,C++11标准下,通过定义一个向bool的显式类型转换 实现同样目的。在条件中使用流对象,会使用IO类型定义的operator bool()。
int value;
auto cinstate = ( cin >> value) . rdstate ( ) ;
while ( cin >> value)
避免二义性类型转换
1.两个类提供了相同的类型转换:
A类定义了接受B类的转换构造函数同时B类存在到A类的转换运算符。此时无法使用强制类型转换解决二义性问题,因为强制类型转换本身也具有二义性。
struct A {
A ( ) = default ;
A ( B& b) { }
} ;
struct B {
operator A ( ) { }
} ;
int main ( ) {
B b1;
A a1 = b1;
A a2 = A ( b. operator A ( ) ) ;
A a3 = A ( b) ;
}
2.定义了多个转换规则:
struct C {
C ( int c = 0 ) { cout << "initint" << endl; }
C ( double c = 0.0 ) { cout << "initdouble" << endl; }
operator int ( ) { cout << "toint" << endl; }
operator double ( ) { cout << "todouble" << endl; }
} ;
int main ( ) {
long l;
long double ld;
short s;
C c1 ( l) ;
C c2 ( ld) ;
C c3 ( s) ;
}
3.多个类的类型转换具有同一类可行匹配,则这些类型转换一样好。
调用重载函数时,不建议使用构造函数或者强制类型转换来改变实参类型。 调用重载函数时,如果多个用户定义的类型转换都提供了可行匹配,则认为这些类型一样好 。即使一个需要额外的标准类型转换而另一个是精准匹配。此时不会考虑任何可能出现的标准类型转换级别。转换级别只有当所有可行函数都请求同一个用户定义的类型转换时才有用。
struct D {
D ( int ) { } ;
} ;
struct E
{
E ( int ) { } ;
} ;
struct F {
F ( double ) { } ;
} ;
void manip ( const C& ) { }
void manip ( const D& ) { }
void manip ( const E& ) { }
int main ( ) {
manip ( 10 ) ;
manip ( D ( 10 ) ) ;
manipu ( F ( double ( 10 ) ) ) ;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
4.函数匹配与重载运算符
struct H {
friend int operator + ( H h1, H h2) ;
H ( int h1 = 0 ) : h ( h1) { }
operator int ( ) { return h; }
H operator + ( H h2) { cout << "成员函数" << endl; return H ( h + h2. h) ; }
int h;
} ;
int operator + ( H h1, H h2) {
cout << "非成员函数" << endl;
return h1. h + h2. h;
}
int main ( ) {
H hh ( 2 ) , xx ( 3 ) ;
cout << hh + xx << endl;
cout << ( hh. operator + ( xx) ) . h<< endl;
cout << 3 + hh << endl;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17