C++ | 友元

时间:2023-02-28 12:13:29

类的主要特点之一就是数据的封装,即类的私有成员无法在类的外部(作用域之外)进行访问。但是,有时需要在类的外部访问类的私有成员,怎么办?C++提供了另外一种形式的访问权限:友元

创建友元函数

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

friend void operator(param1, param2 , ... );

该原型意味着两点:

  • 虽然operator()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用;
  • 虽然operator()函数不是成员函数,但它与成员函数的访问权限相同(可访问对象任意成员属性,包括私有属性)。
#include<iostream>
#include<string>
using namespace std;

class TV{
public:
	friend void show(const TV &t); // 类内声明友元函数(friend关键字只出现在声明处)
	enum state_choice{OFF, ON};
	enum volume_range{MinVal=0, MaxVal=10};
	TV():state(OFF), volume(5){}
private:
	int state; // 开/关
	int volume; // 音量
};

void show(const TV &t){
	cout << "TV's state=" << t.state << "  TV's volume=" << t.volume << endl;
}


int main(){
	TV t;
	show(t);

	return 0;
}

输出:

TV's state=0  TV's volume=5

友元声明可以位于共有、私有或保护部分,其所在的位置无关紧要。

2. 友元类

电视:class TV

class TV{
public:
	friend class Remote; // 声明友元类,Remote可以访问TV的私有成员
	enum state_choice{OFF, ON};
	enum volume_range{MinVal=0, MaxVal=10};
	TV():state(OFF), volume(5){}
	void onOff(){ 
		state = (state == ON)?OFF:ON;
		cout << "电视 ";
		if(state == ON)
			cout << "已开启..." << endl;
		else
			cout << "已关闭..." << endl;
		
	}
	bool volUp(){
		if(volume < MaxVal){
			volume++;
			cout << "音量+1." << endl;
			return true;
		}else{
			cout << "您已达最大音量!" << endl;
			return false;
		}
	}
	bool volDown(){
		if(volume > MinVal){
			volume--;
			cout << "音量-1." << endl;
			return true;
		}else{
			cout << "您已达最小音量!" << endl;
			return false;
		}
	}
private:
	int state; // 开/关
	int volume; // 音量
};

遥控:class Remote

class Remote{
public:
	void onOff(TV &t){ t.onOff();}
	bool volUp(TV &t){
		if(t.state == 1){
			return t.volUp();
		}else{
			cout << "电视尚未开启,无法增减音量!" << endl;
			return false;
		}
	}
	bool volDown(TV &t){ 
		if(t.state == 1){
			return t.volDown();
		}else{
			cout << "电视尚未开启,无法增减音量!" << endl;
			return false;
		}
	}
	void show(TV &t){ 
		cout << "电视状态:" << t.state << "\t电视音量:" << t.volume << endl;
	}
};

使用:

int main(){
	TV t;
	Remote r;

	r.volUp(t);
	r.show(t);
	cout << endl;

	r.onOff(t);
	r.show(t);
	cout << endl;

	r.volUp(t);
	r.show(t);
	cout << endl;

	r.onOff(t);
	r.show(t);
    
	return 0;
}

输出:

电视尚未开启,无法增减音量!
电视状态:0      电视音量:5

电视 已开启...
电视状态:1      电视音量:5

音量+1.
电视状态:1      电视音量:6

电视 已关闭...
电视状态:0      电视音量:6

3. 友元成员函数

3.1 循环依赖

如果想让Remove的show方法称为TV类的友元成员函数:

class TV{
    friend void Remote::show(TV &t);
};

然而,要使编译器能够处理这条语句,它必须知道Remote的定义。否则,它无法知道Remote是一个类,而show是这个类的方法。这意味着应将Remote的定义放到TV的定义前面。而Remote的方法show中又提到了TV对象,这意味着TV类定义又要位于Remote定义之前,这就出现了循环依赖的情况。

C++ | 友元

3.2 解决循环依赖

避开这种循环依赖的方法是使用前向声明(forward declaration)。为此,需要在Remote定义的前面插入下面的语句:

class TV; 

这样,排列次序应如下:

class TV; // forward declaration
class Remote{...};
class TV{...};

能否像下面这样排列呢?

class Remote; 
class TV{...};
class Remote{...};

不能!

原因在于,编译器在TV类的声明中看到Remote的一个方法被声明为TV类的友元之前,应该先看到Remote类的声明和show()方法的声明。

还有一个问题尚需解决。那就是Remote的show方法中调用了TV的私有变量state和volume,这表明编译器此时必须已经看到了TV类的声明,这样才知道TV类中有哪些成员,但是TV类的声明又位于Remote之后,解决方法就是使Remote声明中只包含方法声明,具体的方法定义放在TV类之后。完整正确代码如下:

#include<iostream>
#include<string>
using namespace std;
class TV;

class Remote{
public:
	void show(TV &t);
};

class TV{
public:
	friend void Remote::show(TV &t); // 声明友元成员函数
	enum state_choice{OFF, ON};
	enum volume_range{MinVal=0, MaxVal=10};
	TV():state(OFF), volume(5){}
private:
	int state; // 开/关
	int volume; // 音量
};

void Remote::show(TV &t){ 
	cout << "电视状态:" << t.state << "\t电视音量:" << t.volume << endl;	
}


int main(){
	TV t;
	Remote r;

	r.show(t);

	return 0;
}

输出:

电视状态:0      电视音量:5

注意:让整个Remote类称为友元类并不需要前向声明,因为友元语句本身已经指出Remote是一个类:

friend class Remote;