zoukankan      html  css  js  c++  java
  • 《C++ Primer Plus》14.3 多重继承 学习笔记

    多重继承(MI)描述的是有多个直接基类的类。与单继承一样,共有MI表示的也是is-a关系。例如,可以从Awiter类和Singer类派生出SingingWaiter类:
    class SingingWaiter : public Waiter, public Singer {...};
    MI可能会给程序员带来很多新问题。其中两个主要的问题是:从两个不同的基类继承同名方法;从两个或更多相关基类那里继承同一个类的多个实例。
    在下面的例子中,我们将定义一个抽象基类Worker,并使用它派生出Waiter类和Singer类。然后,使用MI从Waiter类和Singer类派生出SingingWaiter类。
    程序清单14.7 worker0.h

    // worker0.h -- working classes
    #ifndef WORKER0_H_
    #define WORKER0_H_
    
    #include <string>
    
    class Worker    // an abstract base class
    {
    private:
        std::string fullname;
        long id;
    public:
        Worker() : fullname("no one"), id(0L) {}
        Worker(const std::string & s, long n)
                : fullname(s), id(n) {}
        virtual ~Worker() = 0;  // pure virtual destructor
        virtual void Set();
        virtual void Show() const;
    };
    
    class Waiter : public Worker
    {
    private:
        int panache;
    public:
        Waiter() : Worker(), panache(0) {}
        Waiter(const std::string & s, long n, int p = 0)
                : Worker(s, n), panache(p) {}
        Waiter(const Worker & wk, int p = 0)
                : Worker(wk), panache(p) {}
        void Set();
        void Show() const;
    };
    
    class Singer : public Worker
    {
    protected:
        enum {other, alto, contralto, soprano,
                        bass, baritone, tenor};
        enum {VTypes = 7};
    private:
        static char *pv[VTypes];    // string equivs of voice types
        int voice;
    public:
        Singer() : Worker(), voice(other) {}
        Singer(const std::string & s, long n, int v = other)
                : Worker(s, n), voice(v) {}
        Singer(const Worker & wk, int v = other)
                : Worker(wk), voice(v) {}
        void Set();
        void Show() const;
    };
    
    #endif // WORKER0_H_

    程序清单14.7的类声明中包含一些表示声音类型的内部变量。一个枚举类型符号常量alto、contralto等表示声音类型,静态数组pv存储了指向相应C-风格字符串的指针,程序清单14.8初始化了该数组,并提供了方法的定义。
    程序清单14.8 worker0.cpp

    // worker0.cpp -- working class methods
    #include "worker0.h"
    #include <iostream>
    using std::cout;
    using std::cin;
    using std::endl;
    // Worker methods
    
    // must implement virtual destructor, even if pure
    Worker::~Worker() {}
    
    void Worker::Set()
    {
        cout << "Enter worker's name: ";
        getline(cin, fullname);
        cout << "Enter worker's ID: ";
        cin >> id;
        while(cin.get() != '
    ')
            continue;
    }
    
    void Worker::Show() const
    {
        cout << "Name: " << fullname << "
    ";
        cout << "Employee ID: " << id << "
    ";
    }
    
    // Waiter methods
    void Waiter::Set()
    {
        Worker::Set();
        cout << "Enter waiter's panache rating: ";
        cin >> panache;
        while (cin.get() != '
    ')
            continue;
    }
    
    void Waiter::Show() const
    {
        cout << "Category: waiter
    ";
        Worker::Show();
        cout << "Panache rating: " << panache << "
    ";
    }
    
    // Singer methods
    
    char * Singer::pv[] = {"other", "alto", "contralto",
                    "soprano", "bass", "baritone", "tenor"};
    
    void Singer::Set()
    {
        Worker::Set();
        cout << "Enter number for singer's vocal range:
    ";
        int i;
        for (i = 0; i < VTypes; i ++)
        {
            cout << i << ": " << pv[i] << "    ";
            if (i % 4 == 3)
                cout << endl;
        }
        if (i % 4 != 0)
            cout << endl;
        while (cin >> voice && (voice < 0 || voice >= VTypes) )
            cout << "Please enter a value >= 0 and < " << VTypes << endl;
    
        while (cin.get() != '
    ')
            continue;
    }
    
    void Singer::Show() const
    {
        cout << "Category: singer
    ";
        Worker::Show();
        cout << "Vocal range: " << pv[voice] << endl;
    }

    程序清单14.9是一个简短的程序,它使用一个多台指针数组对这些类进行了测试。
    程序清单14.9 worktest.cpp

    // worktest.cpp -- test worker class hierarchy
    #include <iostream>
    #include "worker0.h"
    const int LIM = 4;
    int main()
    {
        Waiter bob("Bob Apple", 314L, 5);
        Singer bev("Beverly Hills", 522L, 3);
        Waiter w_temp;
        Singer s_temp;
    
        Worker * pw[LIM] = {&bob, &bev, &w_temp, &s_temp};
    
        int i;
        for (i = 2; i < LIM; i ++)
            pw[i]->Set();
        for (i = 0; i < LIM; i ++)
        {
            pw[i]->Show();
            std::cout << std::endl;
        }
    
        return 0;
    }

    效果:

    Enter worker's name: Waldo Dropmaster
    Enter worker's ID: 442
    Enter waiter's panache rating: 3
    Enter worker's name: Sylvis Sirenne
    Enter worker's ID: 555
    Enter number for singer's vocal range:
    0: other    1: alto    2: contralto    3: soprano    
    4: bass    5: baritone    6: tenor    
    3
    Category: waiter
    Name: Bob Apple
    Employee ID: 314
    Panache rating: 5
    
    Category: singer
    Name: Beverly Hills
    Employee ID: 522
    Vocal range: soprano
    
    Category: waiter
    Name: Waldo Dropmaster
    Employee ID: 442
    Panache rating: 3
    
    Category: singer
    Name: Sylvis Sirenne
    Employee ID: 555
    Vocal range: soprano

    这种设计看起来是可行的:使用Waiter指针来调用Waiter::Show()和Waiter::Set();使用Singer指针来调用Singer::Show()和Singer::Set()。然后,如果添加一个从Singer和Waiter类派生出的SingingWaiter类后,将带来一些问题。具体地说,将出现以下问题。
    * 有多少Worker?
    * 哪个方法?
    14.3.1 有多少Worker
    假设首先从Singer和Waiter共有派生出SingingWaiter:
    class SingingWaiter: public Singer, public Waiter {...};
    因为Singer和Waiter都继承了一个Worker组件,因此SingingWaiter将包含两个Worker组件,这将引起问题。例如,通常可以将派生类对象的地址赋给基类指针,单现在将出现二义性:
    SingingWaiter ed;
    Worker * pw = &ed;    // ambiguous
    通常,这种赋值把基类指针设置为派生类对象中的基类对象的地址。但ed中包含两个Worker对象,有两个地址可供选择,所以应使用类型转换来指定对象:
    Worker * pw1 = (Waiter *) &ed;    // the Worker in Waiter
    Worker * pw2 = (Singer *) &ed;    // the Worker in Singer
    C++引入多重继承的同时引入了虚基类(virtual base class),使MI成为可能。
    1.虚基类
    虚基类使得从多个类(它们的基类相同)派生出的对象值继承一个基类对象。例如,通过在类声明中使用关键字virtual,可以使Worker被用作Singer和Waiter的虚基类(virtual和public的次序无关紧要):
    class Singer : virtual public Worker {...};
    class Waiter : public virtual Worker {...};
    然后,可以将SingingWaiter类定义为:
    class SIngingWaiter: public Singer, public Waiter {...};
    现在,SingerWaiter对象将只包含Worker对象的一个副本。从本质上说,继承的Singer和Waiter对象共享一个Worker对象,而不是各自引入自己的Worker对象副本。因为SingingWaiter现在值包含了一个Worker子对像,所以可以使用多态。
    2.新的构造函数规则
    使用虚基类时,需要对类构造函数采用一种新的方法。对于非虚基类,唯一可以出现在初始化列表中的构造函数是即使基类构造函数。但这些构造函数可能需要将信息传递给其基类。例如,可能有下面一组构造函数:
    class A
    {
        int a;
    public:
        A(int n = 0) : a(n) {}
    };
    class B: public A
    {
        int b;
    public:
        B(int m = 0, int n = 0) : A(n), b(m) {}
    };
    class C : public B
    {
        int c;
    public:
        C(int q = 0, int m = 0, int n = 0) : B(m, n), c(q) {}
        ...
    };
    C类的构造函数只能调用B类的构造函数,而B类的构造函数只能调用A类的构造函数。这里,C类的构造函数使用值q,并将值m和n传递给B类的构造函数;而B类的构造函数使用值m,并将值n传递给A类的构造函数。
    如果Worker是虚基类,则这种信息自动传递将不起作用。例如,对于下面的MI构造函数:
    SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other)
            : Waiter(wk, p), Singer(wk, v) {} // flawed
    存在的问题是,自动传递信息时,将通过2条不同的途径(Waiter和Singer)将wk传递给Worker对象。为避免这种冲突,C++在基类是虚的时,禁止信息通过中间类自动传递给基类。因此,上述构造函数将初始化成员panache和voice,但wk参数中的信息将不会传递给子对像Waiter。然而,编译器必须在构造派生对象之前构造基类对象组件:在上述情况下,编译器将使用Worker的默认构造函数。
    如果不希望默认构造函数来构造虚基类对象,则需要显式地调用所需的基类构造函数。因此,构造函数应该是这样:
    SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other)
            : Worker(wk), Waiter(wk, p), Singer(wk,v) {}
    上述代码将显式地调用构造函数worker(const Worker &)。请注意,这种用法是合法的,对于虚基类,必须这样做;但对于非虚基类,则是非法的。
    注:如果类有间接虚基类,则除非只需使用该虚基类的默认构造函数,否则必须显式地调用该虚基类的某个构造函数。
    14.3.2 哪个方法
    除了修改类构造函数规则外,MI通常还要求调整其他代码。假设要在SingingWaiter类中扩展Show()方法。因为SingingWaiter对象没有新的数据称源,所以可能会认为它只需使用继承的方法即可。这引出了第一个问题。假设没有在SingingWaiter类中重新定义Show()方法,并试图使用SingingWaiter对象调用继承的Show()方法:
    SingingWaiter newhire("Elise Hawks", 2005, 6, soprano);
    newhire.Show();    // ambiguous
    对于单继承,如果没有重新定义Show(),则将使用最近祖先中的定义。而在多重继承中,每个直接祖先都有一个Show()函数,这使得上述调用是二义性的。
    (1)可以使用作用域解析运算符来澄清编程者的意图:
    SingingWaiter newhire("Elise Hawks", 2005, 6, soprano);
    newhire.Singer::Show();    // use Singer version
    (2)更好的方法是在SingerWaiter中重新定义Show(),并指出要使用哪个Show()。例如,如果希望SingingWaiter对象使用Singer版本的Show(),则可以这样做:
    void SingingWaiter::Show()
    {
        Singer::Show();
    }
    (3)另一种办法是将所有的数据组件都设置为保护的,而不是私有的,不过使用保护方法(而不是保护数据)将可以更严格地空置对数据的访问。

    其他一些有关MI的问题:
    1.混合使用虚基类和非虚基类
    ……
    2.虚基类和支配
    ……

    14.3.3 MI小结
    使用虚基类的MI:当派生类使用关键字irtual来指示派生时,基类就称为虚基类:
    class marketing : public virtual reality { ... };
    主要变化(同时也是使用虚基类的原因)是:从虚基类的一个或多个实例派生而来的类将只继承了一个基类对象。为实现这种特性,必须满足其他要求:
    * 有简介虚基类的派生类包含直接调用简介基类构造函数的构造函数,这对于简介非虚基类来说是非法的;
    * 通过有限规则解决名称二义性。

    在必要时对继承的名称进行限定。

  • 相关阅读:
    【HDU1724】Ellipse-自适应Simpson积分法
    【HDU1724】Ellipse-自适应Simpson积分法
    【51Nod1227】平均最小公倍数-杜教筛
    【51Nod1227】平均最小公倍数-杜教筛
    【HDU5628】Clarke and math-狄利克雷卷积+快速幂
    【HDU5628】Clarke and math-狄利克雷卷积+快速幂
    deleted
    deleted
    deleted
    deleted
  • 原文地址:https://www.cnblogs.com/moonlightpoet/p/5668870.html
Copyright © 2011-2022 走看看