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

news/2024/11/30 13:43:15/

文章目录

    • 友元
      • 友元类
      • 友元成员函数
      • 互为友元关系
      • 共同的友元

友元

友元类

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

假定需要编写一个模拟电视机和遥控器的简单程序。决定定义一个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是一种类型。


http://www.ppmy.cn/news/535433.html

相关文章

CSS3-定位

网页常见布局方式 1 标准流 1 块级元素独占一行 → 垂直布局 2 行内元素/行内块元素一行显示多个 → 水平布局 2 浮动 可以让原本垂直布局的 块级元素变成水平布局 3 定位 1 可以让元素自由的摆放在网…

香橙派 1. 上手,配置wifi以及vnc

0. 环境 香橙派4以及电源 读卡器 32GB TF卡 1. 重新烧写固件 Orangepi4_2.1.2_ubuntu_bionic_desktop_linux4.4.179.img 用sd card formatter 格式化TF卡 安装Win32DiskImager&#xff0c;打开&#xff0c;选择IMG&#xff0c;确认U盘&#xff0c;点击写入。 2. 插上TTL 注意…

揭秘led室外显示屏该怎么选?如何选择合适的户外全彩LED显示屏

在户外广告、体育馆和公共场所等场景中&#xff0c;户外全彩LED显示屏以其绚丽的色彩和强烈的视觉效果成为焦点。然而&#xff0c;在众的选择中&#xff0c;如何选择合适的户外全彩LED显示屏成为一个关键问题。本文将为您介绍选择室外全彩LED显示屏的要点和技巧&#xff0c;助您…

用户如何选择适合的全彩色LED显示屏?

在现代社会中&#xff0c;LED显示屏广泛应用于各种场合&#xff0c;如室内和室外电视牌、舞台演出、体育场馆等。选择适合的LED显示屏对于获得良好的视觉效果至关重要。本文将介绍一些选择LED显示屏的关键要素&#xff0c;帮助您做出明智的决策。 一、分辨率和像素密度 LED显示…

出奇制胜的小间距LED显示屏

晶锐创显小间距LED显示屏的概念&#xff1a; LED 显示屏&#xff08;LED display&#xff09;&#xff1a;一种平板显示器&#xff0c;由一个个小的LED模块面板组成。 LED发光二极管&#xff08;light emitting diode缩写&#xff09;。它是一种通过控制半导体发光二极管的显示…

探索室内LED显示屏技术参数:视觉盛宴的细节之光

在当今数字化时代&#xff0c;室内LED显示屏以其卓越的视觉效果和多样化的应用&#xff0c;成为商业、娱乐和教育等领域的焦点。然而&#xff0c;了解室内LED显示屏的技术参数对于选择合适的产品至关重要。本文将带您深入了解室内LED显示屏的技术参数知识&#xff0c;为您点亮一…

【玩转单片机系列001】 08接口双色LED显示屏驱动方式探索

前些日子&#xff0c;从淘宝上购得一块08接口的双色LED显示屏&#xff08;打算做个音乐频谱显示器&#xff09;&#xff0c;捣鼓了好几天&#xff0c;终于搞清楚了其控制原理&#xff0c;在这里做个总结&#xff0c;算是备忘吧。 1.LED显示屏的扫描方式 LED显示屏的扫描方式有静…

室内全彩LED显示屏选购指南,行业揭秘

室内全彩LED显示屏作为一种引人注目的视觉媒体&#xff0c;广泛应用于商业、文化和娱乐场所。但在众多产品中选择合适的室内全彩LED显示屏可能会令人感到困惑。本文将为您提供一份详尽的选购指南&#xff0c;帮助您挑选到性能卓越、适用的室内全彩LED显示屏。 一、确定需求和用…