cpp►运算符重载与友元friend

Posted itzyjr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了cpp►运算符重载与友元friend相关的知识,希望对你有一定的参考价值。

如果是设置成友元函数,一定要注意:
(1)当重载友元函数时,将没有隐含的参数this指针。这样,对双目运算符,友元函数有2个参数,对单目运算符,友元函数有一个参数。
(2)operatorop对于成员函数只能有一个参数。
(3)有些运行符不能重载为友元函数,它们是 =, (), [], ->。

class Time {
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h, int m = 0);
	Time operator*(double n) const;
};

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

int main() {
	Time t(3, 35);
	Time temp = t * 1.17;// 运用重载运算符operator*
}

如果在operator操作符函数里面多加一个参数,则报错如下:

如果operator
操作符函数前面加上关键字friend,则报错如下:

意思是非成员函数(此处为友元函数)不允许被const后缀修饰。由于友元函数不存在调用者本身,所以不存在被修改或不被修改的特性,故不能有const后缀。
如果在以上基础上去掉报错的const后缀,则报错如下:

意思是非成员函数需要一个类类型的参数或枚举类型的参数。由于参数列表没有以上类型的参数,所以有问题。

对于原正确的代码,operator*是一个成员函数,即此操作符函数是被对象所调用的,用“.”运算符,如下调用被转化:

Time temp = t * 1.17;
它在编译器被转化为调用:t * 1.17 => t.operator*(1.17)

如下写法:

Time temp = 1.17 * t;
则被转化为:1.17.operator*(t),显然1.17不是个对象,出错。

要想以上代码正确合理运行,必须跳脱出对象调用,可以用友元函数去完成操作:

friend Time operator*(double n, Time& t);
声明为友元函数,就解除了对象去调用的禁锢。
1.17 * t => operator*(1.17, t),这就正常了。

对于如下友元函数:

friend ostream& operator<<(ostream& output, const Distance& D) {
	output << "F=" << D.feet << ",I=" << D.inches;
	return output;
}

即,ostream对象的<<运算符被重载,当调用output<<D时,被转化为operator<<(output, D)。
对于cout << “dist=” << D; 即分解为:(cout << “dist=”) << D,而cout << "dist="是内置重载了的成员函数:

ostream& ostream::operator<<(const char* s) {
    //输出s的代码
    return * this;
}

cout << "dist="对应cout.operator<<(“dist=”),返回一个ostream引用,然后执行自己重载的<<,所以上述代码正常执行。

那么,以上友元operator<<操作符函数可以写成成员函数的形式吗?当然可以:

ostream& operator<<(ostream & os) {
	os << "F=" << feet << ",I=" << inches;
	return os;
}

这时调用起来会有点奇怪,但能正常运行。调用如下:

Distance d(20, 18);
d << cout;// 相当于d.operator<<(cout)

为什么能在class里面直接重载比如<<运算符呢?
因为有#include< iostream>,而iostream继承了istream和ostream,所以include iostream时,就有了ostream的特性,而cout继承了ostream,所以在class里面直接可以std::cout使用屏幕输出功能,所以可以重载输出运算符。

注:调用cout << d应使用cout对象本身,而不是它的拷贝,因此该函数按引用(而不是按值)来传递该对象。这样,表达式cout << d将导致os成为cout的一个别名;而表达式cerr << d将导致os成为cerr的一个别名。

对于如下友元函数原型:

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

在编写函数定义时,==因为它不是成员函数,所以不要使用Time::限定符。==另外,不要在定义中使用关键字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;
}

不是成员函数,就不要Time::限定符。就像下面代码:

// abc.h
int sum(int a, int b);

// def.cpp
#include "abc.h"
int sum(int a, int b) {
	return a + b;
}

肯定在实现sum函数时,不需要任何前缀限定符。所以对于非成员函数的实现,同理。

C++控制对类对象私有部分的访问。通常,公有类方法提供唯一的访问途径,但是有时候这种限制太严格,以致于不适合特定的编程问题。在这种情况下,C++提供了另外一种形式的访问权限:友元。
友元有3种:友元函数、友元类、友元成员函数。

类的友元函数是非成员函数,其访问权限与成员函数相同。这句话怎么理解?

Time operator*(double m, const Time& t) {
	long t1 = t.hours * m * 60 + t.minutes * m;// 正确
	long t2 = hours * m * 60 + minutes * m;// 错误
	// ...
}


为什么友元函数里面直接访问Time类的private成员hours和minutes报错提示undefined呢?
因为友元函数不是成员函数,它的定义也不包含作用域解析运算符::,所以必定是无法访问Time类的private成员的。
t.hours这句为什么正确访问到呢?一个Time类对象t能直接用“.”运算符访问private成员?这不应该啊,之所以能成,唯一原因就是因为friend友元赋予了类对象直接访问private成员的能力,虽然friend友元是不隐含this参数的,但可以通过类对象直接用dot访问到成员,这就是答案,仅此而已。

作为成员函数还是非成员函数?
对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载。一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据。例如,Time类的加法运算符在Time类声明中的原型如下:

Time operator+(const Time & t) const; // 成员函数
这个类也可以使用下面的原型:
friend Time operator+(const Time & t1, const Time & t2); // 非成员函数

法运算符需要两个操作数。对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显式地传递;对于友元版本来说,两个操作数都作为参数来传递。

非成员版本的重载运算符函数所需的形参数目与运算符使用的操作数数目相同;而成员版本所需的参数数目少一个,因为其中的一个操作数是被隐式地传递的调用对象。

T1 = T2 + T3;
转换为下面两个的任何一个:
T1 = T2.operator+(T3); // 成员函数
T1 = operator+(T2, T3); // 非成员函数

记住,在定义运算符时,必须选择其中的一种格式,而不能同时选择这两种格式。因为这两种格式都与同一个表达式匹配,同时定义这两种格式将被视为二义性错误,导致编译错误。
那么哪种格式最好呢?对于某些运算符来说(如前所述),成员函数是唯一合法的选择。在其他情况下,这两种格式没有太大的区别。有时,根据类设计,使用非成员函数版本可能更好(尤其是为类定义类型转换时)。

以上是关于cpp►运算符重载与友元friend的主要内容,如果未能解决你的问题,请参考以下文章

C++ 提高教程 模板-类模板与友元

5.友元运算符重载

friend class

c++知识点总结--友元&运算符重载

黑马程序员 C++教程从0到1入门编程笔记4C++核心编程(类和对象——封装权限对象的初始化和清理构造函数析构函数深拷贝浅拷贝初始化列表友元friend运算符重载)

C++运算符重载中 重载为类的成员函数和重载为类的友元函数 的区别是啥?