类的主要特点之一就是数据的封装,即类的私有成员无法在类的外部(作用域之外)进行访问。但是,有时需要在类的外部访问类的私有成员,怎么办?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定义之前,这就出现了循环依赖
的情况。
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;