C++类和对象之友元(friend)详解
引言
一、友元概述
C++中,友元(friend)是一种机制,它允许一个类的非成员函数或另一个类访问该类的私有成员。友元可以在类定义中声明,在类定义外部实现。
类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部作用域之外访问。但是有时候需要在类的外部访问类的私有成员,因此出现了友元函数;友元函数是一种特权函数,允许访问私有成员。可以把一个全局函数、或者类的成员函数、甚至整个类声明为友元。
需要注意的是,友元机制破坏了类的封装性,因此应该谨慎使用。如果过度使用友元机制,会导致代码难以维护和扩展。
二、友元的语法
使用friend关键字声明为友元。声明一个友元函数或友元类的语法如下:
(1)友元函数的声明语法。
class ClassName {
private:
    // 私有成员
public:
    friend ReturnType FunctionName(ParameterList); // 声明友元函数
};
 
其中,ClassName为当前类名,ReturnType为友元函数的返回类型,FunctionName为友元函数名,ParameterList为参数列表。
(2)友元类的声明语法。
class ClassName1 {
private:
    // 私有成员
public:
    friend class ClassName2; // 声明友元类
};
 
其中,ClassName1为当前类名,ClassName2为被声明为友元的类名。
需要注意的是,在程序中定义和实现了一个被声明为该类的友元的全局函数或其他类中的成员函数时,在该函数前需要加上 friend ClassName::FunctionName() 来指定这个函数是属于哪个类。例如:
class MyClass {
private:
    int privateMember;
public:
    friend void friendFunction(MyClass obj);
};
void friendFunction(MyClass obj) {
    cout << "The value of private member is: " << obj.privateMember;
}
int main() {
    MyClass myObj;
    
    myObj.privateMember = 10;
    
    friendFunction(myObj); // 调用友元函数
    
    return 0;
}
 
如果将 friendFunction() 函数定义放在 MyClass 类外面,则需要使用以下语法:
void friend MyClass::friendFunction(MyClass obj) { ... }
 
三、友元的应用举例
3.1、普通全局函数作为类的友元
#include <iostream>
#include <string>
using namespace std;
class Room {
	friend int visiting(Room &room);
private:
	string bedRoom;//私有
public:
	string setingRoom;//公共
public:
	Room(string bedRoom, string setingRoom)
	{
		this->bedRoom = bedRoom;
		this->setingRoom = setingRoom;
	}
};
// 普通全局函数
int visiting(Room &room)
{
	cout << room.setingRoom << endl;
	cout << room.bedRoom << endl;//如果没有设置友元则无法访问
	return 0;
}
int main()
{
	Room room("Bed", "seting");
	visiting(room);
	return 0;
}
 
3.2、类的某个成员函数作为另一个类的友元
#include <iostream>
#include <string>
using namespace std;
class Room;// 向前声明,只能说明类名称
class Good {
public:
	int visiting01(Room &room);
	int visiting02(Room &room);
};
class Room {
	friend int Good::visiting02(Room &room);
private:
	string bedRoom;//私有
public:
	string setingRoom;//公共
public:
	Room(string bedRoom, string setingRoom)
	{
		this->bedRoom = bedRoom;
		this->setingRoom = setingRoom;
	}
};
// 成员函数
int Good::visiting01(Room &room)
{
	cout << room.setingRoom << endl;
	//cout << room.bedRoom << endl;// error
	return 0;
}
int Good::visiting02(Room &room)
{
	cout << room.setingRoom << endl;
	cout << room.bedRoom << endl;//如果没有设置友元则无法访问
	return 0;
}
int main()
{
	Room room("Bed", "seting");
	Good good;
	good.visiting01(room);
	good.visiting02(room);
	return 0;
}
 
3.3、整个类作为另一个类的友元
#include <iostream>
#include <string>
using namespace std;
class Room;// 向前声明,只能说明类名称
class Good {
public:
	int visiting01(Room &room);
	int visiting02(Room &room);
};
class Room {
	friend class Good;
private:
	string bedRoom;//私有
public:
	string setingRoom;//公共
public:
	Room(string bedRoom, string setingRoom)
	{
		this->bedRoom = bedRoom;
		this->setingRoom = setingRoom;
	}
};
// 成员函数
int Good::visiting01(Room &room)
{
	cout << room.setingRoom << endl;
	cout << room.bedRoom << endl;// OK
	return 0;
}
int Good::visiting02(Room &room)
{
	cout << room.setingRoom << endl;
	cout << room.bedRoom << endl;//如果没有设置友元则无法访问
	return 0;
}
int main()
{
	Room room("Bed", "seting");
	Good good;
	good.visiting01(room);
	good.visiting02(room);
	return 0;
}
 
四、友元的注意事项
-  
友元函数可以访问类的私有成员和保护成员,因此需要谨慎使用。过度使用友元函数可能会破坏类的封装性,导致代码难以维护。
 -  
友元关系不能被继承。即使是派生类也不能访问基类中声明为友元的函数或类。
 -  
友元关系是单向的。如果A是B的友元,那么B并不一定是A的友元。
 -  
在定义友元函数时,需要在函数名前加上关键字“friend”,同时在类定义中声明该函数。
 -  
