C++继承二三事

继承定义

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类

继承是is a的关系,子类(派生类)继承了父类(基类),子类拥有和父类一样的属性:

所以子类可以使用父类的方法,父类的成员变量也拷贝到了子类!

定义格式

     派生类  继承方式   基类 

class subclass : public parentclass {}

继承方式

在这里插入图片描述
权限:public>protected>private
父类该成员的权限与继承方式的权限取最小权限,就是在子类的成员权限

public都可以访问
protected 类外不可以访问,但子类可以访问
private 类外不可以访问,子类不可见

不可见:
父类中的"成员"还是拷贝到了子类中,子类访问不到,但子类内存中有

基类和派生类对象赋值转换

这部分是继承的重中之重,没完全理解这部分,继承肯定还存在问题,多态肯定也懵
下面我来阐述下我个人的理解,如有问题,敬请各位大佬斧正

1.基类不能赋值给派生类
2.派生类对象可以赋值给基类的对象/指针/引用,有个形象的说法叫切片或者切割

1:
基类含有的成员肯定小于等于派生类,这类似于类与对象this指针的赋值,只允许权限缩小,不允许权限扩大

2:
①派生类赋值给基类的对象

#include<iostream>
using namespace std;
class A
{
    
    
public:
	void show()//切片
	{
    
    
		cout << _age << endl;
	}
protected:
	int _age=10;
};
class B :public A
{
    
    
public:
	int _num;
};
int main()
{
    
    
	B b;//派生类
	A a=b;//基类
}

在这里插入图片描述
可以看到派生类继承了A,另外有拥有特有成员_num
将派生类赋值给基类的时候会发生截断(切片)

a是具体的类,会另外开辟出一个父类对象的空间(这在多态原理中会用到),并将派生类中父类的成员拷贝过去,虚函数指针并不会拷贝过去,a是具体的类,拥有A独有的虚函数指针

②派生类赋值给基类的指针/引用

这种情况指针会指向派生类中和父类共同的部分

像这样:
在这里插入图片描述
该指针和引用仅能访问和父类的公共部分,并且只能访问继承下来的父类成员

#include<iostream>
using namespace std;
class A
{
    
    
public:
	//A:this *= B:this*;
	void show()//切片
	{
    
    
		cout << _age << endl;
	}
protected:
	int _age=10;
};
class B :public A
{
    
    
	
public:

	int _age = 20;
	int _num;
};
int main()
{
    
    
	//1
	B b1;
	b1.show();
	//2
	A* a1 = &b1;
	a1->show();
	//3
	A& a2 = b1;
	a2.show();
	
	return 0;
}

b1,a1的内部结构
在这里插入图片描述

实例化b1后,b1调用show函数,派生类可以调用父类的函数,此时发生,父类的this指针=子类的this指针,发生切片行为,并且这个指针只能访问从父类中继承下来的成员,不能访问子类的特有成员

此外,子类跟父类都有独有的作用域,子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义

所以从上图中看到b1中有两个_age,从A中继承下来的等于10,重定义的等于20,所以打印的是父类继承下来的10

同理2,3也发生了切片,调用函数该指针只能访问基类继承下来的成员而不是重定义的20

运行结果:
在这里插入图片描述

那如果我们对B类修改一下,第一步就会打印出20,
在这里插入图片描述
在这里插入图片描述

因为父类继承下来的是10,在构造函数中,修改了父类继承下来的为20

菱形继承

单继承和多继承:

单继承:一个子类只有一个直接父类时称这个继承关系为单继承
在这里插入图片描述

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
在这里插入图片描述
菱形继承:菱形继承是多继承的一种特殊情况
在这里插入图片描述

菱形继承的问题(坑)

#include<iostream>
using namespace std;
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
{
    
    
	int _d;
};
int main()
{
    
    
	D d;
	//d._a = 10; 二义性编译器不知道调用从B还是C中继承而来的_a
	
	return 0;
}

d的类成员模型:
在这里插入图片描述

在这里插入图片描述

可以看到,D继承了来自从B来的_a,以及从C来的_a,造成了数据冗余,
当我们d._a访问的时候,编译器不知道访问哪个_a,又造成了二义性

如果通过下面方式解决:
在这里插入图片描述
在这里插入图片描述
虽然可以通过指定作用域来访问_a,解决了二义性,但是无法解决冗余性,一个类有同一成员变量有两份,这是不允许的

为了解决这个问题
提出了虚继承(跟虚函数没得关系)
在这里插入图片描述

#include<iostream>
using namespace std;
class A
{
    
    
public:
	int _a;
};
class B :virtual public A
{
    
    
public:
	int _b;
};
class C :virtual public A
{
    
    
public:
	int _c;
};
class D :public B, public C
{
    
    
	int _d;
};
int main()
{
    
    
	D d;
	d._a = 20;
	cout << d._a << endl;
	d.B::_a = 10;
	cout << d.B::_a << endl;
	return 0;
}

程序执行d._a=20后
在这里插入图片描述
从监视窗口看:
虚继承后,可以看到d内中的_a被同时赋值了,就不存在二义性,也没有了冗余相当于只有一份数据

C++编译器是如何解决虚继承的二义性和冗余的

通过监视窗口并不能真正的查看到真实的情况,编译器被处理过了
所以通过内存窗口查看

①:不是虚继承的情况
在这里插入图片描述
在这里插入图片描述
在普通菱形继承里面有两份_a的地址
②虚继承情况
请添加图片描述
在这里插入图片描述

_a放在了最下面

在虚继承,两个_a共用了一份地址,就解决了数据冗余和二义性

但B和C区域中多了两行数据,这一行刚好四个字节

很容易想到是指针,那它指向的是什么呢?
让我来带你研究

虚基表:
在这里插入图片描述
可以看到所指向位置的下一个分别为20,12
这分别对应B里面跟C里面的指针(虚基表指针)到公共基类_a的偏移量

那这有什么用呢?

D d;
B b=d;
C c=d;

像这样b,c发生切片,要找到公共的_a就需要通过这个偏移量。

第一行全部为零,是为多态虚表预留的偏移量(多态再介绍)

猜你喜欢

转载自blog.csdn.net/hbbfvv1h/article/details/120959839