15.1.1 友元類
假定需要編寫一個模擬電視機和遙控器的簡單程式。決定定義一個Tv類和一個Remote類,來分別表示電視機和遙控器。遙控器和電視機之間既不是is-a關係也不是has-a關係。事實上,遙控器可以改變電視機的狀態,這表明應將Remote類作為Tv類的一個友元。
首先定義Tv類:
友元宣告可以位於共有、私有或保護部分,其所在的位置無關緊要。由於Remote類提到了Tv類,所以編譯器必須瞭解Tv類後,才能處理Remote類,為此,最簡單的方法是首先定義Tv類。也可以使用前向宣告(forward declaration),這將稍候介紹。
程式清單15.1 tv.h
// tv.h -- Tv and Remote classes #ifndef TV_H_ #define TV_H_ class Tv { public: friend class Remote; // Remote can access Tv private parts enum {Off, On}; enum {MinVal, MaxVal = 20}; enum {Antenna, Cable}; enum {TV, DVD}; Tv(int s = Off, int mc = 125) : state(s), volume(5), maxchannel(mc), channel(2), mode(Cable), input(TV) {} void onoff() { state = (state == On) ? Off : On; } bool ison() const { return state == On; } bool volup(); bool voldown(); void chanup(); void chandown(); void set_mode() { mode = (mode == Antenna) ? Cable : Antenna; } void set_input() { input = (input == TV) ? DVD : TV; } void settings() const; // display all settings private: int state; // on or off int volume; // assumed to be digitized int maxchannel; // maximum number of channels int channel; // current channel setting int mode; // broadcast or cable int input; // TV or DVD }; class Remote { private: int mode; // controls TV or DVD public: Remote(int m = Tv::TV) : mode(m) {} bool volup(Tv & t) { return t.volup(); } bool voldown(Tv & t) { return t.voldown(); } void onoff(Tv & t) { t.onoff(); } void chanup(Tv & t) { t.chanup(); } void chandown(Tv & t) { t.chandown(); } void set_chan(Tv & t, int c) { t.channel = c; } void set_mode(Tv & t) { t.set_mode(); } void set_input(Tv & t) { t.set_input(); } }; #endif // TV_H_
程式清單15.3 tv.cpp
// tv.cpp -- methods for the Tv class (Remote methods are inline) #include <iostream> #include "tv.h" bool Tv::volup() { if (volume < MaxVal) { volume ++; return true; } else return false; } bool Tv::voldown() { if (volume > MinVal) { volume --; return true; } else return false; } void Tv::chanup() { if (channel < maxchannel) channel ++; else channel = 1; } void Tv::chandown() { if (channel > 1) channel --; else channel = maxchannel; } void Tv::settings() const { using std::cout; using std::endl; cout << "TV is " << (state == Off ? "Off" : "On") << endl; if (state == On) { cout << "Volume setting = " << volume << endl; cout << "Channel setting = " << channel << endl; cout << "Mode = " << (mode == Antenna ? "antenna" : "cable") << endl; cout << "Inpute = " << (input == TV ? "TV" : "DVD") << endl; } }
程式清單15.3是一個簡短的程式,可以測試一些特性。靈位,可使用同一個遙控器控制兩臺不同的電視機。
程式清單15.3 use_tv.cpp
// use_tv.cpp -- using the Tv and Remote classes #include <iostream> #include "tv.h" int main() { using std::cout; Tv s42; cout << "Initial settings for 42\" TV\n"; s42.settings(); s42.onoff(); s42.chanup(); cout << "\nAdjusted settings for 42\" TV:\n"; s42.chanup(); cout << "\nAdjusted settings for 42\" TV:\n"; s42.settings(); Remote grey; grey.set_chan(s42, 10); grey.volup(s42); grey.volup(s42); cout << "\n42\" settings after using remote:\n"; s42.settings(); Tv s58(Tv::On); s58.set_mode(); grey.set_chan(s58, 28); cout << "\n58\" settings:\n"; s58.settings(); return 0; }
程式輸出:
Initial settings for 42" TV TV is Off Adjusted settings for 42" TV: Adjusted settings for 42" TV: TV is On Volume setting = 5 Channel setting = 4 Mode = cable Inpute = TV 42" settings after using remote: TV is On Volume setting = 7 Channel setting = 10 Mode = cable Inpute = TV 58" settings: TV is On Volume setting = 5 Channel setting = 28 Mode = antenna Inpute = TV
15.1.2 友元成員函式
上一個例子中,唯一直接訪問Tv成員的Remote方法是Remote::set_chan(),因此它是唯一需要作為友元的方法。
讓Remoete::set_chan()成為Tv類的友元的方法是,在Tv類宣告中將其宣告為友元:
class Tv
{
friend void Remote::set_chan(Tv & t, int c);
...
};
避開“我一來你,你依賴我”的情況出現的方法——使用前向宣告(forward declaration)。為此,需要在Remote定義的前面插入下面的語句:
class Tv; // forward declaration
這樣,排列次序如下:
class Tv; // forward declaration
class Remote { ... };
class Tv { ... };
能否像下面這樣排列呢?
class Remote;
class Tv { ... };
class Remote { ... };
答案是不能。原因在於,在編譯器在Tv類的宣告中看到Remote的一個方法被宣告為Tv類的友元之前,應該先看到Remote類的宣告和set_chan()方法的宣告。
……
使Remote宣告中只包含方法宣告,並將實際的定義放在Tv類之後。這樣,排列順序將如下:
class Tv; // forward declaration
class Remote { ... }; // Tv-using methods as prototypes only
class Tv { ... };
// put Remote method definitions here
15.1.4 共同的友元
需要使用友元的另一種情況是,函式需要訪問兩個類的私有資料。從邏輯上看,這樣的函式應是每個類的成員函式,單這時不可能的。它可以使一個類的成員,同時是另一個類的友元,單有時將函式作為兩個類的友元更合理。例如,假定有一個Probe類和一個Analyzer類,前者表示某種可程式設計的測量裝置,後者表示某種可程式設計的分析裝置。這來那個哥類都有內部時鐘,且希望它們能夠同步,則應該包含下述程式碼行:
class Analyzer; // forward declaration
class Probe
{
friend void sync(Analyzer & a, const Probe & p); // sync a to p
friend void sync(Prob & p, const Analyzer & a); // sync p to a
...
};
class Analyzer
{
friend void sync(Analyzer & a, const Probe & p); // sync a to p
friend void sync(Prob & p, const Analyzer & a); // sync p to a
...
};
// define the friend functions
inline void sync(Analyzer & a, const Probe & p)
{
...
}
inline void sync(Probe & p, const Analyzer & a)
{
...
}
前向宣告使編譯器看到Probe類宣告中的友元宣告時,知道Analyzer是一種型別。