文章目录
- 友元
- 友元类
- 友元成员函数
- 互为友元关系
- 共同的友元
友元
友元类
什么时候希望一个类成为另一个类的友元呢?
假定需要编写一个模拟电视机和遥控器的简单程序。决定定义一个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 partsenum {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 offint volume; // assumed to be digitizedint maxchannel; // maximum number of channelsint channel; // current channel settingint mode; // broadcast or cableint 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;} elsereturn false;
}
bool Tv::voldown() {if (volume > MinVal) {volume--;return true;} elsereturn false;
}
void Tv::chanup() {if (channel < maxchannel)channel++;elsechannel = 1;
}
void Tv::chandown() {if (channel > 1)channel--;elsechannel = 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 declarationclass 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 onlybool 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 pfriend void sync(Probe& p, const Analyzer& a);// sync p to a...
};
class Analyzer {friend void sync(Analyzer& a, const Probe& p);// sync a to pfriend 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是一种类型。