运算符重载时要遵循以下规则:
成员函数运算符
当运算符重载为类的成员函数时,函数的参数个数比原来的操作数要少一个(后置单目运算符除外),这是因为成员函数用this指针隐式地访问了类的一个对象,它充当了运算符函数最左边的操作数。因此:
友元函数运算符
当运算符重载为类的友元函数时,由于没有隐含的this指针,因此操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左至右一一对应。
两种重载形式的比较
在多数情况下,将运算符重载为类的成员函数和类的友元函数都是可以的。但成员函数运算符与友元函数运算符也具有各自的一些特点:
(7) 当需要重载运算符具有可交换性时,选择重载为友元函数。
为什么要对运算符进行重载:
C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型(类)是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。
C++运算符重载的实质:
运算符重载的实质就是函数重载或函数多态。运算符重载是一种形式的C++多态。目的在于让人能够用同名的函数来完成不同的基本操作。要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数形式:operatorp(argument-list)//operator 后面的'p'为要重载的运算符符号。
即:
一个简单运算符重载实例:
(我在这篇博文里说过了,很简单,刚接触的朋友可以看一下,有代码)
http://blog.csdn.net/lishuzhai/article/details/50764312
运算符重载的规则:
(1)为了防止用户对标准类型进行运算符重载,C++规定重载后的运算符的操作对象必须至少有一个是用户定义的类型
这是什么意思呢?
比如说现在有两个数:int number1,int number2,
那么number1+number2 求的是两个数的和,
但是如果你重载以后让着两个数相加为他们的乘积,这肯定是不合乎逻辑的。
可能重载以后会有二义性,导致程序不知道该执行哪一个(是自带的的还是重载后的函数)
(2)使用运算符不能违法运算符原来的句法规则。如不能将% 重载为一个操作数,
例如:
int index;
%index;这种是不被允许的。
(3)不能修改运算符原先的优先级。
(4)不能创建一个新的运算符,例如不能定义operator** (···)来表示求幂
(5)不能进行重载的运算符:成员运算符,作用域运算符,条件运算符,sizeof运算符,typeid(一个RTTI运算符),const_cast、dynamic_cast、reinterpret_cast、static_cast强制类型转换运算符
(6)大多数运算符可以通过成员函数和非成员函数进行重载但是下面这四种运算符只能通过成函数进行重载:
= 赋值运算符,()函数调用运算符,[ ]下标运算符,->通过指针访问类成员的运算符。
(7)除了上述的规则,其实我们还应该注意在重载运算符的时候遵守一些明智的规则:例如:不要将+运算符重载为交换两个对象的值。
重载运算符的两种形式:
重载运算符有两种方式,即:
重载为类的成员函数||重载为类的非成员函数。
重载为类的非成员函数的时候:
通常我们都将其声明为友元函数,因为大多数时候重载运算符要访问类的私有数据,(当然也可以设置为非友元非类的成员函数。但是非友元又不是类的成员函数是没有办法直接访问类的私有数据的),如果不声明为类的友元函数,而是通过在此函数中调用类的公有函数来访问私有数据会降低性能。所以一般都会设置为类的友元函数,这样我们就可以在此非成员函数中访问类中的数据了。
下面我同意讲解一个经典的例子来说明一下这两者的区别、特点、要重载时可能出现的问题
GO:
现在我们有一个时间类,我们要进行的任务是对时间类的加减乘。或许你会觉得很简单,简单就对了,因为这更容易让你深入的理解到这二者
在主函数中,我们创建了weeding 和waxing连个对象,进行了操作。那么现在问题来了。
我们看(3)标记处,现在adjusted = total *1.5; 是可以运行的,那么adjusted = 1.5*total可以运行吗?
答案当然是不可以,我们还原下此语句:adjusted = total.operator*(1.5),换成1.5在乘号前面当然是不可以的。因为1.5不是对象。
这个时候有两种解决方式:
一:告诉每个人只能按照adjusted = total *1.5; 这种方式来,这种方式看起来当然是欠缺的。那么重头戏来了:
二:非成员函数,声明为 CMyTime operator *(double m,const CMyTime &t);
这样等价于:A = operator *(1.5,B)等价于:A = 1.5 * B;出于性能考虑,我们将其声明为friend 即友元函数(前面已经解释过为什么要声明为友元了):
friend CMyTime operator*(double m,const CMyTime &t);
接下来我们这样做:在上面的MyTIme.h文件的public中加入:friend CMyTime operator*(double m, const CMyTime &t){ return t*m; } (因为这个函数操作很简单,可以直接为内联函数);然后就可以运行 A = 1.5 * B语句了。
接下来我们为了更好的了解重载运算符,来进行<<运算符的重载:
现在我们想让 cout< 第一种声明为成员函数: 照葫芦画瓢:CMyTime operator<<(ostream &s); 那么这种生命方式会造成什么结果呢?答案是:输出会变成:adjusted<< cout;或许看起来很不好,但这确实是正确的。因为这等价于:adjusted.operator<<(cout); 第二种版本:(也是更好的版本):MyTIme.h 声明:friend void operator<<(ostream &os,const CMyTime &t); MyTime.cpp实现: void operator<<(ostream &os,const CMyTime &t){os << t.hours << t.minutes};这样就可以执行cout << adjusted 这条语句了。 但是这样会存在一个问题: 我们没有办法执行cout << adjusted < friend ostream operator<<(ostream &s,const CMyTIme &t); 实现的时候:return &os;就行了。 (程序可以运行,更改也很少,可以试一试的呦); 那么最重要的问题来了,我们什么时候声明为成员函数,什么时候声明为非成员函数呢? 首先,我们要明白这句话:对于成员函数来说,一个操作数通过this指针隐式的传递,(即本身),另一个操作数作为函数的参数显示的传递;对于友元函数(非成员函数)两个操作数都是通过参数来传递的。 (1)一般来说,弹幕运算符重载为类的成员函数,双目运算符重载为类的友元函数(咳咳,一般情况下) (2)双目运算符不能将 = 。 ()【】。-> 重载为类的友元函数。 (3)如果运算符的第一次操作数要求为隐式转换则必须为友元函数。 (4)当最左边的要求为类对象,而右边的是一个内置类型,则要为友元函数。 最重要的注意点: T1 = T2 + T3; 可以为T1 = T2.operator+(T3); 也可以为T1 = operator+(T2,T3); 但是这两种方式不能同时声明定义,因为这会出现二义性。造成程序不知道该执行那个函数。在进行运算符重载的时候千万要注意造成二义性的情况。 ok。基本完了。如果你真正搞懂了这篇博文,最基本性的东西就可以了。
C++中预定义的运算符的操作对象只能是基本数据类型,实际上,对于很多用户自定义类型,也需要有类似的运算操作。例如: 一、 运算符重载的规则 1. 运算符重载的作用: 运算符重载允许C/C++的运算符在用户定义类型(类)上拥有一个用户定义的意义。重载的运算符是函数调用的语法修饰: class Fred { public: // ... }; #if 0 // 没有算符重载: Fred add(Fred, Fred); Fred mul(Fred, Fred); Fred f(Fred a, Fred b, Fred c) { return add(add(mul(a,b), mul(b,c)), mul(c,a)); // 哈哈,多可笑... } #else // 有算符重载: Fred operator+ (Fred, Fred); Fred operator* (Fred, Fred); Fred f(Fred a, Fred b, Fred c) { return a*b + b*c + c*a; } #endif 2. 可以用作重载的运算符: 算术运算符:+,-,*,/,%,++,--; 位操作运算符:&,|,~,^,<<,>> 逻辑运算符:!,&&,||; 比较运算符:<,>,>=,<=,==,!=; 赋值运算符:=,+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=; 其他运算符:[],(),->,,(逗号运算符),new,delete,new[],delete[],->*。 下列运算符不允许重载: .,.*,::,?: 3. 运算符重载后,优先级和结合性: 用户重载新定义运算符,不改变原运算符的优先级和结合性。这就是说,对运算符重载不改变运算符的优先级和结合性,并且运算符重载后,也不改变运算符的语法结构,即单目运算符只能重载为单目运算符,双目运算符只能重载双目运算符。 4. 编译程序如何选用哪一个运算符函数: 运算符重载实际是一个函数,所以运算符的重载实际上是函数的重载。编译程序对运算符重载的选择,遵循着函数重载的选择原则。当遇到不很明显的运算时,编译程序将去寻找参数相匹配的运算符函数。 5. 重载运算符有哪些限制: (1) 不可臆造新的运算符。必须把重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中。 (2) 重载运算符坚持4个“不能改变”。 ·不能改变运算符操作数的个数; ·不能改变运算符原有的优先级; ·不能改变运算符原有的结合性; ·不能改变运算符原有的语法结构。 6. 运算符重载时必须遵循哪些原则: 运算符重载可以使程序更加简洁,使表达式更加直观,增加可读性。但是,运算符重载使用不宜过多,否则会带来一定的麻烦。 (1) 重载运算符含义必须清楚。 (2) 重载运算符不能有二义性。 运算符重载函数的两种形式 运算符重载的函数一般地采用如下两种形式:成员函数形式和友元函数形式。这两种形式都可访问类中的私有成员。 1. 重载为类的成员函数 这里先举一个关于给复数运算重载复数的四则运算符的例子。复数由实部和虚部构造,可以定义一个复数类,然后再在类中重载复数四则运算的运算符。先看以下源代码: #include <iostream.h> class complex { public: complex() { real=imag=0; } complex(double r, double i) { real = r, imag = i; } complex operator +(const complex &c); complex operator -(const complex &c); complex operator *(const complex &c); complex operator /(const complex &c); friend void print(const complex &c); private: double real, imag; }; inline complex complex::operator +(const complex &c) { return complex(real + c.real, imag + c.imag); } inline complex complex::operator -(const complex &c) { return complex(real - c.real, imag - c.imag); } inline complex complex::operator *(const complex &c) { return complex(real * c.real - imag * c.imag, real * c.imag + imag * c.real); } inline complex complex::operator /(const complex &c) { return complex((real * c.real + imag + c.imag) / (c.real * c.real + c.imag * c.imag), (imag * c.real - real * c.imag) / (c.real * c.real + c.imag * c.imag)); } void print(const complex &c) { if(c.imag<0) cout<<c.real<<c.imag<<'i'; else cout<<c.real<<'+'<<c.imag<<'i'; } void main() { complex c1(2.0, 3.0), c2(4.0, -2.0), c3; c3 = c1 + c2; cout<<"/nc1+c2="; print(c3); c3 = c1 - c2; cout<<"/nc1-c2="; print(c3); c3 = c1 * c2; cout<<"/nc1*c2="; print(c3); c3 = c1 / c2; cout<<"/nc1/c2="; print(c3); c3 = (c1+c2) * (c1-c2) * c2/c1; cout<<"/n(c1+c2)*(c1-c2)*c2/c1="; print(c3); cout<<endl; } 该程序的运行结果为: c1+c2=6+1i c1-c2=-2+5i c1*c2=14+8i c1/c2=0.45+0.8i (c1+c2)*(c1-c2)*c2/c1=9.61538+25.2308i 在程序中,类complex定义了4个成员函数作为运算符重载函数。将运算符重载函数说明为类的成员函数格式如下: <类名> operator <运算符>(<参数表>) 其中,operator是定义运算符重载函数的关键字。 程序中出现的表达式: c1+c2 编译程序将给解释为: c1.operator+(c2) 其中,c1和c2是complex类的对象。operator+()是运算+的重载函数。 该运算符重载函数仅有一个参数c2。可见,当重载为成员函数时,双目运算符仅有一个参数。对单目运算符,重载为成员函数时,不能再显式说明参数。重载为成员函数时,总时隐含了一个参数,该参数是this指针。this指针是指向调用该成员函数对象的指针。 2. 重载为友元函数: 运算符重载函数还可以为友元函数。当重载友元函数时,将没有隐含的参数this指针。这样,对双目运算符,友元函数有2个参数,对单目运算符,友元函数有一个参数。但是,有些运行符不能重载为友元函数,它们是:=,(),[]和->。 重载为友元函数的运算符重载函数的定义格式如下: friend <类型说明符> operator <运算符>(<参数表>) {……} 下面用友元函数代码成员函数,重载编写上述的例子,程序如下: #include <iostream.h> class complex { public: complex() { real=imag=0; } complex(double r, double i) { real = r, imag = i; } friend complex operator +(const complex &c1, const complex &c2); friend complex operator -(const complex &c1, const complex &c2); friend complex operator *(const complex &c1, const complex &c2); friend complex operator /(const complex &c1, const complex &c2); friend void print(const complex &c); private: double real, imag; }; complex operator +(const complex &c1, const complex &c2) { return complex(c1.real + c2.real, c1.imag + c2.imag); } complex operator -(const complex &c1, const complex &c2) { return complex(c1.real - c2.real, c1.imag - c2.imag); } complex operator *(const complex &c1, const complex &c2) { return complex(c1.real * c2.real - c1.imag * c2.imag, c1.real * c2.imag + c1.imag * c2.real); } complex operator /(const complex &c1, const complex &c2) { return complex((c1.real * c2.real + c1.imag * c2.imag) / (c2.real * c2.real + c2.imag * c2.imag), (c1.imag * c2.real - c1.real * c2.imag) / (c2.real * c2.real + c2.imag * c2.imag)); } void print(const complex &c) { if(c.imag<0) cout<<c.real<<c.imag<<'i'; else cout<<c.real<<'+'<<c.imag<<'i'; } void main() { complex c1(2.0, 3.0), c2(4.0, -2.0), c3; c3 = c1 + c2; cout<<"/nc1+c2="; print(c3); c3 = c1 - c2; cout<<"/nc1-c2="; print(c3); c3 = c1 * c2; cout<<"/nc1*c2="; print(c3); c3 = c1 / c2; cout<<"/nc1/c2="; print(c3); c3 = (c1+c2) * (c1-c2) * c2/c1; cout<<"/n(c1+c2)*(c1-c2)*c2/c1="; print(c3); cout<<endl; } 该程序的运行结果与上例相同。前面已讲过,对又目运算符,重载为成员函数时,仅一个参数,另一个被隐含;重载为友元函数时,有两个参数,没有隐含参数。因此,程序中出现的 c1+c2 编译程序解释为: operator+(c1, c2) 调用如下函数,进行求值, complex operator +(const coplex &c1, const complex &c2) 3. 两种重载形式的比较 一般说来,单目运算符最好被重载为成员;对双目运算符最好被重载为友元函数,双目运算符重载为友元函数比重载为成员函数更方便此,但是,有的双目运算符还是重载为成员函数为好,例如,赋值运算符。因为,它如果被重载为友元函数,将会出现与赋值语义不一致的地方。 其他运算符的重载举例 1).下标运算符重载 由于C语言的数组中并没有保存其大小,因此,不能对数组元素进行存取范围的检查,无法保证给数组动态赋值不会越界。利用C++的类可以定义一种更安全、功能强的数组类型。为此,为该类定义重载运算符[]。 下面一个例子: #include <iostream.h> class CharArray { public: CharArray(int l) { Length = l; Buff = new char[Length]; } ~CharArray() { delete Buff; } int GetLength() { return Length; } char & operator [](int i); private: int Length; char * Buff; }; char & CharArray::operator [](int i) { static char ch = 0; if(i<Length&&i>=0) return Buff[i]; else { cout<<"/nIndex out of range."; return ch; } } void main() { int cnt; CharArray string1(6); char * string2 = "string"; for(cnt=0; cnt<8; cnt++) string1[cnt] = string2[cnt]; cout<<"/n"; for(cnt=0; cnt<8; cnt++) cout<<string1[cnt]; cout<<"/n"; cout<<string1.GetLength()<<endl; } 该数组类的优点如下: (1) 其大小不一定是一个常量。 (2) 运行时动态指定大小可以不用运算符new和delete。 (3) 当使用该类数组作函数参数时,不心分别传递数组变量本身及其大小,因为该对象中已经保存大小。 在重载下标运算符函数时应该注意: (1) 该函数只能带一个参数,不可带多个参数。 (2) 不得重载为友元函数,必须是非static类的成员函数。 2). 重载增1减1运算符. 增1减1运算符是单目运算符。它们又有前缀和后缀运算两种。为了区分这两种运算,将后缀运算视为又目运算符。表达式 obj++或obj-- 被看作为: obj++0或obj--0 下面举一例子说明重载增1减1运算符的应用。 #include <iostream.h> class counter { public: counter() { v=0; } counter operator ++(); counter operator ++(int ); void print() { cout<<v<<endl; } private: unsigned v; }; counter counter::operator ++() { v++; return *this; } counter counter::operator ++(int) { counter t; t.v = v++; return t; } void main() { counter c; for(int i=0; i<8; i++) c++; c.print(); for(i=0; i<8; i++) ++c; c.print(); } 3). 重载函数调用运算符 可以将函数调用运算符()看成是下标运算[]的扩展。函数调用运算符可以带0个至多个参数。下面通过一个实例来熟悉函数调用运算符的重载。 #include <iostream.h> class F { public: double operator ()(double x, double y) const; }; double F::operator ()(double x, double y) const { return (x+5)*y; } void main() { F f; cout<<f(1.5, 2.2)<<endl; }
class complex
{
public:
complex(double r=0.0,double I=0.0){real=r;imag=I;}
void display();
private:
double real;
double imag;
};
complex a(10,20),b(5,8);
“a+b”运算如何实现?这时候我们需要自己编写程序来说明“+”在作用于complex类对象时,该实现什么样的功能,这就是运算符重载。运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据导致不同类型的行为。
运算符重载的实质是函数重载。在实现过程中,首先把指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参,然后根据实参的类型来确定需要调用达标函数,这个过程爱编译过程中完成。
运算符重载规则如下:
①、 C++中的运算符除了少数几个之外,全部可以重载,而且只能重载C++中已有的运算符。
②、 重载之后运算符的优先级和结合性都不会改变。
③、 运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来说,重载的功能应当与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自定义类型。
不能重载的运算符只有五个,它们是:成员运算符“.”、指针运算符“*”、作用域运算符“::”、“sizeof”、条件运算符“?:”。
运算符重载形式有两种,重载为类的成员函数和重载为类的友元函数。
运算符重载为类的成员函数的一般语法形式为:
函数类型 operator 运算符(形参表)
{
函数体;
}
运算符重载为类的友元函数的一般语法形式为:
friend 函数类型 operator 运算符(形参表)
{
函数体;
}
其中,函数类型就是运算结果类型;operator是定义运算符重载函数的关键字;运算符是重载的运算符名称。
当运算符重载为类的成员函数时,函数的参数个数比原来的操作个数要少一个;当重载为类的友元函数时,参数个数与原操作数个数相同。原因是重载为类的成员函数时,如果某个对象使用重载了的成员函数,自身的数据可以直接访问,就不需要再放在参数表中进行传递,少了的操作数就是该对象本身。而重载为友元函数时,友元函数对某个对象的数据进行操作,就必须通过该对象的名称来进行,因此使用到的参数都要进行传递,操作数的个数就不会有变化。
运算符重载的主要优点就是允许改变使用于系统内部的运算符的操作方式,以适应用户自定义类型的类似运算。
二、 运算符重载为成员函数
对于双目运算符B,如果要重载B为类的成员函数,使之能够实现表达式oprd1 B oprd2,其中oprd1为类A的对象,则应当把B重载为A类的成员函数,该函数只有一个形参,形参的类型是oprd2所属的类型。经过重载后,表达式oprd1 B oprd2 就相当于函数调用oprd1.operator B(oprd2).
对于前置单目运算符U,如“-”(负号)等,如果要重载U为类的成员函数,用来实现表达式U oprd,其中oprd为A类的对象,则U应当重载为A类的成员函数,函数没有形参。经过重载之后,表达式U oprd相当于函数调用oprd.operator U().
对于后置运算符“++”和“- -”,如果要将它们重载为类的成员函数,用来实现表达式oprd++或oprd--,其中oprd为A类的对象,那么运算符就应当重载为A类的成员函数,这时函数要带有一个整型形参。重载之后,表达式oprd++和oprd—就想当于函数调用oprd.operator++(0)和oprd.operator—(0);
运算符重载就是赋予已有的运算符多重含义。通过重新定义运算符,使它能够用于特定类的对象执行特定的功能,这便增强了C++语言的扩充能力。