程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++學習筆記之友元

C++學習筆記之友元

編輯:C++入門知識

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved