考虑如下一种场景:有一个电视机类Tv和一个遥控器类Remote,如何定义二者的关系呢?首先遥控器不能继承电视机,因为不是is-a关系;其次,遥控器也并非电视机的一部分,因此包含关系has-a也不适合。这时候将Remote类作为Tv类的友元类比较合适,使其能够使用Tv类的任何数据来控制电视机。但是如何定义两个类的位置关系呢?
首先是类的定义,稍后做出解释:
bt_友元类的位置关系.h
1 #ifndef TV_ROMOTE_H 2 #define TV_REMOTE_H 3 #include <iostream> 4 5 class Tv 6 { 7 public: 8 friend class Remote; // 遥控器作为电视机的一个友元类 9 enum{OFF, ON}; // 电视机的开关状态 10 enum{MINVAL = 0, MAXVAL = 50}; // 电视机的音量范围 11 enum{MINCHANNEL = 1, MAXCHANNEL = 100}; // 电视机的频道范围 12 13 Tv(int s = OFF, int v = 15) : state(s), volume(v), channel(1){ } 14 void onOff(){ state = (state == ON) ? OFF : ON; } // 开关机 15 bool volumeUp(); // 音量控制 16 bool volumeDown(); 17 void channelUp(); // 频道控制 18 void channelDown(); 19 void settings() const; // 显示所有当前设置 20 21 private: 22 int state; 23 int volume; 24 int channel; 25 }; 26 bool Tv::volumeUp() 27 { 28 if(volume < MAXVAL) 29 { 30 volume++; 31 return true; 32 } 33 else 34 return false; 35 } 36 bool Tv::volumeDown() 37 { 38 if(volume > MINVAL) 39 { 40 volume--; 41 return true; 42 } 43 else 44 return false; 45 } 46 47 void Tv::channelUp() 48 { 49 if(channel < MAXCHANNEL) 50 channel++; 51 else 52 channel = MINCHANNEL; // 在最大频道时执行Up操作立即返回MINCHANNEL 53 } 54 void Tv::channelDown() 55 { 56 if(channel > MINCHANNEL) 57 channel--; 58 else 59 channel = MAXCHANNEL; // 在最小频道时执行Down操作立即跳到MAXCHANNEL 60 } 61 62 void Tv::settings() const 63 { 64 using std::cout; 65 using std::endl; 66 cout << "电视机当前状态为:" << (state == ON ? "开启" : "关闭") << endl; 67 cout << "当前音量设置为:" << volume << endl; 68 cout << "当前频道设置为:" << channel << endl; 69 } 70 71 class Remote 72 { 73 public: 74 bool volumeUp(Tv& tv){ return tv.volumeUp(); } 75 bool volumeDown(Tv& tv){ return tv.volumeDown(); } 76 void onOff(Tv& tv){ tv.onOff(); } 77 void channelUp(Tv& tv){ tv.channelUp(); } 78 void channelDown(Tv& tv){ tv.channelDown(); } 79 void setChannel(Tv& tv, int chan); 80 }; 81 void Remote::setChannel(Tv& tv, int chan) 82 { 83 tv.channel = chan; 84 } 85 86 #endif // TV_ROMOTE_H
测试用例:
1 #include "bt_友元类的位置关系.h" 2 #include <iostream> 3 4 int main() 5 { 6 using std::cout; 7 using std::endl; 8 Tv tv; // 实例化一台电视机 9 tv.settings(); 10 11 cout << endl; 12 tv.onOff(); // 打开电视机 13 tv.settings(); 14 15 cout << endl; 16 Remote remote; // 实例化一个遥控器 17 remote.volumeDown(tv); // 用遥控器降低音量 18 remote.channelUp(tv); // 用遥控器增加频道 19 remote.setChannel(tv, 10); // 用遥控器切换到10频道 20 tv.settings(); 21 22 cout << endl; 23 remote.onOff(tv); // 用遥控器关闭电视机 24 tv.settings(); 25 26 return 0; 27 }
如上所示,Tv的友元类必须定义在Tv类的后边,如果将Remote类的定义放在Tv类之前,那么由于友元类要使用Tv类的引用变量,那么此时编译器还没有看见过Tv类,造成编译失败。
仔细分析上边Remote的函数,大多数都是通过Tv类的公共接口实现的,就是说根本不需要友元类,一个普通类也可以实现,只有setChannel()函数是直接访问Tv类的私有成员的。因此,其实让Remote::setChannel()函数成为Tv类的友元函数即可,让其他函数保持普通函数的身份,那么此时又如何安排Tv类和Remote类的相对位置关系呢?
若如之前那样安排,就得如下表示:
class Tv
{
friend void Remote::setChannel(Tv& tv, int chan);
}
class Remote{ ... }
当编译器编译Tv类时,它无法明确得知Remote是一个类,因此出现“’Remote’ has not been declared”。而若此时将Remote类定义移到Tv类之前,又会出现Remote中引用的Tv未定义,此时为了避免这种循环依赖关系,可以使用前向声明(forward declaration),如:
1 class Tv; 2 class Remote 3 { 4 public: 5 bool volumeUp(Tv& tv); 6 bool volumeDown(Tv& tv); 7 void onOff(Tv& tv); 8 void channelUp(Tv& tv); 9 void channelDown(Tv& tv); 10 void setChannel(Tv& tv, int chan); 11 }; 12 class Tv{ ... } 13 14 void Remote::setChannel(Tv& tv, int chan) 15 { 16 tv.channel = chan; 17 }
注意:使用前向声明Tv时,Remote只能使用Tv来声明函数,而不能在定义中具体使用Tv的功能,因为在编译器看到完整的Tv类之前,是无法确定Tv中具体有什么的,因此,必须将Remote中引用Tv类的函数实现放到Tv类的定义后进行。
友元类和友元函数的示意图如下:
说明:使用友元类时不需要前向声明,因为友元语句(friend class Remote;)本身告诉编译器Remote是一个类,而使用友元函数时就需要把这一点补上。