欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深度剖析c++单继承与多继承

程序员文章站 2022-12-10 22:53:52
c++具有四大特性,封装,继承,多态,其中继承是很重要的一个特性,它的出现,保持了c++中类的复用性,因此继承的使用也很普遍。常见的继承分为单继承和多继承,而从这两个分支又可以扩展...

c++具有四大特性,封装,继承,多态,其中继承是很重要的一个特性,它的出现,保持了c++中类的复用性,因此继承的使用也很普遍。常见的继承分为单继承和多继承,而从这两个分支又可以扩展出很多种情况。所以,我把继承中最常见也最实用的几种方式总结在下面,希望可以对你们产生帮助。

前言
继承的关系是由人类的血缘关系衍生出来的一个名词,因此,继承中分为父类和子类,这和人类关系是
相同的。当一个子类只有一个父类时称为单继承,当它同时具有多个父类时称为多继承,当然啦,这种关系在
现实生活中是不会出现的。

既然叫做继承,那么子类就一定会继承父类的一些特性啦。这就好比一个孩子的长相肯定和他父亲很像。
子类会继承父类中的所有成员,包括父类的数据和成员函数。

继承可以分为 私有继承,保护继承和共有继承,这三种继承方式所产生的派生类的访问数据的权限都是有所区别的
具体情况可以看下面的这张表。

深度剖析c++单继承与多继承


现在我已经把继承的大概思路讲完啦,接下来就开始讲一些常见的继承方式了,你们可要好好看哦

单继承

上面已经说过了,单继承就是一个子类只有一个父类。那么我们来看一段代码吧

class Student
{
public:
	void set()
	{
		cout << "name:";
		cin>>name;
	}
	void show()
	{
		cout<>sex;
	}
	void show1()
	{
		show();
		cout<

深度剖析c++单继承与多继承

从这段代码中,我们可以看到student类作为student1类的父类,将它的数据已经继承了,所以我们可以在
内存空间中看到,student1类不但有自己的数据,还有student的数据 name ,这就是所谓的继承
当然,由于派生类的函数不能访问基类的私有数据,但是我们就是想要访问时,那就需要使用基类的
函数去访问了。

多继承

多继承的情况就比较复杂了。多继承有一种最难理解的情况,菱形继承。这种继承方式类似于一个菱形结构
大概方式可以参考下面的图片。

深度剖析c++单继承与多继承
而多继承的重点也就在菱形继承展开。

菱形继承有很多缺点,以至于很多代码高手都很诟病这种继承方式,我们作为一个代码新手,一般情况下也不会
碰到这么复杂的情况,但是由于面试的时候面试官问到这种问题的几率还是很大的,所以我们有必要了解它的内部构成
原理。

先说说菱形继承的缺点吧。它会造成数据冗余和二义性的问题。你问我怎么才能看得出来呢?那么你就仔细看下面这段代码

class A
{
public:
int a;
};
class B:public A
{
public:
int b;
};
class C:public A
{
public:
int c;
};
class D:public B,public C
{
public:
int d;
}

深度剖析c++单继承与多继承
程序没有跑过,为什么?函数调用时不知道该选取哪一个父类作为基类,因为,在类D中具有两个基类,如果你不显示的去
说明应该使用哪一个,那么编译器自己肯定不会去主动选择的。请看类中数据的存储情况

深度剖析c++单继承与多继承
从这张图片中,我们可以大致看到a的数据在类b中有一份,在类c中也有一份,这样的话,数据重复不说,调用也会模糊,为了解决这个问题,我们引入了一个虚基类。只需要在类b和类c的前面加上关键字 virtual ,这些问题都会随之解决。
具体请看内存情况。很明显,现在a只有一份了,但是,又好像多了点什么?经过一番分析,我弄明白了,这里多了两个指针。这俩指针干啥用的呢?

这里又引入了一个新的名词,虚基表指针,就是因为它的出现,才使得菱形继承没有了刚刚的问题。

虚基表指针大概是这么使用的。请看下图。

深度剖析c++单继承与多继承

它指向了一段内存空间,里面存储着其他数据和基类数据的偏移量,这样,当有两个类同时继承了类a时
,我们只需要根据这个数据和其他数据的偏移量就可以判断它属于哪一个类了,这样就可以避免出现二义性的问题
那么我们再来算一算这两种继承方式的派生类的内存大小吧。
第一种方式的内存大小是20,第二种是24,这样算下来的话,好像第一种占用的内存空间会更小啊。实际上,我们定义的只是
最简单的数据类型啊,如果复杂一些的数据类型,那么节省下来的内存空间还是很客观的。因此,虚基类的使用不但解决了以上问题




还节约了大量的内存,着实很适合程序开发人员的使用呢

继承类的构造函数和析构函数
在说这个知识点之前,我得先说明一下,不论是哪种继承方式,派生类都不会继承基类的构造函数和析构函数。但是,常见情况是,
派生类的构造函数应该显示地调用基类的构造函数,而析构函数会在函数出了作用域之后自己执行,不需要显示的去调用,相反,如果你显式地调用这个函数,反倒会造成错误(内存泄漏)。

单继承的构造函数
例子

class Student
{
public:
	Student(int n,char *nam)
	{
		num=n;
		name=nam;
	}
	void display()
	{
		cout<<"num:"<


多继承的构造函数

一个派生类同时具有多个父类,并且都是直接父类,那么构造函数就要这样写啦。

class Student
{
public:
	Student(int a)
	{
		age=a;
	}
	int age;
};
class Teacher
{
public:
	Teacher(string nam)
	{
		name=nam;
	}
	stirng name;
};
class Graduate:public Student,public Teacher
{
public:
	Graduate(int a,string nam,char s):Student(a),Teacher(nam),wage(w){};
	float wage;
}

记得,一个多继承对象的构造函数只会在它的父类成员需要初始化时才会调用,因此,派生类中的每一个成员都只是显式地初始化了一次。

至于析构函数,它的规则和单继承没有什么冲突,采取先构造的后析构的原则。但是需要记得的是,析构函数一定不要显式地去调用,这会破坏程序的可执行性。


关于继承的知识我就总结这么多了。毕竟这部分的知识还是很重要的,我建议你们下去多敲代码,多去总结,光有理论是肯定不够的啊,一定要多敲代码啊.最后,欢迎广大朋友私信我哦,共同交流进步。

注:这篇文章中因为提到了虚基表指针的概念,但是我没有在此详细说明,我会再写一篇博客重点来介绍的。