【C++】关于友元类与友元函数

友元

友元类

什么时候希望一个类成为另一个类的友元呢?

假定需要编写一个模拟电视机和遥控器的简单程序。决定定义一个Tv类和一个Remote类,来分别表示电视机和遥控器。很明显,这两个类之间应当存在某种关系,但是什么样的关系呢?

遥控器并非电视机,反之亦然,所以公有继承的is-a关系并不适用。遥控器也非电视机的一部分,反之亦然,因此包含或私有继承和保护继承的has-a关系也不适用。事实上,遥控器可以改变电视机的状态,这表明应将Romote类作为Tv类的一个友元。

友元类:友元类的所有方法都可以访问原始类的私有成员和保护成员,比如模拟电视机和遥控器之间的关系,需要使用友元的另一种情况是,函数需要访问两个类的私有数据,友元声明可以位于公有,私有或保护部分,其所在的位置无关紧要。

// 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.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 << "Input = "
			 << (input == TV ? "TV" : "DVD") << endl;
	}
}
//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.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; 
}

这个示例的主要目的在于表明,类友元是一种自然用语,用于表示一些关系。如果不使用某些形式的友元关系,则必须将Tv类的私有部分设置为公有的,或者创建一个笨拙的、大型类来包含电视机和遥控器。这种解决方法无法反应这样的事实,即同一个遥控器可用于多台电视机。

友元成员函数

让Remote类的set_chan()函数成为Tv类的友元的方法是,在Tv类声明中将其声明为友元:

class Tv {
    friend void Remote::set_chan(Tv& t, int c);
    ...
}

然而,要使编译器能够处理这条语句,它必须知道Remote的定义。否则,它无法知道Remote是一个类,而set_chan是这个类的方法。这意味着应将Remote的定义放到Tv的定义前面。Remote的方法提到了Tv对象,而这意味着Tv定义应当位于Remote定义之前。避开这种循环依赖的方法是,使用前向声明。为此,需要在Remote定义的前面插入下面的语句:

class Tv;// forward declaration

正确的排列:
class Tv;// 前向声明
class Remote {...};
class Tv {...};
————————————————————————
不能像下面这样排列:
class Remote;// 前向声明
class Tv {...};
class Remote {...};

由于这将调用Tv的一个方法,所以编译器此时必须已经看到了Tv类的声明,这样才能知道Tv有哪些方法,但正如看到的,该声明位于Remote声明的后面。这种问题的解决方法是,使Remote声明中只包含方法声明,并将实际的定义放在Tv类之后。这样,排列顺序将如下:

class Tv;// 前向声明
class Remote {...};// 这里只定义Tv要使用的方法的原型
class Tv {...};
>>>把Remote的实现方法放到这里!<<<
// tvfm.h -- Tv and Remote classes using a friend member
#ifndef TVFM_H_
#define TVFM_H_
 
class Tv;                       // forward declaration
 
class Remote {
public:
    enum State{Off, On};
    enum {MinVal,MaxVal = 20};
    enum {Antenna, Cable};
    enum {TV, DVD};
private:
    int mode;
public:
    Remote(int m = TV) : mode(m) {}
    bool volup(Tv & t);         // prototype only
    bool voldown(Tv & t);
    void onoff(Tv & t);
    void chanup(Tv & t);
    void chandown(Tv & t);
    void set_mode(Tv & t);
    void set_input(Tv & t);
    void set_chan(Tv & t, int c);
};
 
class Tv {
public:
    friend void Remote::set_chan(Tv & t, int c);
    enum State{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;
private:
    int state;
    int volume;
    int maxchannel;
    int channel;
    int mode;
    int input;
};
 
// Remote methods as inline functions
inline bool Remote::volup(Tv & t) { return t.volup();}
inline bool Remote::voldown(Tv & t) { return t.voldown();}
inline void Remote::onoff(Tv & t) { t.onoff(); }
inline void Remote::chanup(Tv & t) {t.chanup();}
inline void Remote::chandown(Tv & t) {t.chandown();}
inline void Remote::set_mode(Tv & t) {t.set_mode();}
inline void Remote::set_input(Tv & t) {t.set_input();}
// 以下是Tv类的唯一友元函数,可以访问Tv类对象的私有数据
inline void Remote::set_chan(Tv & t, int c) {t.channel = c;} 
#endif

与前一个程序示例不同的是,只有一个Remote方法是Tv类的友元,而在原来的版本中,所有的Remote方法都是Tv类的友元。

内联函数的链接性是内部的,这意味着函数定义必须在使用函数的文件中。在这个例子中,内联定义位于头文件中,因此在使用函数的文件中包含头文件可确保将定义放在正确的地方。也可以将定义放在实现文件中,但必须删除关键字inline,这样函数的链接性将是外部的。

互为友元关系

假设由于技术进步,出现了交互式遥控器。例如,交互式遥控器让您能够回答电视节目中的问题,如果回答错误,电视将在控制器上产生嗡嗡声。忽略电视使用这种设施安排观众进入节目的可能性,我们只看C++的编程方面。新的方案将受益于相互的友情,一些Remote方法能够像前面那样影响Tv对象,而一些Tv方法也能影响Remote对象。这可以通过让类彼此成为对方的友元来实现,即除了Remote是Tv的友元外,Tv还是Remote的友元。

需要记住的一点是,对于使用Remote对象的Tv方法,其原型可在Remote类声明之前声明,但必须在Remote类声明之后定义,以便编译器有足够的信息来编译该方法。这种方案与下面类似:

class TV
{
    friend class Remote;
    public:
    	void buzz(Remote & r);
    ...
};
class Remote
{
    friend class Tv;
    public:
    void Bool volup(Tv & t){
        t.volup();
    }
    ...
}
inline void TV::buzz(Remote & r)
{
    ...
}

由于Remote的声明位于Tv声明的后面,所以可以在类声明中定义Remote::volup( ),但Tv::buzz( )方法必须在Tv声明的外部定义,使其位于Remote声明的后面。如果不希望buzz( )是内联的,则应在一个单独的方法定义文件中定义它。

共同的友元

需要使用友元的另一种情况是,函数需要同时访问两个类的私有数据。从逻辑上看,这样的函数应是每个类的成员函数,但这是不可能的。它可以是一个类的成员,同时是另一个类的友元,但有时将函数作为两个类的友元更合理。

例如,假定有一个Probe类和一个Analyzer类,前者表示某种可编程的测量设备,后者表示某种可编程的分析设备。这两个类都有内部时钟,且希望它们能够同步,则应该包含下述代码行:

class Analyzer;// forward declaration
class Probe {
    friend void sync(Analyzer& a, const Probe& p);// sync a to p
    friend void sync(Probe& 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(Probe& 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是一种类型。

猜你喜欢

转载自blog.csdn.net/weixin_43717839/article/details/131352209
今日推荐