C++學習筆記之友元
C++控制對類對象私有部分(private)的訪問,通常只能通過公有的(public)類方法去訪問。但是有時候這種限制太嚴格,不適合特定的問題,於是C++提供了另外一種形式的訪問權限:友元。友元有三種:
友元函數
友元類
友元成員函數
二、友元函數
通過讓函數稱為友元函數,可以賦予該函數與類的成員函數相同的訪問權限。為何需要友元呢?在為類重載二元運算符時常常需要友元,關於運算符的重載可以參考我的博文:
C++學習筆記之運算符重載
下面舉例說明:
復制代碼
1 //mytime0
2 #ifndef MYTIME0_H
3 #define MYTIME0_H
4
5 class Time
6 {
7 private:
8 int hours;
9 int minutes;
10 public:
11 Time();
12 Time(int h, int m = 0);
13 void addMin(int m);
14 void addHr(int h);
15 void reset(int h = 0, int m = 0);
16 Time operator*(double n) const;
17 void show() const;
18 };
19
20 #endif
復制代碼
復制代碼
1 #include <iostream>
2 #include "mytime0.h"
3
4 Time::Time()
5 {
6 hours = minutes = 0;
7 }
8
9 Time::Time(int h, int m)
10 {
11 hours = h;
12 minutes = m;
13 }
14
15 void Time::addMin(int m)
16 {
17 minutes += m;
18 hours += minutes / 60;
19 minutes %= 60;
20 }
21
22 void Time::addHr(int h)
23 {
24 hours += h;
25 }
26
27 void Time::reset(int h, int m)
28 {
29 hours = h;
30 minutes = m;
31 }
32
33 Time Time::operator*(double mult)const
34 {
35 Time result;
36 long totalminutes = hours * mult * 60 + minutes * mult;
37 result.hours = totalminutes / 60;
38 result.minutes = totalminutes % 60;
39 return result;
40 }
41 void Time::show() const
42 {
43 std::cout << hours << " hours, " << minutes << " minutes";
44 }
復制代碼
上述代碼建立了一個Time類並重載了這個類的*運算符,將一個Time值與一個double值結合在一起。但是,這將會產生一個問題,重載函數使得*運算符左側的操作數是調用它的對象,即,下面的語句:
A = B * 1.75;(這裡A,B均為Time類對象)
將被轉化為:A = B.operator*(1.75);
但是,下面的語句會怎麼轉化呢?
A = 1.75 * B;
從概念上說,B * 1.75應該與1.75 * B相同,但是因為1.75不是Time類的對象,所以1.75無法調用被重載的*的成員函數operator*(),所以編譯器這時候就不知道如何處理這個表達式,將會報錯。
如果要求使用Time的*運算符時,只能按照B * 1.75這種格式書寫,當然可以解決問題,但是顯然不友好。
另一種解決方式--非成員函數。假如我們定義了一個非成員函數重載*:
Time operator*(double m, const Time & t);
則編譯器既可以將 A = 1.75 * B與下面的非成員函數A = operator*(1.75, B);
但是如何實現這個函數呢?因為非成員函數不能直接訪問類的私有數據,至少常規非成員函數不可以。但是,完事皆有例外,有一類特殊的非成員函數可以訪問類的私有成員,即友元函數。
創建友元函數
將原型前面加上關鍵字friend,然後放在類聲明中:
friend Time operator*(double n, const Time & t);
該原型有如下兩點特性:雖然operator*()函數是在類中聲明的,但是它不是成員函數,因此不能用成員運算符來調用;雖然operator*()不是成員函數,但它與成員函數的訪問權限相同。
編寫函數定義
因為它不是成員函數,故不能用Time::限定符,特別注意,不要在定義中使用關鍵字friend,如下:
復制代碼
1 Time operator*(double m, const Time & t) //不要使用friend關鍵字
2 {
3 Time result;
4 long totalminutes = t.hours * m * 60 + t.minutes * m;
5 result.hours = totalminutes / 60;
6 result.minutes = totalminutes % 60;
7 return result;
8 }
復制代碼
然後,編譯器就會調用剛才定義的非成員函數將A = 1.75 * B轉換為A = operator*(1.75, B)。本人感覺就是重載了operator*()函數,當然是不是如此有待討論。
總之,記住,類的友元函數是非成員函數,但其訪問權限與成員函數相同。
三、友元類
一個類可以將其他類作為友元,這樣,友元類的所有方法都可以訪問原始類的私有成員(private)和保護成員(protected),也可以根據需要,只將特定的成員函數指定為另一個類的友元,哪些函數,成員函數或類為友元是由類自己定義的,不能外部強加,就好像你願意把誰當做是你的朋友,是你自己在心裡決定的,別人無法強制。
舉例說明,假定需要編寫一個模擬電視機和遙控器的簡單程序。定義一個Tv類和一個Remote,分別表示電視機和遙控器,遙控器可以改變電視機的狀態,應將Remote類作為Tv類的一個友元。
復制代碼
1 /*Tv and Remote classes*/
2 #ifndef TV_H_
3 #define TV_H_
4 class Tv
5 {
6 public:
7 friend class Remote; //聲明誰是自己的“好基友”(友元)
8 enum {Off, On}; //
9 enum {MinVal, MaxVal};
10 enum {Antenna, Cable};
11 enum {TV, DVD};
12
13 Tv(int s = Off, int mc =125) : state(s), volume(5),
14 maxchannel(mc), channel(2), mode(Cable), input(TV) {};
15 void onoff(){state = (state == On)? Off : On;}
16 bool ison() const {return state == On;}
17 bool volup();
18 bool voldown();
19 void chanup();
20 void chandown();
21 void set_mode() {mode = (mode == Antenna) ? Antenna : Cable;}
22 void set_input() {input = (input = TV) ? DVD : TV;}
23 void settings() const; //顯示所有設置
24 private:
25 int state; //開或者關
26 int volume; //音量
27 int maxchannel; //最多頻道數
28 int channel; //當前頻道號
29 int mode; //Antenna或者Cable模式
30 int input; //TV或者DVD輸入
31 };
32
33 class Remote
34 {
35 private:
36 int mode; //控制是TV或者DVD
37 public:
38 Remote(int m = Tv::TV) : mode(m) {}
39 bool volup(Tv & t) {return t.volup();}
40 bool voldown(Tv & t) {return t.voldown();}
41 void onoff(Tv & t) {t.onoff();}
42 void chanup(Tv & t) {t.chanup();}
43 void chandown(Tv & t) {t.chandown();}
44
45 /*此處,友元類成員函數set_chan()訪問了原始類Tv的私有成員channel
46 即使t是Tv的對象,要知道一個類是不允許對象直接訪問私有成員的,此處
47 之所以可以,就是因為Remote是Tv“好基友”(友元)的緣故*/
48 void set_chan(Tv & t, int c) {t.channel = c;}
49
50 void set_mode(Tv & t) {t.set_mode();}
51 void set_input(Tv & t) {t.set_input();}
52 };
53 #endif
復制代碼
復制代碼
1 #include <iostream>
2 #include "tv.h"
3
4 bool Tv::volup()
5 {
6 if (volume < MaxVal)
7 {
8 volume++;
9 return true;
10 }
11 else
12 return false;
13 }
14 bool Tv::voldown()
15 {
16 if (volume > MinVal)
17 {
18 volume--;
19 return true;
20 }
21 else
22 return false;
23 }
24 void Tv::chanup()
25 {
26 if (channel < maxchannel)
27 channel++;
28 else
29 channel = 1;
30 }
31 void Tv::chandown()
32 {
33 if (channel > 1)
34 channel--;
35 else
36 channel = maxchannel;
37 }
38
39 void Tv::settings() const
40 {
41 using std::cout;
42 using std::endl;
43 cout << "TV is " << (state == Off ? "Off" : "On") << endl;
44 if (state == On)
45 {
46 cout << "Volume setting = " << volume << endl;
47 cout << "Channel setting = " << channel << endl;
48 cout << "Mode = " <<
49 (mode == Antenna? "antenna" : "cable") << endl;
50 cout << "Input = "
51 << (input == TV? "TV" : "DVD") << endl;
52 }
53 }
復制代碼
復制代碼
1 /*usetv*/
2 #include <iostream>
3 #include "tv.h"
4
5 int main()
6 {
7 using std::cout;
8 Tv s42;
9 cout << "Initial settings for 42\" TV: \n";
10 s42.settings();
11 s42.onoff();
12 s42.chanup();
13 cout << "\n Adjusted settings for 42\" TV: \n";
14 s42.chanup();
15 cout << "\n Adjusted settings for 42\" TV: \n";
16 s42.settings();
17
18 Remote grey;
19
20 grey.set_chan(s42, 10);
21 grey.volup(s42);
22 grey.volup(s42);
23 cout << "\n42\" settings after using remote:\n";
24 s42.settings();
25
26 Tv s58(Tv::On);//這反應了一個遙控器可以控制多台電視
27 s58.set_mode();
28 grey.set_chan(s58, 28);
29 cout << "\n58\' settings:\n";
30 s58.settings();
31 return 0;
32 }
復制代碼
運行結果:
四、友元成員函數
對於上面的例子,大多數Remote方法都是用Tv的共有接口實現的,意味著這些用Tv的共有接口實現的方法不需要作為友元,唯一直接訪問Tv成員的Remote方法是Remote::set_chan(),因此它是唯一需要作為友元的方法,所以可以僅讓特定的類成員成為另一個類的友元,而不必將整個類成為友元。
讓Remote::set_chan()成為Tv友元的方法是:
class Tv
{
friend void Remote::set_chan(Tv & t, int c);
...
};
要處理上述語句,編譯器必須知道Remote的定義,所以Remote的定義應該放在Tv定義前面,問題是Remote::set_chan(Tv & t, int c)使用了Tv類的對象,故而Tv的定義應該放在Remote定義前面,這就產生了矛盾。
為了解決這個矛盾,需要使用一種叫做前向聲明(forward declaration),就是在Remote定義之前插入如下語句:
class Tv;
即排列次序如下:
1 class Tv;//前向聲明
2 class Remote {...};
3 class Tv {...};
能否這樣:
1 class Remote ;//前向聲明
2 class Tv {...};
3 class Remote {...};
這樣做是不可以的,因為編譯器在Tv類的聲明中看到Remote的一個方法被聲明為Tv類的友元之前,應該先看到Remote類的聲明和set_chan()方法的聲明,如果像上面這樣,雖然看到了Remote類的聲明,但是看不到set_chan()方法的聲明。
還有一個問題,比如在Remote聲明中包含如下內聯函數:
void onoff(Tv & t) {t.onoff();}
也就是說,在聲明這個方法的時候,就給出了它的定義,即調用了Tv的一個方法,而Tv的聲明(不是前向聲明哦)是放在Remote聲明後面的,顯然會有問題。所以解決方法是,在Remote聲明中只包含方法的聲明,而不去定義,將實際的定義放在Tv類之後,即
void onoff(Tv & t) ;
編譯器在檢查該原型時,需要知道Tv是一個類,而前向聲明提供了這種信息,當編譯器到達真正的方法定義時,它已經讀取了Tv類的聲明,關於函數內聯,後面可以使用inline關鍵字聲明。
給出修改後的頭文件:
復制代碼
1 /*Tv and Remote classes*/
2 #ifndef TV_H_
3 #define TV_H_
4
5 class Tv;
6
7 class Remote
8 {
9 public:
10 enum State{Off, On};
11 enum {MinVal, MaxVal = 20};
12 enum {Antenna, Cable};
13 enum {TV, DVD};
14 private:
15 int mode;
16 public:
17 //只聲明,不定義
18 Remote(int m =TV) : mode(m) {}
19 bool volup(Tv & t);
20 bool voldown(Tv & t);
21 void onoff(Tv & t);
22 void chanup(Tv & t);
23 void chandown(Tv & t);
24 void set_chan(Tv & t, int c);
25 void set_mode(Tv & t);
26 void set_input(Tv & t);
27 };
28
29 class Tv
30 {
31 public:
32 friend void Remote::set_chan(Tv & t, int c); //聲明誰是自己的“好基友”(友元)
33 enum State{Off, On};
34 enum {MinVal, MaxVal = 20};
35 enum {Antenna, Cable};
36 enum {TV, DVD};
37
38 Tv(int s = Off, int mc =125) : state(s), volume(5),
39 maxchannel(mc), channel(2), mode(Cable), input(TV) {};
40 void onoff(){state = (state == On)? Off : On;}
41 bool ison() const {return state == On;}
42 bool volup();
43 bool voldown();
44 void chanup();
45 void chandown();
46 void set_mode() {mode = (mode == Antenna) ? Antenna : Cable;}
47 void set_input() {input = (input = TV) ? DVD : TV;}
48 void settings() const; //顯示所有設置
49 private:
50 int state; //開或者關
51 int volume; //音量
52 int maxchannel; //最多頻道數
53 int channel; //當前頻道號
54 int mode; //Antenna或者Cable模式
55 int input; //TV或者DVD輸入
56 };
57
58 //Remote方法定義為內聯函數
59 inline bool Remote::volup(Tv & t) {return t.volup();}
60 inline bool Remote::voldown(Tv & t) {return t.voldown();}
61 inline void Remote::onoff(Tv & t) {t.onoff();}
62 inline void Remote::chanup(Tv & t) {t.chanup();}
63 inline void Remote::chandown(Tv & t) {t.chandown();}
64 inline void Remote::set_chan(Tv & t, int c) {t.channel = c;}
65 inline void Remote::set_mode(Tv & t) {t.set_mode();}
66 inline void Remote::set_input(Tv & t) {t.set_input();}
67 #endif