`
mmdev
  • 浏览: 12890942 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

C++学习-多继承和虚基类(11)

 
阅读更多
作者:gzshun. 原创作品,转载请标明出处!
来源:http://blog.csdn.net/gzshun


本文主要是讨论类的多继承,多继承的方式可使派生类具有多个基类的特性。以下是一个继承关系,C从A和B基类中派生出来,在派生类C中,包含了基类A与基类B的成员,还有C类自己的成员。

在上述关系图中,可以看到,类继承的结构很清晰,但在很多时候,有可能有以下继承关系:


在这幅类的继承关系图中可以看到,B1和B2分别继承于A,再由B1和B2派生出C,这关系看起来很简单,但在内部,有点麻烦?
产生二义性。
A派生出B1和B2,所以B1和B2中分别包含A的成员,再由B1和B2派生出C,此时C包含了B1和B2类的成员,所以C中总共有2个A成员。这时候麻烦了,如何要对C类中B1父类中的A类成员修改,或者是对C类中的B2父类中的A类成员修改呢?这有点绕口,C++提供了一种方法是通过作用域来指定,可以参考前一篇博文《C++学习-继承中的作用域(10)

这里给一个简单的例子:

#include <iostream>

using namespace std;

class A
{
public:
    A(int a = 0) : mA(a) {}
    int mA;
};

class B1 : public A
{
public:
    B1(int a = 0, int b = 0) : A(b), mB1(a) {}
    int mB1;
};

class B2 : public A
{
public:
    B2(int a = 0, int b = 0) : A(b), mB2(a) {}
    int mB2;
};

class C : public B1, public B2
{
public:
    C(int x, int a, int b, int c, int d) : B1(a, b), B2(c, d), mC(x) {}
    void Print() const
    {
//        cout << "mA = " << mA << endl; //编译出错
        cout << "B1::mA = " << B1::mA << endl;
        cout << "B2::mA = " << B2::mA << endl;
        cout << "mB1 = " << mB1 << endl;
        cout << "mB2 = " << mB2 << endl;
        cout << "mC = " << mC << endl;
    }
    int mC;
};

int main()
{
    C obj(10, 20, 30, 40, 50);
    obj.Print();
    return 0;
}

执行结果:
B1::mA = 30
B2::mA = 50
mB1 = 20
mB2 = 40
mC = 10

从这个例子可以看到,使用作用域运算符"::"来消除二义性,这样就能巧妙地避免那个棘手的问题。不过在类C中,包含了2个A的部分,有点浪费空间。当然C++是非常伟大的,还有另一种更好的办法,就是使用虚基类,也可以称为虚继承。
虚基类的作用:使用虚基类能够在多重派生的过程中,使公有的基类在派生类中只有一个拷贝,这样就能解决上述二义性的错误。如果使用虚基类,那么以上类C当中也就只有1个A的部分。在虚基类的使用过程中,还有一个非常重要的特性,看例子便可发现
看例子:

#include <iostream>

using namespace std;

class A
{
public:
    A(int a = 0) : mA(a) {}
    int mA;
};

class B1 : virtual public A
{
public:
    B1(int a = 0, int b = 0) : A(b), mB1(a) {}
    int mB1;
};

class B2 : virtual public A
{
public:
    B2(int a = 0, int b = 0) : A(b), mB2(a) {}
    int mB2;
};

class C : public B1, public B2
{
public:
    C(int x, int a, int b, int c, int d) : B1(a, b), B2(c, d), mC(x) {}
    void Print() const
    {
        cout << "B1::mA = " << B1::mA << endl;
        cout << "B2::mA = " << B2::mA << endl;
        cout << "mA = " << mA << endl;
        cout << "mB1 = " << mB1 << endl;
        cout << "mB2 = " << mB2 << endl;
        cout << "mC = " << mC << endl;
    }
    int mC;
};

int main()
{
    C obj(10, 20, 30, 40, 50);
    obj.Print();
    cout << endl;
    obj.mA = 163;
    obj.Print();
    return 0;
}

执行结果:
B1::mA = 0
B2::mA = 0
mA = 0
mB1 = 20
mB2 = 40
mC = 10

B1::mA = 163
B2::mA = 163
mA = 163
mB1 = 20
mB2 = 40
mC = 10

对比一下这两个例子,差别很小,将作用域的调用方式给去掉了。在这个例子当中,使用了virtual方式继承了基类A,所以在B1和B2中,对类A只有一个拷贝,若使用virtual方式继承,不管派生多少派生类,类A永远只有一个拷贝。对mA进行修改,从B1或B2调用mA,都是调用同一个副本,从结果可以看出这一结论。
重要:
虚基类的构造函数的调用方法与一般基类的构造函数的调用方法是不同的
。在这个例子中,编译器没有调用B1或者B2的构造函数来调用基类A的构造函数,因为在虚继承过程中,基类A只有一个拷贝,所以编译器无法确定应该由类B1或者类B2的构造函数来调用基类A的构造函数,所以此时调用的是基类A的默认构造函数,所以刚开始mA的结果为0,是基类A的默认构造函数设置的默认值。

C++规定,由虚基类经过一次或者多次派生出来的派生类,在其每一个派生类的构造函数的成员初始化列表中必须给出对虚基类的构造函数的调用,如果未列出,则调用虚基类的默认构造函数。
在本例当中,在执行B1和B2的构造函数时都不调用虚基类A的构造函数,而是在类C中的构造函数直接调用虚基类A的默认构造函数。(因为编译器无法确定)
再看一个与上面几乎是一样的例子,只有一个区别,就是在类C的初始化列表中显示的调用虚基类A的构造函数,如下:

#include <iostream>

using namespace std;

class A
{
public:
    A(int a = 0) : mA(a) {}
    int mA;
};

class B1 : virtual public A
{
public:
    B1(int a = 0, int b = 0) : A(b), mB1(a) {}
    int mB1;
};

class B2 : virtual public A
{
public:
    B2(int a = 0, int b = 0) : A(b), mB2(a) {}
    int mB2;
};

class C : public B1, public B2
{
public:
    C(int x, int a, int b, int c, int d) : A(a), B1(a, b), B2(c, d), mC(x) {}
    void Print() const
    {
        cout << "B1::mA = " << B1::mA << endl;
        cout << "B2::mA = " << B2::mA << endl;
        cout << "mA = " << mA << endl;
        cout << "mB1 = " << mB1 << endl;
        cout << "mB2 = " << mB2 << endl;
        cout << "mC = " << mC << endl;
    }
    int mC;
};

int main()
{
    C obj(10, 20, 30, 40, 50);
    obj.Print();
    cout << endl;
    obj.mA = 163;
    obj.Print();
    return 0;
}

执行结果:
B1::mA = 20
B2::mA = 20
mA = 20
mB1 = 20
mB2 = 40
mC = 10

B1::mA = 163
B2::mA = 163
mA = 163
mB1 = 20
mB2 = 40
mC = 10

唯一的区别就是这行代码:
C(int x, int a, int b, int c, int d) : A(a), B1(a, b), B2(c, d), mC(x) {}
在这里显示地调用虚基类A的构造函数,并传入初始值,所以第一次打印mA的值不是0,而是20。
虚基类有这些特性,务必记住,看例子便可知分晓,OVER!

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics