继承【C++】

继承【C++】

  • 一.什么是继承?
  • 二. 继承的方式与权限
  • 三. 继承中的成员
    • 3.0 基类和派生类中的重名成员
      • i. 限定符
      • ii. 隐藏
    • 3.1 继承与默认成员函数
      • i. 默认构造
      • ii. 析构函数
    • 3.2 继承与友元函数
    • 3.3 继承与静态成员变量
  • 四. 基类和派生类的赋值
  • 五. 多继承
    • 5.1 菱形继承
    • 5.2 菱形模拟继承
  • 六. 继承的总结
    • i. 能用多继承就不要用菱形继承
    • ii. 能用单继承就不要用多继承
    • iii.能用组合就不要用继承


一.什么是继承?

继承听名字大家可能就知道它是什么意思了
但是不知道继承的对象和内容是什么?
这时候告诉大家继承是用在类和类的

那这个时候应该都清楚继承是干嘛用的了

类如果继承了一个类,那能继承什么呢?
类中的内容:成员函数和成员变量

所以这里想都不用想就知道继承的是类中的函数和变量。

这里被继承的类叫做:基类
继承后的新类叫做:派生类

派生类拿到的是基类的成员的使用权

二. 继承的方式与权限

上面我们知道了继承的具体对象,那接下来就是该如何继承了

最普通的格式就是:
继承【C++】_第1张图片

上面就是派生类child继承了基类parent

这里我们能看到public
这个public就是继承方式

我们都清楚:
类的成员中都有成员变量的访问权限,用来控制外界对类中成员的访问权限

继承【C++】_第2张图片
那既然有访问限定符能控制类中的成员的外界访问权。

那有没有一种方法来控制派生类对基类继承成员的访问权限。

实现这个作用的就是我们的继承方式了

继承【C++】_第3张图片

这里用了一张表格来表示了
不同权限的基类在用对应的继承方式后在派生类的权限

这里其实也不用记得特别牢
因为品尝我们使用时基本上都是public继承
但是要记的话也很简单:
1. private让所有成员不可见
2. 其他访问方式都是取决于访问方式和类本身权限的大小,谁小取谁

这里我们能了解到private与protected的区别了
没学继承前,我们都把private和protected看成一样的。

但是现在我们能了解到它们的区别了

继承【C++】_第4张图片

这里我们发现这个c变量报错了

这就能看出区别了:
private继承后不能被派生类以及其他类访问
protect继承后能被派生类访问,但是不能被其他类访问

这里还有个小细节,不知道大家发现没有:
这里的private继承方式写的是不可见:
这里我们应该就能想到:
private的继承方式,并非是继承时不带上其他成员,而是派生类中带上了基类成员,但是不可访问而已

三. 继承中的成员

我们知道了继承是一个了类继承一个类的成员函数和成员变量

但是还是有一些细节的,因为成员变量和成员函数也有很多不同的类型。

3.0 基类和派生类中的重名成员

继承【C++】_第5张图片
这两个重命名变量,这里我们进行一下打印

继承【C++】_第6张图片
这里优先的是派生类中的a

这里其实和全局变量和局部变量重命名打印一样
遵从的是就近原则

我们都知道类有自己的类域
所以同时这里要注意:派生类和基类这里都有各自的类域

i. 限定符

因为派生类和基类有各自的类域

所以这里想要访问基类中的a
这里就可以用访问限定符。

继承【C++】_第7张图片
这里我们就能发现结果是1了
继承【C++】_第8张图片

ii. 隐藏

不光是变量能重名,函数同样也能重名。
继承【C++】_第9张图片
按以前来来说当函数名不一样,这个时候就是重载
但是这个看起来就和重载不一样

因为重载当初的有两点:
1.必须要在同一个域中
2.函数参数列表不同

那这里换个情况
继承【C++】_第10张图片
这里两个类的函数参数列表不同,那这样算不算是函数重载

这里肯定是否定的,因为这样就不符合在同一个域中的条件。

那这样不算是重载,那算是什么情况。

这个就是全新的情况了:隐藏

隐藏的条件如下:
1.成员名重名
2.处于不同类域中。

3.1 继承与默认成员函数

默认成员函数可以说是类都会调用的函数

当派生类继承基类后,对类进行实例化时
调用派生类的构造函数时,会自动去调用父类的构造和析构函数

因为派生类继承了父类的成员
所以派生类进行实例化的时候,基类同样也要进行实例化。
因为父类的成员也需要去定义

但是这里要注意,派生类继承基类的成员函数时
和基类是公用一个成员函数的,但是传this指针时,还是看类的类型

所以这里就来研究一下不管什么类实例化都需要走的默认构造和析构

i. 默认构造

这里我们先来测试一下子类会不会去调用父进程的构造函数

继承【C++】_第11张图片

继承【C++】_第12张图片
这里就能清楚的看出来子类调用了父类的构造函数。

同时我们能发现父类比子进程要先进行实例化

这里还有种情况:如果父类没有合适的构造函数该怎么办:
继承【C++】_第13张图片
这里能发现报的错是父类不存在默认构造函数

那这里我们想用参数给父类传参进行构造该怎么办?
继承【C++】_第14张图片
这里我们就可以在子类构造函数的初始化列表中传入给父类构造的参数。

这是c++语法设计的。

ii. 析构函数

接下来除了构造接下来第二个大头就是析构函数了

因为我们也清楚既然派生类会先自动调用父类的构造函数
那这样也会自动去调用父类的析构函数
所以我这里只要知道执行的先后顺序即可:

