您的位置:首页 > 编程语言 > C语言/C++

C++基础篇--运算符重载

2015-09-01 15:04 218 查看
运算符重载没有多高深复杂,但牵扯到的相关概念和语法变化不少,如果这块基石不牢固,读复杂C++代码时就只有雾里看花的份。
运算符重载由来
C/C++中所有运算符(+-*/等)默认只用于标准数据类型(int float double等),而对自定义的struct/class通常不适用,如:
typedef struct TIMETAG
{
int hours;
int minutes;
//int *days; ①
}duration;
duration a,b, sum;
a.hours =2; a.minutes = 30;
//days =malloc(4); ②
b.hours = 1; b.minutes=10;
sum = a +b; //不成立
直接sum=a+b是美好的愿望,但编译器事先不知道自定义结构体里有哪些成员,也不知道+号功能应该针对哪些(全部?有指针也直接加?),让编译器怎么翻译这个+号?只能自定义函数:
duration add(duration a, duration b){
duration sum;
sum.hours =a.hours+b.hours;
sum.minutes =a.minutes+b.minutes;
//*(sum.days)=*(a.days) +*(b.days); ③
return sum;
}
倒是有两个操作符默认可以用于struct/class:=和&,如sum=a;等价于memcpy(&sum,&a,
sizeof(duration));是对=号默认的运算符重载,实现了浅拷贝(相关浅拷贝、深拷贝、拷贝构造函数以及默认=号运算符重载等概念的联系与区别另文讨论),这已是编译器能力极限,它没法进一步给出其他运算符用于struct/class对象时的默认实现,如上例对sum=a+b不知怎么操作指针days③,最后还是要用户自定义函数。C++规定可用operator+()代替上例的add函数实现"+"号运算符对应的加法语义,比如对sum=a+b,编译器支持用函数”operator+()”自动替换表达式里的”+”号,使程序员能像对待标准数据一样去操作类对象,简化了代码书写,这就是C++运算符重载的思路。这里与+号映射的函数operator+()称为运算符重载函数。这种古怪的定义明显是为方便编译器查找映射运算符。
C++运算符重载函数
C++运算符重载函数与普通函数类似,只不过函数名由关键字“operator”加“运算符"构成,如operator+()、operator*()等,函数内部实现运算符对应操作(如operator+()实现某些类成员的相加)。当运算符(+号)作用于类对象时,编译器调用对应的函数operator+(),完成自定义功能。可见运算符重载就是对运算符重载函数的简化调用,例:
class Complex{
public:
Complex (double R=0.0, double I=0.0){ real=R; imag=I; }
private:
double real,imag;
};
Complex a(10.0,20.0),b(5.0,8.0),c(3.0,2.0); //a,b,c加减乘除运算怎么实现?
#if 0//没有运算符重载:
Complex add(Complex& a, Complex& b); { ……//实现复数相加…. }
Complex mul(Complex& a,Complex& b); { ……//实现复数相乘…. }
Complex alg(Complex& a, Complex& b, Complex& c){
return add(add(mul(a,b), mul(b,c)), mul(c,a)); //..④.看得出计算公式么?
}
#else//运算符重载:
Complex operator+(Complex& a, Complex& b); { …//实现复数相加…. } //⑤
Complexoperator* (Complex& a, Complex& b); { …//实现复数相乘…. } //⑥
Complex alg(Complex& a, Complex& b, Complex& c){
return a*b +b*c + c*a; //⑦ 这次看明白了吧
}
#endif
④⑦功能相同,⑦形式更简洁,编译器背后转换:运算符”*”对应运算符函数operator*(),运算符操作数转化为运算符函数的实参(a,
b),最终a*b<=>operator*(a,b)。
⑤⑥处定义+号和*号的运算符函数,或者说实现+号和*号的运算符重载:赋予内部运算符多重含义,使它作用于特定对象时完成特定功能,最终同一运算符用于不同类型数据产生不同行为。
注意:函数重载是同名不同参的函数实现各自功能;运算符重载是同运算符不同操作对象实现不同功能。各从不同角度体现“复用”的语义,没有其他关联。
运算符函数实现三种形式
运算符重载可通过类成员函数、友元函数及非成员非友元普通函数三种形式实现,前例⑤⑥处为普通函数,由于不能直接访问Complex类的私有成员real和imag,实际上无法通过a.real+b.real实现预想功能;如果为Complex类增加访问real和imag的public接口,就能把运算符重载为普通函数,但比较低效和麻烦。因此如果不需要直接访问类私有成员,可以重载为非成员非友元的普通函数,否则多选择成员/友元函数重载运算符函数,例:
class Complex
{
public:
Complex(double R=0.0, double I=0.0) { real=R; imag=I; }
Complex operator+(const Complex &c); //成员函数
//friend Complexoperator+(const Complex &c1,const Complex &c2);//友元函数
private:
double real,imag;
};
Complex Complex::operator+(const Complex &c){ //⑧
return Complex(real + c.real, imag + c.imag);
}
//Complex operator +(constComplex &c1, const Complex &c2){ //⑨
// return Complex (c1.real+ c2.real, c1.imag + c2.imag);
//}
void main()
{
Complexc1(2.0, 3.0), c2(4.0, -2.0), c3;
c3 = c1 + c2; //
}
重载为类成员函数语法形式为:函数返回值类型 类名::operator运算符(形参表){ 函数体;
}
此时编译器将c1+c2翻译为:c1.operator+(&c2) 即调用⑧。operator+()是+号的运算符函数,表面只有一个参数c2,少了c1,实际c1的this指针已伴随成员函数c1.operator+()的调用而隐含传入,即⑧访问两个对象的成员,一是this->real就是c1.real,一是形参对象c2.real。实现相当于:return
Complex(this->real+ c.real,this->imag+ c.imag);
重载为类成员函数要求运算符表达式第一个操作数必须是成员函数所属类的对象,且与运算符重载函数返回值类型相同。很简单,成员函数必须通过所属类的对象去调用,而且只有运算符重载函数返回值与该对象同类型,运算结果才有意义。
重载为类友元函数语法形式为:friend函数返回值类型
operator运算符(形参表){ 函数体; }
友元函数参数中不隐含this指针,所有对象都要传递,因此函数参数个数与运算符操作数相同。此时c1+c2解释为:operator+(c1,c2),即⑨。注意函数参数(c1,c2)必须和操作数c1+c2顺序对应。
重载形式选择与禁忌
1)双目运算符左操作数如果是普通类型(如10.0+c2,左侧10.0是浮点数),不能重载为类成员函数,只能重载为非成员的全局运算符函数(友元或普通)。因为c1+c2重载为成员函数后译为c1.operator+(c2),即operator+()必是左操作数c1的成员。此时如c1是普通类型的10.0,operator+()就没有依附,编译器报错。
此外,输入输出运算符<<和>>的左操作数是C++标准对象,无法重载为其类成员函数,同样只能重载为友元函数。
2)当运算符操作数属于不同类,要重载为成员函数时,必须重载为左操作数所属类的成员,或实现为友元函数。原因同上
3)有几个双目运算符包括:赋值=、函数调用()、下标[]、指针->,这几个运算符语义上与this有密切关联,必须重载为成员函数。
4)双目运算符重载为友元函数时,不同类型左右操作数顺序不能随意切换,如:
friend Complex operator+(int &i,Complex &c) { return Complex(i+c.real,c.imag); }
c3=i+c2; //正确,类型匹配
c3=c2+i; //错误,类型不匹配,交换律不适用,必须再重载一次“+”:
friend Complex operator+(Complex &c, int &i){ return Complex(i+c.real,c.imag); }
这样i+c2和c2+i才都合法,编译器会选择参数顺序匹配的运算符重载函数。
5)除以上规定外,为实现方便,一般单目运算符重载为类成员函数,双目运算符重载为友元函数。若运算符操作需修改对象状态,重载为成员函数较好。如下列左值操作数的赋值运算符(+=,++,-=,
/=,*=, ^=,!= ,%= ,>>= 等)。
运算符重载规则
1)不能臆造新运算符,只能重载C++已有运算符;除下面五个外其他都可重载:类属成员运算符“.”、指针运算符“*”、作用域运算符“::”、“sizeof”、三目运算符“?:”。
2)重载运算符不能改变运算符优先级和结合性,也不能改变操作数个数及语法结构。
3)运算符重载只是针对自定义类型需要,对原运算符的扩展复用,因此必须至少有一个操作数是自定义类型。
4)重载功能应与原运算符的自然语义接近,避免无关功能随意重载,比如在operator+()里就不应该做减法操作。
5)编译器对运算符重载的选择,遵循函数重载的选择原则,寻找参数匹配的运算符函数。
类型转换运算符函数与运算符重载区别
C++中用类型转换函数来把一个类转换为其他类或普通数据,形式为:
operator目标类型(){
...
return目标类型的数据;
}
目标类型是所要转化成的类型名,既可以是标准类型(如int double)也可以是自定义类型(class名)。operator 之前不指定返回类型,且没有参数。函数体最后一条语句一般为:return目标类型数据。
类型转换函数在类类型与其他类型隐式或显式转换时被自动调用,尽管都使用operator关键字,但和运算符重载是实现不同功能的两种平行语法,一个重载运算符(+-*/等),一个重载类型转换符(int(), double()等)。
运算符重载与const
运算符重载中,常见const Complex &c1这种参数,因为当明知参数不会被改变时,按const引用传递,可以加强数据保护。但要澄清运算符重载和const之间并没有硬性语法关联。const在C++中作用将另文分析。
总结
如本文开头所说,运算符重载的创立是为简化C++中操作对象的语法形式,初衷是好的,实际感觉有利有弊,简化了书写却增加了很多理解代码的陷阱。简洁的调用形式往往使新手看不到隐藏在背后的重载实现代码,而不得精髓。即使老手也要小心对待C++中每一个看似简单的运算符,步步惊心:(。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: