友元函数

135 阅读3分钟

「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战

❓ 重载<< ❔

class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);//友元,位置可任意,一般是开头
	friend istream& operator>>(istream& in, Date& d);//友元
public:
	Date(int year = 0, int month = 0, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	/*void operator<<(ostream& out)
	{
		out << _year << "/" << _month << "/" << _day << endl;
	}*/
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& out, const Date& d)//支持连续输出
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}
istream& operator>>(istream& in, Date& d)//支持连续输入
{
	in >> d._year >> d._month >> d._day;
	return in;
}
int main()
{
	Date d1, d2;
	cin >> d1 >> d2;

	//cout << d1 ? d1 << cout
	//cout << d1;
	/*d1.operator<<(cout);
	d1 << cout;*/
	cout << d1 << d2;
	return 0;
}

📝说明

cin | cout 怎么接收 ❓ 在这里插入图片描述 在 C++ 里 cout 是一个 ostream 的对象;cin 是一个 istream 的对象。

cout << d1 | d1 << cout(d1.operator<<(cout)) ❓

为啥不能 cout << d1 呢 ?

之前说过运算符有几个操作数,重载函数就有几个参数。如果有两个操作数,左操作数是第一个参数,右操作数是第二个参数。

在 Date 类成员函数 operator<< 里对象是第一个参数,因为隐含的 this 指针已经默认占据了,那么 cout 就只能作第二个操作数了。可以倒也可以,但是用起来不符合流运算符原来的特性。

怎么 cout << d1 呢 ?

也就是把 cout 作为第一个参数,那么这里就不能用成员函数了,之前我们用成员函数是因为成员变量是私有的。

如何取舍:使用成员函数 | 使用全局函数。这里成员函数的可读性差影响较大,所以将之舍弃,使用全局函数。

支持 cout << d1 后怎么解决私有 ?

解决方案1:提供公有的成员函数 GetYear、GetMonth、GetDay

解决方案2:友元函数

这里我们就引出了友元,C++ 默认是不能在类外访问私有的,但是它提供了友元以帮助我们解决这种场景的问题。友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加 friend 关键字。

cout << d1 << d2; ❓

注意与连续赋值大相径同,只是方向相反。

💨小结

1、友元函数可访问类的私有和保护成员,但不是类的成员函数。

2、友元函数不能用 const 修饰,因为 const 修饰的是 this 指针指向的对象。

3、友元函数可以在类定义的任何地方声明,不受类访问限定符限制。

4、一个函数可以是多个类的友元函数。

5、友元函数的调用与普通函数的调用和原理相同。

注意以上部分概念需要与后面的知识结合 ,不懂的可先忽略。

🍳拓展

这里主要拓展代码错误的解决能力,先来看一段代码。

class A
{
	friend void f(const A& aa, const B& bb);
public:
	A(int a = 0)
		: _a(0)
	{}
private:
	int _a;
};
class B
{
	friend void f(const A& aa, const B& bb);
private:
	int _b = 0;
	A _aa;
};
void f(const A& aa, const B& bb)
{
	cout << aa._a << endl;	
	cout << bb._b << endl;
}
int main()
{
	A aa;
	B bb;
	f(aa, bb);
	return 0;
}

📝分析

相信到了这里绝大部分的人都能凭借着自己的经验去解决大部分的 bug了。但是对于上面代码出现的错误又百思不得其解。 在这里插入图片描述 注意面对这种情况的时候,有时候编译器报的错误是不准确的,这里有两条建议能帮助提升查找 bug 的能力。

1、有很多错误的时候,一定是看最上面的错误,因为下面的错误有可能是上面的错误间接导致的。

2、排除法,这里有一百行代码(程序崩了),你注释掉了部分代码程序正常了,那么不用怀疑,错误就是注释处代码引发的。

经过分析,我们发现错误处是:在 B 类的友元里能找到 A、B;但是在 A 类的友元里就找不到 B 了。所以解决方法就是加前置声明 —— class B;