友元关系不具备传递性。即如果A是B的友元,B是C的友元,并不意味着A也是C的友元。
 -  
可以将一个类声明为另一个类的友元,这样就可以让该类中所有成员都能够访问另一个类中的私有成员和保护成员。
 -  
如果一个函数需要访问多个类中的私有成员和保护成员,可以将其声明为这些类中任意一个类的友元即可。
 
五、友元案例
设计一个电视机类,电视机属性有:
- 开机和关机;
 - 音量;
 - 频道;
 - 操作音量的方法;
 - 操作频道的方法。
 
电视机只能逐一调整频道,不能指定频道。添加遥控类,遥控类除了拥有电视机 已有功能,再添加根据输入调台功能。
将遥控器类作为电视机类的友元类。
class TV;
class Remote {
private:
	TV *p;
public:
	Remote(TV *p)
	{
		this->p = p;
	}
	void onOrOff();
	void upVolume();
	void downVolume();
	void upChannel();
	void downChannel();
	void showTV();
	void setChannel(int num);
};
class TV {
	friend class Remote;
	enum{OFF,ON};
	enum{minVol,maxVol=100};
	enum{minChannel,maxChannel=28};
private:
	int status;
	int volume;
	int channel;
public:
	TV()
	{
		status = OFF;
		volume = minVol;
		channel = minChannel;
	}
	void onOrOff();
	void upVolume();
	void downVolume();
	void upChannel();
	void downChannel();
	void showTV();
};
void TV::onOrOff()
{
	status = (status == OFF ? ON : OFF);
}
void TV::upVolume()
{
	if (volume >= maxVol)
	{
		cout << "音量已经最大啦" << endl;
		return;
	}
	volume++;
}
void TV::downVolume()
{
	if (volume < minVol)
	{
		cout << "音量已经最小啦" << endl;
		return;
	}
	volume--;
}
void TV::upChannel()
{
	if (channel >= maxChannel)
	{
		cout << "频道已经最大啦" << endl;
		return;
	}
	channel++;
}
void TV::downChannel()
{
	if (channel < minChannel)
	{
		cout << "频道已经最小啦" << endl;
		return;
	}
	channel--;
}
void TV::showTV()
{
	cout << "电视机状态:" << (status == OFF ? "关" : "开") << endl;
	cout << "电视频道:" << channel << endl;
	cout << "电视音量:" << volume << endl;
	cout << "------------------------------------" << endl;
}
// -----------------------------------------
void Remote::onOrOff()
{
	p->onOrOff();
}
void Remote::upVolume()
{
	p->upVolume();
}
void Remote::downVolume()
{
	p->downVolume();
}
void Remote::upChannel()
{
	p->upChannel();
}
void Remote::downChannel()
{
	p->downVolume();
}
void Remote::showTV()
{
	p->showTV();
}
void Remote::setChannel(int num)
{
	if (num >= TV::minChannel && num<=TV::maxChannel)
	{
		p->channel = num;
		return;
	}
	cout << "输入的频道不合法" << endl;
}
int main()
{
	TV tv;
	tv.showTV();
	
	tv.onOrOff();
	tv.upChannel();
	tv.upChannel();
	tv.upVolume();
	tv.showTV();
	tv.onOrOff();
	cout << "使用遥控器:" << endl;
	Remote rtv(&tv);
	rtv.showTV();
	rtv.onOrOff();
	rtv.upChannel();
	rtv.upChannel();
	rtv.downChannel();
	rtv.upVolume();
	rtv.showTV();
	rtv.onOrOff();
	return 0;
}
 
输出:
电视机状态:关
电视频道:0
电视音量:0
------------------------------------
电视机状态:开
电视频道:2
电视音量:1
------------------------------------
使用遥控器:
电视机状态:关
电视频道:2
电视音量:1
------------------------------------
电视机状态:开
电视频道:4
电视音量:1
------------------------------------
 
总结
- 友元函数。
 
友元函数是一个普通的非成员函数,但它被声明为某个类的友元。这意味着该函数可以访问该类的所有私有成员和保护成员。声明方式为在类定义中使用“friend”关键字,例如:
class MyClass {
  friend void myFunction();
};
 
- 友元类。
 
友元类是指某个类A将另一个类B声明为自己的友元,在这种情况下,B可以访问A的私有成员和保护成员。声明方式为在类定义中使用“friend”关键字并加上要作为友元的类名,例如:
class MyClass {
  friend class MyFriendClass;
};
 
- 友元成员函数。
 
友元成员函数是指某个函数被声明为另一个类A的友元,并且该函数所属于另一个与A无关的类B。这意味着该函数可以访问A对象中所有私有成员和保护成员。声明方式为在B类定义中使用“friend”关键字和A类型参数,例如:
class MyClassA {
  friend void MyClassB::myFunction(MyClassA&);
};
 
- 友元作用域。
 
友元的作用域是在声明它的类中,而不是在被声明为友元的函数或类中。因此,在同一作用域中可以使用相同名称的友元,但它们分别属于不同的类。
- 友元与继承。
 
子类不能直接访问父类的私有成员,但如果将子类声明为父类的友元,则子类就可以访问父类的私有成员和保护成员。这种情况下,应该谨慎考虑是否破坏了封装性。











