C++学习之 运算符重载

运算符重载是一种形式的C++多态

要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数的格式如下:

operatorop(atgument-list)

例如,operator+()重载+运算符,operator*()重载*运算符。op必须是有效的C++运算符,不能虚构一个新的运算符
一个运算符重载示例

Time Time::operator+(const Time & t) const //const表明函数不会更改调用对象
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}

这两种表示方法等价:

total = coding.operator+(fixing);//函数调用

total = coding + fixing;//运算符重载

这两种表示法都将调用operat+()方法,注意:在运算符表示法中,运算符左侧的对象(这里为coding)是调用对象(隐式参数),运算符右边的对象(这里是fixing)是作为参数被传递的对象(显式参数)

编译器将根据操作数的类型来确定如何做:

int a,b,c;
Time A,B,C;
c = a + b;  //使用int的+号
C = A + B;  //用运算符重载调用

可以将两个以上的对象相加,如:

t4 = t1 + t2 + t3;

由于+是从左向右结合的运算符,因此上述语句首先被转换成下面这样:

t4 = t1.operator+(t2 + t3);

然后,函数参数本身被转换成一个函数调用,结果如下:

t4 = t1.operator+(t2.operator+(t3));

函数调用t2.operator+(t3)返回一个Time对象,后者是t2和t3的和,然而,该对象称为函数调用t1.operator+()的参数,该调用返回t1与t2和t3之和的Time对象的和

重载限制

重载的运算符不必是成员函数,但必须至少有一个操作数是用户自定义的类型下面详细介绍C++对用户定义的运算符重载的限制
1、重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符,不能将减法运算符(-)重载为计算两个double值的和,而不是它们的差
2、使用运算符时不能违反运算符原来的句法规则,例如,不能将求模运算符(%)重载成使用一个操作数,同样,不能修改运算符的优先级
3、不能创建新的运算符,例如,不能定义operator**()函数来求幂
4、不能重载下面的运算符:

  • sizeof:sizeof运算符
  • .:成员运算符
  • .*:成员指针运算符
  • :::作用域解析运算符
  • ?::条件运算符

5、大多数运算符可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载

  • =:赋值运算符
  • ():函数调用运算符
  • []:下标运算符
  • ->:通过指针访问类成员的运算符

友元

友元有3种:

  • 友元函数
  • 友元类
  • 友元成员函数

通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限
在前面的示例中,加法运算符结合两个Time值,而乘法运算符将一个Time值与一个double值结合在一起,这限制了运算符的使用方式,记住,左侧的操作数是调用对象,也就是说,下面的语句:

A = B * 2.75;

将被转换为下面的成员函数调用:

A = B.operator*(2.75);

但下面的语句有如何呢

A = 2.75 * B;

从概念上说,2.75 * B应与B * 2.75相同,但第一个表达式不对应与成员函数,因为2.75不是Time类型对象,记住,左侧的操作数应是调用对象
解决这个难题的一种方式是,告知每个人,只能按B * 2.75这种格式编写,不能写成2.75 * B
然而,还有另一种解决方式–非成员函数,记住,大多数运算符都可以通过成员或非成员函数来重载,非成员函数不是由对象调用的,它使用的所有值(包括对象)都是显式参数,这样编译器能将下面的表达式:

A = 2.75 * B;

与下面的函数调用匹配

A = operator*(2.75, B);

该函数的原型如下:

Time operator*(double m, const Time & t);

对于非成员重载运算符函数来说,运算符表达式左边的操作数对应于运算符函数的第一个参数,运算符表达式右边的操作数对应于运算符函数的第二个参数
但引发了一个问题:非成员函数不能直接访问类的私有数据,至少常规非成员函数不能访问,然而,有一类特殊的非成员函数可以访问类的私有成员,它们被称为友元函数

创建友元

创建友元函数的第一步是将其原型放在类声明中,并在原型声明前面加上关键字friend:

friend Time operator*(double m, const Time & t);

该原型意味着两点:

  • 虽然operator*()函数是在勒声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用;
  • 虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同

第二步是编写函数定义,不要在函数定义中加关键字friend,定义如下:

Time operator*(double m, const Time & t)
{
	Time result;
	long totalminutes = t.hours * mult * 60 + t.minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}

有了上述声明和定义后,下面的语句:

A = 2.75 * B;

将转换为如下语句,从而调用刚才定义的非成员友元函数

A = operator*(2.75, B);

实际上,下面的方式对定义进行修改,可以将这个友元函数编写为非友元函数:

Time operator*(double m, const Time & t)
{
	return t * m;
}

原来的版本显式地访问t.minutes和t.hours,所以它必须是友元,这个版本将Time对象t作为一个整体使用,让成员函数来处理私有值,因此不必是友元

常用的友元:重载<<运算符

假设trip是一个Time对象,为显式Time的值,前面使用的是Show(),然而,如果可以像下面这样操作将更好:

cout<<trip;

之所以可以这样做,是因为<<是可被重载的C++运算符之一

1、<<的第一种重载版本

要使Time类知道使用cout,必须使用友元函数,因为下面这样的语句使用两个对象:

cout<<trip;

如果使用一个Time成员函数来重载<<,Time对象将是第一个操作数,就像使用成员函数重载*运算符那样

trip<<cout;

这样会令人迷惑,但通过使用友元函数,可以像下面那样重载运算符:

void operator<<(ostream & os, const Time & t)
{
	os<<t.hours<<" hours, "<<t.minutes<<" minutes";
}

这样可以使用下面的语句:

cout<<trip;
2、<<的第二种重载版本

前面介绍的实现存在一个问题,像下面这样的语句可以正常工作:

cout<<trip;

但这种实现不允许像通常那样将重新定义的<<运算符与cout一起使用

cout<< "Trip time: " << trip << " (Tuesday)\n";  //不可行

要理解这样做不可行的原因以及如何做才能使其可行,首先要了解关于cout操作的一点知识

int x = 5;
int y = 8;
cout << x << y;

C++从左至右读取输出语句,意味着它等同于:

(cout << x) << y;

正如iostream中定义的那样,<<运算符要求左边是一个ostream对象,因此,ostream类将operator<<()函数实现为返回一个指向ostream对象的引用,具体的说,它返回一个指向调用对象(这里是cout)的引用,因此,表达式(cout<<x)本身就是ostream对象cout,从而可以位于运算符的左侧

可以对友元函数采用相同的方法,只要修改operator<<()函数,让它返回ostream对象的引用即可:

ostream & operator<<(ostream & os, const Time & t)
{
	os<<t.hours<<" hours, "<<t.minutes<<" minutes";
	return os;
}

注意,返回类型是ostream&,这意味着该函数返回ostream对象的引用
只有在类声明中的原型才能使用friend关键字,除非函数定义也是原型,否则不能在函数定义中使用该关键字

重载运算符:作为成员函数还是非成员函数

一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据,例如,Time类的加法运算符在Time类声明中的原型如下:

Time operator+(const Time & t) const;  //作为成员函数

这个类也可以使用下面的原型:

friend Time operator+(const Time & t1, const Time & t2);

加法运算符需要两个操作数:
1、对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显式地传递;
2、对于友元版本来说,两个操作数都作为参数来传递
这两个原型都与表达式T2 + T3匹配,其中T2和T4都是Time类型对象,也就是说,编译器将下面的语句:

T1 = T2 + T3;

转换为下面两个的任何一个:

T1 = T2.operator+(T3);  //成员函数版本
T1 = operator+(T2, T3);  //友元函数版本

记住,在定义运算符时,必须选择其中的一种格式,而不能同时选择这两种格式,因为这两种格式都与同一个表达式匹配,同时定义这两种格式将视为二义性错误,导致编译错误

对已重载的运算符进行重载

可以对已重载的运算符进行重载,例如,在C++中,-运算符有两种含义:首先,它是减法运算符,是二元运算符,因为它有两个操作数;其次,使用一个操作数时,它是负号运算符,是一元运算符,只有一个操作数,如:

Vector operator-(const Vector & b) const;  // 原型
Vector Vector::operator-(const Vector & b) const
{
	return Vector(x - b.x, y - b.y);
}

操作数的顺序非常重要,下面的语句:
diff = v1 - v2;
将被转换为下面的成员函数调用:

diff = v1.operator-(v2);

这意味着将从隐式矢量参数减去以显式参数传递的的矢量,所以应使用x-b.x而不是b.x-x
接下来,来看一元负号运算符,它只使用一个操作数,将这个运算符用于数字(如-x)时,将改变它的符号
下面是重载负号的原型和定义:

Vector operator-() const;
Vector Vector::operator-() const
{
	return Vector (-x, -y);
}

现在,operator-()有两种不同的定义,这是可行的,因为它们的特征标不同,可以定义-运算符的一元和二元版本,因为C++提供了该运算符的一元和二元版本,对于只有二元形式的运算符(如除法运算符),只能将其重载为二元运算符
因为运算符重载是通过函数来实现的,所以只要运算符函数的特征标不同,使用的运算符数量与相应的内置C++运算符相同,就可以多次重载同一个运算符

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值