程序员成长之旅——继承和多态

程序员成长之旅——继承和多态


C++的三大特性有: 封装、继承、多态
今天我们来探索一下继承和多态。

继承和多态

先简单理解一下继承和多态:继承相当于子类继承了父类的数据和方法,子类父类我们也称为派生类和基类,继承一般我们在子类中添加的是父类没有的成员。而多态是建立在继承之上的,它使用了C++编译器最核心的技术,即动态绑定技术。其核心思想是父类对象调用子类对象的方法。

接下来我们通过一些问题加深这块的理解:
C++类继承的三种关系
C++中继承主要有三种关系:public、protected、private。

public继承子类也是public,可以替代父类完成父类接口所声明的行为;
protected继承子类也是protected,子类不能自动转换成为父类的接口。从语法来说,子类还是可以调用父类的protected成员,也就是只能在内部访问,不能在外部访问;
private继承子类是private,虽然子类还可以调用父类中的public和private成员,但是子类的子类就不可以调用了。

在这里插入图片描述
私有继承和组合有什么相同点和不同点
相同点:都可以表示“有一个”关系
不用点:私有继承中派生类能访问基类的protected成员,并且可以重写基类的虚函数,甚至当基类是抽象类的情况。组合不具有这些功能。
什么是多态
多态性的定义:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。有两种类型的多态性:
(1)编译时的多态性。编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译的时候,根据传递的参数,返回的类型等信息决定实现何种操作。
(2)运行时的多态性。运行时的多态性就是指直到系统运行时,才根据实际情况决定何种操作。C++中,运行时的多态性是通过虚成员实现。
虚函数是怎么实现的
简单的来说的话,虚函数是通过虚函数表来实现的。
事实上,如果一个类中含有虚函数,则系统会为这个类分配一个指针成员指向一张虚函数表,表中每一项指向一个虚函数的地址,实现上就是一个函数指针的数组。
构造函数调用虚函数
在构造函数中,虚拟机制不会发生作用,因为基类的构造函数在派生类构造函数之前执行,当基类构造函数运行时,派生类数据成员还没有被初始化。
为什么需要多重继承,优缺点是什么
实际生活中,一些事物往往会拥有两个或两个以上事物的属性,为了解决这个问题,C++引入了多重继承的概念。
优点:对象可以调用多个基类中的接口
缺点:容易出现继承向上的二义性
多重继承二义性的消除
(1)加上全局符确定调用那一份的拷贝。
(2)使用虚拟继承就可以。
多重继承和虚拟继承
(1)任何虚拟基类的构造函数按照它们被继承的顺序构造
(2)任何非虚拟基类的构造函数按照它们被构造的顺序构造
(3)任何成员对象的构造按照它们声明的顺序调用
(4)类自身的构造函数
继承和组合的区别?什么时候用继承?什么时候用组合
(1)public继承是一个is_a的关系,也就是每个派生类对象都是一个基类对象
(2)组合是一个has_a的关系,假设B组合了A,每个B对象中都有一个A对象
(3)继承一定程度会破坏基类的封装,依赖关系强,耦合度高
(4)组合是继承之外另一种复用选择,它的依赖关系不强,耦合度低。
(5)实际尽量去用组合。它的维护性高,但是要呈现多态的话就必须用继承。类之间的关系的换一般用组合就好。
(6)私有继承中派生类能够访问基类的protected成员,并且可以重写基类的虚函数,甚至当基类是抽象类的情况。组合不具有这些功能。
为什么要引入抽象基类和纯虚函数
(1)为了方便使用多态特性
(2)在很多情况下,基类本身生成对象时不合情理的。例如:动物可以作为一个基类派生出老虎、狮子等子类,但动物本身生成对象明显不合常理。抽象基类不能够实例化,它定义的纯虚函数相当于接口,能把派生类的共同行为提取出来。
虚函数和纯虚函数有什么区别
(1)类中如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。
(2)虚函数在子类里面也可以不重写,但是纯虚函数必须在子类中实现,它就是一个接口。
(3)虚函数的类用于“实作继承”,也就是继承接口的同时也继承了父类的实现。当然,大家也可以完成自己的实现。纯虚函数的类用于“介面继承”,即纯虚函数关注的是接口的统一性,实现由子类完成。
(4)但纯虚函数的类叫做虚基类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。这样的类也叫做抽象类。
虚函数存在哪?虚表存在哪?
虚函数和普通函数一样,都是存在代码段的,只是它的指针存在了虚表中。另外对象存的不是虚表,而是虚表指针。验证VS下存的也是代码段的。
inline函数可以是虚函数吗?
不能,因为inline函数没有地址,无法将地址放到虚函数表中。
静态成员可以是虚函数吗?
1.static成员不属于任何类对象或类实例,所以即使给此函数加上virutal也是没有任何意义的。
2.静态与非静态成员函数之间有一个主要的区别。那就是静态成员函数没有this指针。

虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable.
对于静态成员函数,它没有this指针,所以无法访问vptr. 这就是为何static函数不能为virtual.
虚函数的调用关系:this -> vptr -> vtable ->virtual function
构造函数可以是虚函数吗?
不能,因为对象的虚函数指针是在构造函数初始化列表阶段才初始化的。
析构函数可以是虚函数吗?
可以,并且最好把析构函数定义成虚函数。原因:将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
对象访问普通函数快还是虚函数快
首先如果是普通对象,是一样快的。如果是指针对象或者是
引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
虚函数表是什么阶段生成的,一般存在哪
虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
什么是DLL HELL
DLL HELL 主要是指DLL(动态链接库)版本冲突的问题。一般情况下,DLL新版本会覆盖旧版本,那么原来的旧版本的DLL的应用程序就不能继续正常工作了。
虚函数表
大家都知道,虚函数是通过一张虚函数表来实现的。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,其内容真实反映了实际的函数。这样,在有虚函数的类的实例化中,这个表被分配在了这个实例的内存中,所以当用父类的指针来操作一个子类的时候,这个虚函数表就非常重要了,它就像一个地图一样,指明了实际所应该调用的函数。
C++的标准规格说明书中说到,编译器必须保证虚函数表的指针存在于对象实例中最前面的位置(这个是为了保证正确取到虚函数的偏移量)。这意味着通过实例地址得到这张虚函数表,然后就可以遍历其中的函数指针,并调用相应的函数。
举个例子大家就懂了

#include<iostream>
using namespace std;

class Base
{
public:
	virtual void fun1() {cout << "Base::fun1" << endl;}
	virtual void fun2() {cout << "Base::fun2" << endl;}
	virtual void fun3() {cout << "Base::fun3" << endl;}
private:
	int num1;
	int num2;
};
typedef void (*fun)(void);

int main()
{
	Base b;
	Fun pFun;
	
	pFun = (Fun)*((int*)*(int*)(&b)+0);
	pFun();
	pFun = (Fun)*((int*)*(int*)(&b)+1);
	pFun();
	pFun = (Fun)*((int*)*(int*)(&b)+2);
	pFun();
}

执行结果是:

Base::fun1
Base::fun2
Base::fun3

在这里插入图片描述
一个类中会有多少张虚函数表呢?
对于一个单继承的类,如果它有虚拟函数,则只有一张虚函数表。对于多重继承的类,它可能有多张虚函数表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从零出发——

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值