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

2025-05-29 0 98

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

?

1

2

3
class D: public A, private B, protected C{

//类D新增加的成员

}


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

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

?

1

2

3
D类构造函数名(总参数表列): A构造函数(实参表列), B类构造函数(实参表列), C类构造函数(实参表列){

新增成员初始化语句

}


各基的排列顺序任意。

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

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40
#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;

}

运行结果:

?

1

2

3

4

5
a=1

b=2

c=3

d=4

e=5

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

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

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

?

1

2
Sub obj;

obj.display();


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

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

?

1

2

3
Sub obj;

obj.BaseA::display();

obj.BaseB::display();


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

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

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

1) 两个基有同名成员

代码如下所示:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18
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 :

?

1

2

3
C cl;

cl.a=3;

cl.display();


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

?

1

2
cl.A::a=3; //引用cl对象中的基类A的数据成员a

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

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

?

1

2
A::a = 3; //指当前对象

A::display();

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

将上面的C声明改为:

?

1

2

3

4

5
class C: public A, public B

{

int a;

void display();

};


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

?

1

2

3
C cl;

cl.a = 3;

cl.display();


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

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

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

?

1

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

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

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

代码如下所示:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27
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继承下来的成员呢?显然不能用

?

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


?

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


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

?

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

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 深入解析C++中类的多重继承 https://www.kuaiidc.com/107020.html

相关文章

发表评论
暂无评论