《C++ Primer Plus》15.1 友元 學習筆記

weixin_34054866發表於2016-07-14

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是一種型別。

相關文章