深入解析C++中类的多重继承

 更新时间:2015年09月22日 16:31:20   投稿:goldensun  
这篇文章主要介绍了深入解析C++中类的多重继承,包括多重继承相关的二义性问题,需要的朋友可以参考下

C++类的多继承
在前面的例子中,派生类都只有一个基类,称为单继承。除此之外,C++也支持多继承,即一个派生类可以有两个或多个基类。
多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。想快速学习C++的读者可以不必细读。
多继承的语法也很简单,将多个基类用逗号隔开即可。例如已声明了类A、类B和类C,那么可以这样来声明派生类D:

class D: public A, private B, protected C{
 //类D新增加的成员
}


D是多继承的派生类,它以共有的方式继承A类,以私有的方式继承B类,以保护的方式继承C类。D根据不同的继承方式获取A、B、C中的成员,确定各基类的成员在派生类中的访问权限。
多继承下的构造函数

多继承派生类的构造函数和单继承类基本相同,只是要包含多个基类构造函数。如:

D类构造函数名(总参数表列): A构造函数(实参表列), B类构造函数(实参表列), C类构造函数(实参表列){
 新增成员初始化语句
}


各基类的排列顺序任意。

派生类构造函数的执行顺序同样为:先调用基类的构造函数,再调用派生类构造函数。基类构造函数的调用顺序是按照声明派生类时基类出现的顺序。

下面的定义了两个基类,BaseA类和BaseB类,然后用多继承的方式派生出Sub类。

#include <iostream>
using namespace std;
//基类
class BaseA{
protected:
 int a;
 int b;
public:
 BaseA(int, int);
};
BaseA::BaseA(int a, int b): a(a), b(b){}
//基类
class BaseB{
protected:
 int c;
 int d;
public:
 BaseB(int, int);
};
BaseB::BaseB(int c, int d): c(c), d(d){}
//派生类
class Sub: public BaseA, public BaseB{
private:
 int e;
public:
 Sub(int, int, int, int, int);
 void display();
};
Sub::Sub(int a, int b, int c, int d, int e): BaseA(a, b), BaseB(c, d), e(e){}
void Sub::display(){
 cout<<"a="<<a<<endl;
 cout<<"b="<<b<<endl;
 cout<<"c="<<c<<endl;
 cout<<"d="<<d<<endl;
 cout<<"e="<<e<<endl;
}
int main(){
 (new Sub(1, 2, 3, 4, 5)) -> display();
 return 0;
}

运行结果:

a=1
b=2
c=3
d=4
e=5

从基类BaseA和BaseB继承来的成员变量,在 Sub::display() 中都可以访问。
命名冲突

当两个基类中有同名的成员时,就会产生命名冲突,这时不能直接访问该成员,需要加上类名和域解析符。

假如在基类BaseA和BaseB中都有成员函数 display(),那么下面的语句是错误的:

Sub obj;
obj.display();


由于BaseA和BaseB中都有display(),系统将无法判定到底要调用哪一个类的函数,所以报错。

应该像下面这样加上类名和域解析符:

Sub obj;
obj.BaseA::display();
obj.BaseB::display();


通过这个举例可以发现:在多重继承时,从不同的基类中会继承一些重复的数据。如果有多个基类,问题会更突出,所以在设计派生类时要细致考虑其数据成员,尽量减少数据冗余。

C++多重继承的二义性问题
多重继承可以反映现实生活中的情况,能够有效地处理一些较复杂的问题,使编写程序具有灵活性,但是多重继承也引起了一些值得注意的问题,它增加了程序的复杂度,使 程序的编写和维护变得相对困难,容易出错。其中最常见的问题就是继承的成员同名而产生的二义性(ambiguous)问题。

如果类A和类B中都有成员函数display和数据成员a,类C是类A和类B的直接派生类。分别讨论下列3种情况。

1) 两个基类有同名成员

代码如下所示:

class A
{
public:
 int a;
 void display();
};
class B
{
public:
 int a;
 void display ();
};
class C: public A, public B
{
public:
 int b;
 void show();
};

如果在main函数中定义C类对象cl,并调用数据成员a和成员函数display :

 C cl;
 cl.a=3;
 cl.display();


由于基类A和基类B都有数据成员a和成员函数display,编译系统无法判别要访问的是哪一个基类的成员,因此程序编译出错。那么,应该怎样解决这个问题呢?可以用基类名来限定:

 cl.A::a=3; //引用cl对象中的基类A的数据成员a
 cl.A::display(); //调用cl对象中的基类A的成员函数display

如果是在派生类C中通过派生类成员函数show访问基类A的display和a,可以不 必写对象名而直接写

 A::a = 3; //指当前对象
 A::display();

2) 两个基类和派生类三者都有同名成员

将上面的C类声明改为:

 class C: public A, public B
 {
  int a;
  void display();
 };


如果在main函数中定义C类对象cl,并调用数据成员a和成员函数display:

 C cl;
 cl.a = 3;
 cl.display();


此时,程序能通过编译,也可以正常运行。请问:执行时访问的是哪一个类中的成员?答案是:访问的是派生类C中的成员。规则是:基类的同名成员在派生类中被屏蔽,成为“不可见”的,或者说,派生类新增加的同名成员覆盖了基类中的同名成员。因此如果在定义派生类对象的模块中通过对象名访问同名的成员,则访问的是派生类的成员。请注意:不同的成员函数,只有在函数名和参数个数相同、类型相匹配的情况下才发生同名覆盖,如果只有函数名相同而参数不同,不会发生同名覆盖,而属于函数重载。

