多重继承

多重继承是C ++的一个特性,其中类可以从多个类继承。C++中,所有的继承都是实现继承,只继承接口是不可能的。就是使用private继承和protected继承,派生类还是包含了所有基类的数据成员,只有对基类的私有成员有访问限制而已。
C++模拟接口继承的方式是,通过从一个“接口类”派生。接口类是一个不包含数据和函数体的类,意味着它除了析构函数以外,都是纯虚函数(纯虚析构函数也需要函数体,即使不包含代码)。

一般来说使用模板可以解决多重继承的问题。

实现多重继承

继承类的构造函数按照它们继承的顺序进行调用。例如,在以下程序中,B的构造函数在A的构造函数之前调用。

#include<iostream>
using namespace std;

class A
{
public:
  A()  { cout << "A's constructor called" << endl; }
};

class B
{
public:
  B()  { cout << "B's constructor called" << endl; }
};

class C: public B, public A  // Note the order
{
public:
  C()  { cout << "C's constructor called" << endl; }
};

int main()
{
    C c;
    return 0;
}
Output:
B's constructor called
A's constructor called
C's constructor called

析构函数以构造函数相反的顺序调用。

菱形继承问题

当类的两个超类具有公共基类时,会出现菱形继承问题。例如,在下图中,TA类获取人类所有属性的两个副本,在向上类型转换时,有两个子对象选择,导致二义性。对于只在祖父类定义的函数,调用同样会产生二义性。
菱形继承

#include<iostream>
using namespace std;
class Person {
   // Data members of person
public:
    Person(int x)  { cout << "Person::Person(int ) called" << endl;   }
};

class Faculty : public Person {
   // data members of Faculty
public:
    Faculty(int x):Person(x)   {
       cout<<"Faculty::Faculty(int ) called"<< endl;
    }
};

class Student : public Person {
   // data members of Student
public:
    Student(int x):Person(x) {
        cout<<"Student::Student(int ) called"<< endl;
    }
};

class TA : public Faculty, public Student  {
public:
    TA(int x):Student(x), Faculty(x)   {
        cout<<"TA::TA(int ) called"<< endl;
    }
};

int main()  {
    TA ta1(30);
}
Output:
Person::Person(int ) called
Faculty::Faculty(int ) called
Person::Person(int ) called
Student::Student(int ) called
TA::TA(int ) called

在上述程序中,“person”的构造函数被调用为两次。 “person”的析构函数也会在对象“ta1”被析构时被调用两次。所以“ta1”对象有”Person”的所有成员的两个副本,这就造成了歧义,编译器会报错。

引入“virtual”关键字消除重复子对象

要想解决重复父类副本的问题,需要实现真正的菱形继承,也就是说,子对象要共享共同父类对象。具体实现,可以使用virtual public继承。
我们将班级的“Faculty”和“Student”作为虚基类,以避免“TA”类中有的“Person”两个副本。例如,考虑以下程序。

#include<iostream>
using namespace std;
class Person {
public:
    Person(int x)  { cout << "Person::Person(int ) called" << endl;   }
    Person()     { cout << "Person::Person() called" << endl;   }
};

class Faculty : virtual public Person {
public:
    Faculty(int x):Person(x)   {
       cout<<"Faculty::Faculty(int ) called"<< endl;
    }
};

class Student : virtual public Person {
public:
    Student(int x):Person(x) {
        cout<<"Student::Student(int ) called"<< endl;
    }
};

class TA : public Faculty, public Student  {
public:
    TA(int x):Student(x), Faculty(x)   {
        cout<<"TA::TA(int ) called"<< endl;
    }
};

int main()  {
    TA ta1(30);
}
Output:
Person::Person() called
Faculty::Faculty(int ) called
Student::Student(int ) called
TA::TA(int ) called

在上述程序中,“Person”的构造函数被调用一次。在上面的输出中要注意的一个重要事情是,调用“Person”的默认构造函数。当我们使用’virtual’关键字时,默认情况下调用祖父类的默认构造函数,即使父类显式调用带参数构造函数

那如何调用“Person”类的带参数构造函数?
构造函数必须在“TA”类中调用。例如,

#include<iostream>
using namespace std;
class Person {
public:
    Person(int x)  { cout << "Person::Person(int ) called" << endl;   }
    Person()     { cout << "Person::Person() called" << endl;   }
};

class Faculty : virtual public Person {
public:
    Faculty(int x):Person(x)   {
       cout<<"Faculty::Faculty(int ) called"<< endl;
    }
};

class Student : virtual public Person {
public:
    Student(int x):Person(x) {
        cout<<"Student::Student(int ) called"<< endl;
    }
};

class TA : public Faculty, public Student  {
public:
    TA(int x):Student(x), Faculty(x), Person(x)   {
        cout<<"TA::TA(int ) called"<< endl;
    }
};

int main()  {
    TA ta1(30);
}
Output:
Person::Person(int ) called
Faculty::Faculty(int ) called
Student::Student(int ) called
TA::TA(int ) called

一般来说,不允许直接调用祖父的构造函数,必须通过父类调用。仅当使用’virtual’关键字时才允许。

#include<iostream>
using namespace std;

class A
{
  int x;
public:
  void setX(int i) {x = i;}
  void print() { cout << x; }
};

class B:  public A
{
public:
  B()  { setX(10); }
};

class C:  public A 
{
public:
  C()  { setX(20); }
};

class D: public B, public C {
};

int main()
{
    D d;
    d.print();
    return 0;
}
Output: 
 error: request for member 'print' is ambiguous
  d.print();

如果class B和class C 继承时,加上virtual关键字则输出20.因为按照多个父类出现的先后顺序构造,C在B之后构造。

#include<iostream>
using namespace std;

class A
{
  int x;
public:
  A(int i) { x = i; }
  void print() { cout << x; }
};

class B: virtual public A
{
public:
  B():A(10) {  }
};

class C:  virtual public A
{
public:
  C():A(10) {  }
};

class D: public B, public C {
};

int main()
{
    D d;
    d.print();
    return 0;
}
Output: 

error: no matching function for call to 'A::A()'

如果为A添加一个默认构造函数,则编译通过。

名称查找问题

这里写图片描述
对于上图继承体系,假设Left和Right中都有一个名称为f的函数,则类Bottom继承了两个同名函数,消除二义性调用的方法是,用基类名限定函数的调用,如using Left::f。
一个派生类的作用域被认为嵌套在基类的作用域之内,这在同名函数之间的选择时,编译器选择更高派生层次内的函数。

何时使用多重继承

1、需要通过新类来显式两个类的公共接口
2、需要向上类型转换成两个基类类型。任何时候,如果将新类的一个对象作为参数传递给某个期盼嵌入对象的函数,这就需要类型转换函数。

如果不必需要向上类型转换为所有基类类型,则应该像避免使用goto语句一样避免使用多重继承。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值