类的运算符重载与友元函数

轻松的学习C++, 不要觉得必须使用所有的特性,不要在第一次学习的时候就试图使用所有的的特性
学习这些知识的最好办法是,在我们自己开发的C++程序中使用其中的新特性,当对这些新学习的特性有了充分的认识以后,就可以添加其他的C++特性了。

一、运算符重载

1.1、多态

C++可以定义多个同名的函数,但是特征标(参数列表)不同的函数,这被称为函数多态或函数重载。目的在于让开发人员可以使用同名的函数来完成相同的基本操作,区别是这种操作被用于不同的数据类型。
运算符重载将重载的概念扩展到运算符上,允许赋予C++运算符多种含义,也是一种形式的C++多态。比如运算符可以用于地址,可以得到地址内存放的值,也可以将用于两个数字,得到的是二者的乘积。C++将根据操作数的数目和类型来决定采用哪种操作,其中包括用户自定义的类型。

1.2、运算符重载实现

运算符的重载格式如下:

operatorop(parameter list);        // decalration operation:op

Stock operator+const Stock & os) const// decalration operation:+ in Stock class;
Time operator+const Time & t)const// decalration operation:+ in Time class;

Time Time::operator+(const Time & t) const   // defination operation + in Time class;
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours;
	sum.minutes %= 60;
	return sum;
}

Time total;
Time total2;
Time A(2, 30);
Time B(3, 40);
total = A + B;               //运算符表示法
total2 = A.operator+(B);     //函数表示法

a、其中op必须是一个C++内有效的运算符。
b、将函数参数声明为引用的原因是为了提高程序运行的效率,如果按值传递对象,代码实现功能相同,但是传递引用的速度将更快,使用的内存会更少。
c、实现的函数内返回的是对象,而不是对象的引用,因为sum是函数内的一个局部变量,函数执行完后会被删除,因此引用会指向一个不存在的对象。而返回对象将会创建对象的副本,调用函数可以使用它。使用返回类型Time意味着程序将在删除sum之前构造它的拷贝,调用函数将得到该拷贝。
d、operator+()也是由Time对象来调用的,它以第二个Time对象为参数,并返回一个Time对象。但是该方法本身又被声明为运算符的重载,所以既可以使用运算符表示法也可以使用函数表示法,只要注意运算符左侧的对象是调用对象,右侧的对象是作为参数被传递的对象。

1.3、重载的限制

1、重载后的运算符必须至少有一个操作数是用户自定义的类型,这将防止开发人员为标准类型重载运算符。
2、使用运算符时不能违反运算符原先的句法规则,比如二元运算符求余运算符%不能被重载成一个一元运算符。
3、不能修改运算符的优先级,重载后的运算符与之前具有相同的优先级。比如将+运算符重载成将两个类相加,则与原先的+优先级相同。
4、不能创建新的运算符,只能在原有的运算符基础上重载。
5、部分运算符不允许被重载:
sizeof (sizeof运算符)
.(成员运算符)
.*(成员指针运算符)
::(作用域解析运算符)
?:(条件运算符)
typeid(一个RTTI运算符)
const_cast(强制类型转换运算符)
dynamic_cast(强制类型转换运算符)
reinterpret_cast(强制类型转换运算符)
static_cast(强制类型转换运算符)

6、部分运算符只能通过成员函数进行重载,其余的运算符也允许通过非成员函数进行重载。
=(赋值运算符)
()(函数调用运算符)
[](下标运算符)
->(通过指针访问类成员的运算符)

二、友元

1.1、什么是友元函数

C++严格控制对类的私有成员进行访问,通常情况下,只有通过类对象调用类的公有方法才可以访问,存在一种特殊的访问权限形式:友元。包括友元函数,友元类和友元成员函数。
其中,通过让函数成为一个类的友元,可以赋予该函数与类的成员函数相同的访问权限。

1.2、友元函数的作用

当我们声明和定义完一个运算符重载的成员函数,调用时调用对象必须在左侧,传递参数必须在右侧。比如重载了*运算符,可以有表达式:

A = B * 2.5;

等同于:

A = B.operator*(2.5);

此时如果代码写成了:

A = 2.5 * B;

则无法使用重载的*运算符完成计算,必须按照指定格式编写运算符表达式,因为成员函数完成的重载要求调用对象必须在左侧。
但是可以通过添加非成员函数的方式完成运算符的重载,此时函数不是由类对象调用的,且使用的所有参数必须显式出现,

Time operator*double m, const Time & t)
{
	Time t1;
	t1.minutes = m * t.minutes;
	t1.hours = m * t.hours;
	t1.hours += t1.minutes / 60;
	ti.minutes %= 60;
	return t1;
}

此时编译器可以上述表达式跟非成员函数的调用匹配上:

A = operator* (2.5 , B)

对于非成员函数来说,运算符表达式的左侧对应了函数的第一个参数,右侧操作数对应了函数的第二个参数。但是此时因为函数不是成员函数,所以无法访问类的私有数据,所以定义了一类特殊的非成员函数可以访问类的私有成员,即友元函数。只有在类的声明中将该函数定义成友元函数才能完成上述调用。

1.3、友元函数的创建

第一步:将该函数的原型放在类的声明中,并在原型声明前加上关键字friend。此时可以得知,该函数是在类声明中声明的,但是并不是类的成员函数,所以不能使用成员运算符来调用,但是与成员函数的访问权限相同。

第二步:编写函数定义,因为不是成员函数,所以写定义时不需要添加作用域解析运算符进行限定,另外定义时不再需要添加friend关键字。

当我们有了成员函数的运算符重载,又创建了友元函数的非成员函数运算符重载,无论用户使用下面哪些一种表达式,编译器都可以正确处理。

A = 2.5 * B;      //友元函数重载运算符
A = B * 2.5;	  //类成员函数重载运算符

另外可以通过修改非成员函数的定义,可以将友元函数变为可选的非友元函数。

Time operator*double m, const Time & t)
{
	return t * m;       //此处调用了类的成员函数 t.operator+(m)
}

因为这种定义方式已经不需要访问t的私有成员了,而是作为一个整体使用,让成员函数来处理私有数据,所以此时可以不一定必须作为友元函数了。但是作为友元函数来使用的兼容性会更高,后期维护如果还是要访问私有数据,则修改函数的定义部分即可,不需要修改函数原型。

如果要为类重载运算符,并将非类对象作为其第一个操作数,可以用友元函数来反转操作数的顺序(可以访问私有数据完成运算,也可以调用成员函数,将类对象作为整体完成运算)。

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

对于大部分运算符来说,可以选择使用成员函数或非成员函数实现运算符的重载,其中非成员函数一般是友元函数,可以访问私有数据或不访问。

Time operator+(const Time & t) const;        //成员函数版本,通过this指针隐式传递第一个操作数,函数参数显式传递第二个操作数
friend Time operator+(const Time & t1, const Time & t2); //友元函数版本原型,通过函数参数显式传递两个操作数

对应的编译器调用方式:

T1 = T2.operator+(T3);
T1 = operator+(T2 , T3);

对应相同的用户使用表达式:

T1 = T2 + T3;

二级标题

三级标题

四级标题
五级标题
六级标题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值