c++ primer plus 第15章友,异常和其他:友元类

c++ primer plus 第15章友,异常和其他:友元类

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:友元类


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


15.1 友元

本书前面的一些示例将友元函数用于类的扩展接口中,类并非只能拥有友元函数,也可以将类作为友元。在这种情况下,友元类的所有方法都可以访问原始类的私有成员和保护成员。另外,也可以做更严格的限制,只将特定的成员函数指定为另一个类的友元。哪些函数、成员函数或类为友元是由类定义的,而不能从外部强加友情。因此,尽管友元被授予从外部访问类的私有部分的权限,但它们并不与面向对象的编程思想相悖;相反,它们提高了公有接口的灵活性。

15.1.1 友元类

什么时候希望一个类成为另一个类的友元呢?我们来看一个例子。假定需要编写一个模拟电视机和遥控器的简单程序。决定定义一个Tv类和一个 Remote类,来分别表示电视机和遥控器。很明显,这两个类之间应当存在某种关系,但是什么样的关系呢?遥控器并非电视机,反之亦然,所以公有继承的is-a关系并不适用。遥控器也非电视机的一部分,反之亦然,因此包含或私有继承和保护继承的 has-a关系也不适用。事实上,遥控器可以改变电视机的状态,这表明应将 Romote 类作为Tv类的一个友元。首先定义Tv类。可以用一组状态成员(描述电视各个方面的变量)来表示电视机。下面是一些可能的状态:

  • 开/关;
  • 频道设置;
  • 音量设置;
  • 有线电视或天线调节模式;
  • TV 调谐或 A/V 输入。
  • 调节模式指的是,在美国,对于有线接收和 UHF 广播接收,14频道和14频道以上的频道间隔是不同的。输入选择包括TV(有线TV或广播TV)和 DVD。有些电视机可能提供更多的选择,如多种 DVD/蓝光输入,但对于这个示例的目的而言,这个清单足够了。

另外,电视机还有一些不是状态变量的参数。例如,可接收频道数随电视机而异,可以包括一个记录

这个值的成员。接下来,必须给类提供一些修改这些设置的方法。当前,很多电视机都将控件藏在面板后面,但大多数电视机还是可以在不使用遥控器的情况下进行换台等工作的,通常只能逐频道换台,而不能随意选台。同样,通常还有两个按钮,分别用来增加和降低音量。
遥控器的控制能力应与电视机内置的控制功能相同,它的很多方法都可通过使用Tv方法来实现。另外,遥控器通常都提供随意选择频道的功能,即可以直接从2频道换到20频道,并不用逐次切换频道。另很多遥控器都有多种工作模式,如用作电视控制器和 DVD遥控器。外,这些考虑因素表明,定义应类似于程序清单 15.1。定义中包括一些被定义为枚举的常数。下面的语句使 Remote 成为友元类:
friend class Remote;友元声明可以位于公有、私有或保护部分,其所在的位置无关紧要。由于Remote类提到了Tv类,所以编译器必须了解 Tv 类后,才能处理 Remote 类,为此,最简单的方法是首先定义 Tv类。也可以使用前向声明(forward delaration),这将稍后介绍。

程序清单 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

在程序清单15.1中,大多数类方法都被定义为内联的。除构造函数外,所有的Romote方法都将一个Tv 对象引用作为参数,这表明遥控器必须针对特定的电视机。程序清单15.2列出了其余的定义。音量设置函数将音量成员增减一个单位,除非声音到达最大或最小。频道选择函数使用循环方式,最低的频道设置为1,它位于最高的频道设置maxchannel之后。
很多方法都使用条件运算符在两种状态之间切换:

void onoff(){state=(state ==On)? ff :On;

如果两种状态值分别为tnue(1)和false(0),则可以结合使用将在附录E讨论的按位异或和赋值运算符(^=)来简化上述代码:

void onoff()state^=1;

程序清单 15.2 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 << "Input = "
            << (input == TV? "TV" : "DVD") << endl;
    }
}

程序清单 15.3是一个简短的程序,可以测试一些特性。另外,可使用同一个遥控器控制两台不同的电视机。

//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();
    // std::cin.get();
    return 0; 
}

下面是程序清单15.1~程序清单15.3组成的程序的输出:
在这里插入图片描述

总结

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

  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值