虚函数和多重继承

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

06

对于子类实例中的虚函数表,是下面这个样子:

07

我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。

08

下图中,我们在子类中覆盖了父类的f()函数。

09

下面是对于子类实例中的虚函数表的图:

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:


1


2


3


4


5


6


7


8


9


10


11


Derive d;


Base1 *b1 = &d;


Base2 *b2 = &d;


Base3 *b3 = &d;


b1->f();//Derive::f()


b2->f();//Derive::f()


b3->f();//Derive::f()


 


b1->g();//Base1::g()


b2->g();//Base2::g()


b3->g();//Base3::g()

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

多重继承:

1.继承有优先性 
比如:如果Worker中有一个print函数,Singer中也有一个print函数,那么如果SingingWaiter中如果没有定义print函数的话,调用print时调用的是Singer中的print,而不是Worker中的;但是Singer和Waiter如果同时有print函数,就会产生二义性。

2.

情况一,则

class Worker
{
    std::string name;
    int id;
public:
    void Set();//设置数据成员的函数
    void Show();//输出函数
    virtual ~Worker();
};
class Waiter :public Worker
{
    int ap;//Waiter独有的数据成员,appearance的缩写0.0
public:
    void Set();
    void Show()
};
class Singer :public Worker
{
    int rating;//Singer独有的数据成员
public:
    void Set();
    void Show();
};
  • 1
  •  

似乎这样子没什么问题。但是如果这时候又有一个类SingingWaiter从Waiter和Singer上派生出去,也就是

class SingingWaiter :public Singer, public Waiter//会唱歌的服务员0.0
//注意这里两个前面都要加上public,否则会默认没加的那个是private继承
{
public:
    void Set();
    void Show();
};

那么问题来了,SingingWaiter中有几个Waiter组件呢?答案是两个。因为它从Waiter类中继承了一个Worker组件,又从Singer类中继承了一个Worker组件。也许有人会问:为什么要继承两个Worker组件,这样有什么用?c++标准难道不能把这种情况定义成只需要继承一个Worker组件吗?这个待会再讲。 
其实c++提供了一种解决这种问题的方法:使用虚继承。 
虚继承使得从多个基类相同的类中派生出来的对象只继承一个基类的对象。如:

class Waiter :virtual public Worker
{
    int ap;
public:
    void Set();
    void Show()
};
class Singer :public virtual Worker//和virtual public Worker没什么区别
{
    int rating;
public:
    void Set();
    void Show();
};

这样SingingWaiter就只包含一个Worker的组件。 
现在再来看为什么c++标准不是按照你想象中的那样来定义这种多重继承,而是要用virtual方法。因为:1.有时可能确实需要基类的多个拷贝;2.将基类作为虚基要求程序完成一些额外的计算,倒不如在需要的时候使用virtual; 
但是!即使有了虚继承这一特性,这儿还是有一些小问题。假设原来SingingWaiter成员函数的实现是这样的:

void SingingWaiter::Set()
{
    Waiter::Set();
    Singer::Set();
}

而Waiter和Singer的Set函数是

void Singer::Set()
{
    Worker::Set();
    cin >> rating;
}
void Waiter::Set()
{
    Worker::Set();
    cin >> ap;
}
  •  

发现没,我们要调用两次Worker::Set()! 
因此,我们要改变SingingWaiter中的Set的策略:使SingingWaiter中的Set函数成为多个组件的集合体,也就是说,在这个集合体中,抽出Worker的Set函数,同时添加上Singer和Waiter的Get函数,Get函数中获取的是他们特有的成员,而不包含共有的成员。

再补充几点: 
1.如果使用了虚继承,就不能使用下面这种构造函数:

SingingWaiter(const Worker & wk,int r,int a):Waiter(wk,a),Singer(wk,r){}
  • 1

原因是这里通过两条路径来将wk传给Worker组件,为了避免这种冲突,c++在是虚的情况下,禁止通过中间类的方式传给基类。 
正确的方法是

SingingWaiter(const Worker & wk,int r,int a):Worker(wk),Waiter(wk),Singer(wk,r){}

此外:

如果多个类同时继承基类,成为子类后,在声明接口类类对象调用虚函数时,怎么判断调用的是哪个子类的虚函数呢?这里要注意以下几点:

  1. 接口类生命的对象只能是指针类型。
  2. 具体调用哪个子类里面父类纯虚函数的实现要取决于接口类中调用纯虚函数的对象指向哪个子类。
  3. 可以动态的指定子类

public  继承父类

private 自己特殊的功能。

 人为的进行区分,1多继承(换一种说法).

1个子类继承多个父类。多继承对父类的个数没有限制,继承方式可以是公共继承、保护继承和私有继承,

不写继承方式,默认是private继承

class Worker
{
public:
    Worker(string name)
{
m_strName = name;
cout << "Worker" << endl;
}
virtual ~Worker()
{
cout << "~Worker" << endl;
}
void work()
{
cout << m_strName << endl;
cout << "work" << endl;
}
protected:
string m_strName;
};
class ChildLabourer :public Worker,public Children
{
public:
ChildLabourer(string name, int age):Worker(name),Children(age)
{
cout << "ChildLabourer" << endl;
}

一个标准的父类,构造函数用来初始化内部参数,析构函数是虚函数,子类调用完之后自动析构,在实例化这个类的对象的时候就可以同时传入参数。析构时先析构子类,再析构父类。见 C++学习之继承篇(多继承与多重继承) - CSDN博客https://blog.csdn.net/hudfang/article/details/50556277

由输出结果可以看出,在多继承中,任何父类的指针都可以指向子类的对象,在实例化子类时,先根据继承的顺序依次调用父类的构造函数,然后再调用该子类自己的构造函数;用delete销毁该基类对象时,如果该基类的析构函数不是虚析构函数,且该基类的继承顺序在第一个,如上面的Worker类,(class ChildLabourer :public Worker,public Children)那么delete 父类的时候只会调用父类Worker的析构函数,系统不会出错,但是如果继承的时候顺序不是在第一位(class ChildLabourer :public Children,public Worker),就会报内存泄露的错误,如下图所示,如果父类的析构函数是虚析构函数,那么销毁的时候会先调用子类的析构函数再调用所有父类的析构函数,注意,此时,子类的父类的析构函数都会被调用!!!

2.多重继承

多重继承特点总结如下:

(1)多重继承与多继承不同,当B类从A类派生,C类从B类派生,此时称为多重继承

(1)当实例化子类时,会首先依次调用所有基类的构造函数,最后调用该子类的构造函数;销毁该子类时,则相反,先调用该子类的析构函数,再依次调用所有基类的析构函数。

(2)无论继承的层级有多少层,只要它们保持着直接或间接的继承关系,那么子类都可以与其直接父类或间接父类构成 is a的关系,并且能够通过父类的指针对直接子类或间接子类进行相应的操作,子类对象可以给直接父类或间接父类的对象或引用赋值或初始化。

此外

虚继承用来保证钻石型继承时候不会发生重复继承,即重复数据成员。这时候就需要使用virtual避免

猜你喜欢

转载自blog.csdn.net/baidu_39486224/article/details/81348982