友元的位置关系

考虑如下一种场景:有一个电视机类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是一个类,而使用友元函数时就需要把这一点补上。

 

 

 

你可能感兴趣的:(关系)