有些读者可能对同名覆盖感到不大好理解。为了说明问题,举个例子,例如把中国作为基类,四川则是中国的派生类,成都则是四川的派生类。基类是相对抽象的,派生类是相对具体的,基类处于外层,具有较广泛的作用域,派生类处于内层,具有局部的作用域。若“中国”类中有平均温度这一属性,四川和成都也都有平均温度这一属性,如果没有四川和成都这两个派生类,谈平均温度显然是指全国平均温度。如果在四川,谈论当地的平均温度显然是指四川的平均温度;如果在成都,谈论当地的平均温度显然是指成都的平均温度。这就是说,全国的“平均温度”在四川省被四川的“平均温度”屏蔽了,或者说,四川的“平均温度”在当地屏蔽了全国的“平均温度”。四川人最关心的是四川的温度,当然不希望用全国温度覆盖四川的平均温度。

如果在四川要查全国平均温度,一定要声明:我要查的是全国的平均温度。同样,要在派生类外访问基类A中的成员,应指明作用域A,写成以下形式:

 cl.A::a=3; //表示是派生类对象cl中的基类A中的数据成员a
 cl.A::display(); //表示是派生类对象cl中的基类A中的成员函数display

3) 类A和类B是从同一个基类派生的

代码如下所示:

class N
{
public:
 int a;
 void display(){ cout<<"A::a="<<a<<endl; }
};
class A: public N
{
public:
 int al;
};
class B: public N
{
public:
 int a2;
};
class C: public A, public B
{
public:
 int a3;
 void show(){ cout<<"a3="<<a3<<endl; }
}
int main()
{
 C cl; //定义C类对象cl
 // 其他代码
}

在类A和类B中虽然没有定义数据成员a和成员函数display,但是它们分别从类N继承了数据成员a和成员函数display,这样在类A和类B中同时存在着两个同名的数据成员a和成员函数display。它们是N类成员的拷贝。类A和类B中的数据成员a代表两个不同的存储单元,可以分别存放不同的数据。在程序中可以通过类A和类B的构造函数去调用基类N的构造函数,分别对类A和类B的数据成员a初始化。

怎样才能访问类A中从基类N继承下来的成员呢?显然不能用

 cl.a = 3; cl.display();



 cl.N::a = 3; cl. N::display();


因为这样依然无法区别是类A中从基类N继承下来的成员,还是类B中从基类N继承下来的成员。应当通过类N的直接派生类名来指出要访问的是类N的哪一个派生类中的基类成员。如

 cl.A::a=3; cl.A::display(); //要访问的是类N的派生类A中的基类成员

相关文章

  • C语言中dlopen和dlsym的使用方式详解

    C语言中dlopen和dlsym的使用方式详解

    这篇文章主要为大家详细介绍了C语言中dlopen和dlsym的使用方式,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • 基于Qt和Opencv实现二维码解析

    基于Qt和Opencv实现二维码解析

    这篇文章主要为大家详细介绍了如何利用 Qt 和 OpenCV 实现一个可从视频和图片中检测二维码的软件,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-11-11
  • 详解C++如何实现在Word文档中创建列表

    详解C++如何实现在Word文档中创建列表

    这篇文章主要为大家详细介绍了介绍如何使用C++在Word文档中创建编号列表、项目符号列表和多级列表,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-05-05
  • C语言实现简单扫雷游戏

    C语言实现简单扫雷游戏

    这篇文章主要为大家详细介绍了C语言实现简单扫雷游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-02-02
  • C语言实现手写字符串处理工具的示例代码

    C语言实现手写字符串处理工具的示例代码

    这篇文章主要为大家详细介绍了利用C语言实现手写字符串处理工具的相关资料,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以参考一下
    2022-09-09
  • C++实现二分法的一些细节(常用场景)

    C++实现二分法的一些细节(常用场景)

    二分法算法思想首先确定有根区间,将区间二等分,通过判断f(x)的符号,逐步将有根区间缩小,直至有根区间足够小,便可求出满足精度要求的近似值
    2021-07-07
  • C++的STL中accumulate函数的使用方法

    C++的STL中accumulate函数的使用方法

    这篇文章主要介绍了C++的STL中accumulate的使用方法,accumulate作用是累加求和即自定义类型数据处理,下文具体的操作方法需要的小伙伴可以参考一下
    2022-03-03
  • 详解C++中基类与派生类的转换以及虚基类

    详解C++中基类与派生类的转换以及虚基类

    这篇文章主要介绍了详解C++中基类与派生类的转换以及虚基类,是C++入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09
  • C++学习之虚函数表与多态详解

    C++学习之虚函数表与多态详解

    这篇文章主要为大家详细介绍了C++中虚函数表与多态的相关知识,文中的示例代码讲解详细,对我们学习C++有一定的帮助,感兴趣的小伙伴可以了解一下
    2023-03-03
  • 详解C++中多态的底层原理

    详解C++中多态的底层原理

    要了解C++多态的底层原理需要我们对C指针有着深入的了解,这个在打印虚表的时候就可以见功底,所以快来跟随小编一起学习一下吧
    2022-04-04

最新评论