继承【C++】_第15张图片

继承【C++】_第16张图片
这里我们就能够看出
基类要后进行析构

这样设计正好符合了先定义的要后进行析构
因为如果先定义的先进行析构了,这样的话如果还有其他成员在使用,就会产生问题。

3.2 继承与友元函数

这里其实一句话就能讲清楚:

父类的友元关系不会继承到派生类中。

3.3 继承与静态成员变量

要了解这两个的关系

首先我们要清楚子类继承的是父类成员中的使用权
所以子类继承父类中的静态成员的使用权后,不再需要去定义

因为父类成员中已经定义过一次了,静态成员只会定义一次

这样也能解决一个问题:检测父类被继承了多少次

继承【C++】_第17张图片

创建一个静态成员变量,在父类的构造函数中赛个++就可以了。
因为子类实例化都会去调用父类的构造函数。
继承【C++】_第18张图片

四. 基类和派生类的赋值

这里我们要注意一个父类和基类能够进行赋值运算

这里我 们其实想想也知道是派生类能赋值给基类。

继承【C++】_第19张图片
因为派生类中有很多的基类中的内容
但是基类没有派生类中的成员。

五. 多继承

多继承这里就造成了一个C++中的一个巨坑
其实java中或者其他语言都不支持多继承。
就是看到了其中的坑,具体坑在哪,接下来就来看看。

首先是多继承,顾名思义,就是一个类继承了多个类。

继承【C++】_第20张图片
这里的子类就继承了father类和mom类

这里看起来好像很正常没有坑的样子,但是换种别的继承方式,就有坑了。

5.1 菱形继承

这个就是多继承中的天坑了

这里先直接上代码

class people 
{
public:
int a;

};

class father : public people
{
public:

	~father()
	{
		std::cout << "father\n";
	}
};
class mom : public people 
{

};


class child : public father ,public mom
{
public:
	~child()
	{
		std::cout << "child\n";

	}
};

这里或许可能还有人看不出来
这里就直接上个示意图
继承【C++】_第21张图片
大致就是这样的一个继承关系
这样我们就能很方便的看出来是个菱形了
这里我们也能看到这里最大的问题不是继承了父母

而是父母共同继承了people

这里是结构图
继承【C++】_第22张图片

这里我们也从底层验证一下:

class people 
{
public:
	int people;

};

class father : public people
{
public:
	int father;
};
class mom : public people 
{
public:
	int mom;
};


class child : public father ,public mom
{
public:
	int child;
};

int main()
{
	child c1;
	c1.child = 0;
	c1.father = 1;
	c1.mom = 2;
	c1.father::people = 3;
	c1.mom::people = 4;
}

继承【C++】_第23张图片
这里能看出这里的内存按照需求进行了排列

这样子会带来两个问题:
1.数据冗余
有两个people类(重点是这个问题)
2.访问不清
(这个其实还好,用限定符就可以解决)

5.2 菱形模拟继承

所以C++为了填坑,特意出了菱形模拟继承

专门用来解决这个多继承的坑。

class people 
{
public:
	int people;

};

class father :virtual public people
{
public:
	int father;
};
class mom : virtual public people
{
public:
	int mom;
};


class child : public father ,public mom
{
public:
	int child;
};

int main()
{
	child c1;
	c1.child = 0;
	c1.father = 1;
	c1.mom = 2;
	c1.father::people = 3;
	c1.mom::people = 4;
}

使用方法:
在两个类都需要的类继承时添加上virtual即可
继承【C++】_第24张图片

这里我们就来看看它的变化
(接下来用了32位,但其实没有啥大区别)
继承【C++】_第25张图片

这里发现原本的在father和mom中的people
到了最底下,并且只有一个值了

这就是虚拟继承干的事了

mom和fahter共同继承的people变成了公有类
塞在了类的最下方

这里明眼人可能已经看到了
继承【C++】_第26张图片
mom和father类中多了这样两个数据
而且只有最前面的数据不一样

其实这里是通过了B和C的两个指针
指向的一张表。这两个指针叫虚基表指针

而指向的一个表则是虚基表

虚基表中存的偏移量能帮助father和mom找到people类。

这里可以来验证一下
在这里插入图片描述

在这里插入图片描述
这里能看到虚基指针指向的虚基表中的偏移量是14

这样完美解决了多继承中的数据冗余的问题

六. 继承的总结

这里讲完了继承的用法与细节,这里就来总结一下继承的用法

i. 能用多继承就不要用菱形继承

菱形继承我们也知道了它的坑有多大,所以能不用最好不用

ii. 能用单继承就不要用多继承

iii.能用组合就不要用继承

这里的组合就是指
继承【C++】_第27张图片
就是将类和类进行组合

用这个对比继承有两个好处:
1.保留了类的封装性
2.低耦合性

第一点很好理解
继承后,能使用类中的成员,而组合则是使用这个类的对象。

第二点的耦合性是什么意思?
其实也很好理解
耦合可以看作是粘黏性,也可以说是相关性

比如继承中,如果继承的基类成员发生了改变
那么派生类中的也发生了改变,因为派生类很有可能引用了基类中的成员
所以当我们在开发时,修改了基类,就要去修改一大堆继承它的派生类。
所以这是很不利于开发的。

所以组合来说,只是引用了它的类对象。

所以关联性不是很大。

所以我们尽量使用低耦合的开发模式。

你可能感兴趣的:(c++,开发语言)