运算符重载
对于数组的相加来说,需要使用循环语句,将两个数组的各个元素逐个相加,然而,我们可以使用运算符重载,重载+ 运算符,就可以直接使用加法来进行两个数组的相加。
要想重载运算符,需要使用被称为运算符函数的特殊函数形式:
operatorop(argument-list)
比如说下面两个函数一个重载乘法运算符,一个重载加法运算符:
operator+();
operator*();
op必须是有效的C++运算符。
不能够虚构一个新的符号。
再有,假设针对stock对象,重载了加法运算符来进行两个对象的相加,可以有如下写法:
stock s1,s2;
......
s1=s1+s2;
实际上编译器像下面这样解释运算符:
s1=s1.operate(s2);
C++对运算符重载做出了一些限制,稍后会有。
注意 不要返回指向局部变量或者临时对象的引用,函数执行完毕后,局部变量和临时变量将消失,引用将指向不存在的数据。
重载限制
多数运算符多可以使用上面的方式重载,重载的运算符不必是成员函数,但必须至少有一个操作数是用户定义的类型。
下面是重载的一些限制:
- 重载后的运算符必须至少有一个操作数是用户自定义的类型。 这将防止用户为标准类型重载运算符,因此不能够将减法运算符重载为计算两个类型值的和,而不是他们的差,这样可以保证程序的正常运行。
- 使用运算符时不能够违反运算符原来的句法规则。 比如说
int x,y;
int z=x%y;//这是正确的
int c=%y;//这样违反了原来的句法规则是错误的
同样的, 不能够改变运算符的优先级。 因此,重载后的运算符与重载的运算符具有相同的优先级。
- 不能够创建新的运算符。
- 不能够重载下面的运算符:
- sizeof
- .
- .* 成员指针运算符
- ::
- ?:
- typeid
- const_cast
- dynamic_cast
- reinterpret_cast
- static_cast
- 只能够通过成员函数重载的运算符:
- =
- ()
- []
- ->
下面表中是即能够通过成员函数又能够通过非成员函数重载的运算符:
+ | - | * | / | % | ^ |
---|---|---|---|---|---|
& | | | ~= | ! | = | < |
> | += | -= | *= | /= | %= |
^= | &= | |= | << | >> | >>= |
<<= | == | != | <= | >= | && |
|| | ++ | – | , | ->* | -> |
() | [] | new | delete | new[] | delete[] |
友元
通常,公有类方法提供唯一的访问途径,但是有时候这种限制太过于严格,以至于不适合特定的编程问题。因此,C++提供了另外一种访问权限:友元。
- 友元函数
- 友元类
- 友元成员函数
通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。下面只学习友元函数,另外两种在专门的章节学习。
在为类重载二元运算符时,通常需要友元函数,假设我们重载乘法运算符:
stock operator*(const double s1)const;
在对其进行一个double类型数据的乘法时,必须要这样写:
stock s2;
double s1;
s2=s2*s1;
而不能够交换两者的顺序,左侧的操作数是调用对象,右侧的操作数是待乘数据。
上面的乘法语句实际上等价于:
s2=s2.operator*(s1);
因此,顺序不可交换。为解决这一问题,可以让每一个使用这个类的人只是用这一种方式编写,这是一种对 服务器友好-客户警惕 的解决方案。
也可以有另一种解决方法: 使用非成员函数。
stock operator*(const double,const stock&);
这样,乘号左边的操作数对应于第一个参数,右边的操作数对应于第二个操作数。实现了特定顺序的乘法,但是非成员函数却不能够访问私有数据,这时候就需要使用友元函数了。
创建友元
创建友元的第一步是将其原型放在类声明中,并在声明的原型前加上关键字friend :
friend stock operator*(const double,const stock&);
这意味着两点:
- 虽然函数声明是在类声明中,但是他并不是成员函数,不能够用成员运算符来调用。
- 虽然函数不是成员函数,但是它与成员函数访问权限相同。
然后就是定义友元函数,因为他不是成员函数,所以定义时不需要使用成员限定符,也不用再使用关键字friend :
stock operator*(const double m,const stock& s1)
{
......
}
实际上,按照下面的方式对定义修改可以将友元函数编写为非友元函数:
stock operator*(const double m,const stock& s1)
{
return s1*m;
}
由于原来需要访问对象中的私有成员数据,所以需要访问权限,但是改变后,只是调用成员函数改变顺序,因此它不必是友元函数。但是把它作为友元函数也是一个好操作,这样的话,他将正式作为类接口的组成部分。此外,如果以后发现需要直接访问私有成员数据,只需要修改函数定义而不用修改函数原型。
总而言之
如果要为类重载运算符,并将非类的项作为其第一个操作数,则可以使用友元函数来反转操作数的顺序。
常用的友元——重载 << 运算符
一个很有用的类特性是,可以对<< 运算符重载,使之能够与输出cout 一起使用来显示对象的内容。
我们可以专门设置成员函数用来显示对象的数据,但是直接使用cout输出流会更简洁。
第一种重载版本
重载必须使用友元函数,因为这样的语句使用两个对象:
cout<<s1;
一个是ostream 类对象cout 一个是对象s1 ,而第一个操作数是ostream对象。
如果使用类成员函数来重载运算符,那么此类对象将是第一个操作数,输出将变为下面这种迷惑行为:
s1<<cout;
因此,可以使用友元函数来进行重载:
void operator<<(ostream &os,const stock&t)
{
os<<t.value<<t.value2;
}
然后就可以使用cout来显示对象的相应值。
第二种重载版本
前面那种定义有一个问题。
在ostream中定义,<<左边必须是一个ostream对象,对于
int a,b;
......
cout<<a<<b;
ostream中定义<< 返回一个指向调用对象的引用,因此,(cout<<a) 实际上就是一个ostream对象cout。
但是第一个版本的返回值为void,所以不能够这样使用,可以参照这种设置,也将返回值设为ostream类对象。
ostream & operator<<(ostream &os,const stock&t)
{
os<<t.value<<t.value2;
return os;
}
也就是说,对于这样的语句:
cout<<trip;
编译器转换为下面的调用:
operator<<(cout,trip);
由于返回对象,因此可以像正常使用cout一样,输出对象。
上面的定义也可以把输出输出到文件中。
类继承属性让ostream引用能够指向ostream对象和ofstream对象。
注意
一般来说,要重载<<运算符,来显示c_name对象,可以使用一个友元函数:
ostream& operator<<(ostream& os,const c_name& obj)
{
os<<...;
return os;
}
只有在类声明中的友元函数原型才能够使用关键字friend ,否则,除非定义也是原型,不能够在函数定义中使用该关键字。