目录
一、多态
🍔多态的概念
🍟多态的定义及实现
🌮虚函数重写的两个例外
🥪C++11 override和final关键字
二、重载、重写(覆盖)、重定义(隐藏)的对比
三、抽象类
🍔概念
🍟接口继承和实现继承
四、多态的原理
🍔虚函数表
🍟多态的原理
五、单继承和多继承的虚函数表
🍔单继承的虚函数表
🍟多继承的虚函数表
六、结语
一、多态
🍔多态的概念
🌟多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
🍟多态的定义及实现
实现多态的要求:
1️⃣必须通过父类的指针或者引用调用虚函数
2️⃣被调用的函数必须是虚函数,且子类必须对父类的虚函数进行重写
🌟虚函数:即被virtual修饰的类成员函数称为虚函数。
🌟重写:子类中有一个跟父类完全相同的虚函数(即返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了父类的虚函数。
🌮虚函数重写的两个例外
1️⃣协变:子类重写父类虚函数时,与父类虚函数返回值类型不同。
即父类虚函数返回父类对象的指针或者引用,子类虚函数返回子类对象的指针或者引用时,称为协变。
2️⃣析构函数名字不同:如果父类的析构函数为虚函数,此时子类析构函数只要定义,无论是否加virtual关键字, 都与父类的析构函数构成重写
背后的原理:虽然父类与子类析构函数名字不同。虽然函数名不相同, 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
🥪C++11 override和final关键字
1️⃣override:检查子类虚函数是否重写了父类某个虚函数,如果没有重写编译报错
2️⃣final:修饰虚函数,表示该虚函数不能再被重写
二、重载、重写(覆盖)、重定义(隐藏)的对比
❓区分三个概念:
三、抽象类
🍔概念
🌟纯虚函数:在虚函数的后面写上=0 ,则这个函数为纯虚函数。
🌟抽象类:包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。子类继承后也不能实例化出对象,只有重写纯虚函数,子类才能实例化出对象。纯虚函数规范了子类必须重写,另外纯虚函数更体现出了接口继承。
🍟接口继承和实现继承
🌟实现继承:普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
🌟接口继承:虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
四、多态的原理
🍔虚函数表
通过vs监视窗口我们发现,Base对象除了有成员_b还有一个__vfptr指针,这个指针我们叫做虚函数表指针(v代表virtual,f代表function),。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表
❓子类表中放了什么呢?我们进一步分析更复杂的情况
发现以下几点:
1️⃣ 子类对象d中也有一个虚表指针,子类对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在这部分的,另一部分是自己的成员。
2️⃣父类b对象和子类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表 中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
3️⃣虚函数表本质是一个存虚函数指针的指针数组,一般这个数组最后面放了一个nullptr。
4️⃣总结一下子类的虚表生成:
🅰️先将父类中的虚表内容拷贝一份到子类虚表中
🅱️如果子类重写了基类中某个虚函数,则用子类自己的虚函数覆盖虚表中基类的虚函数
🆑派生类自己新增加的虚函数按其在子类中的声明次序增加到子类虚表的最后。
5️⃣这里还有一个很容易混淆的问题:虚函数存在哪的?虚表存在哪的?
答:虚函数存在虚表,虚表存在对象中。
注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的
🍟多态的原理
🌟了解了虚函数表的存在后我们就能想到为什么传Person调用 Person::BuyTicket,传Student调用的是Student::BuyTicket:
多态的原理:
p指向Person对象时,p->BuyTicket在p的虚表中找到虚函数是Person::BuyTicket
p指向Student对象时,p->BuyTicket在s的虚表中找到虚函数是Student::BuyTicket
这样就实现出了不同对象去完成同一行为时,展现出不同的形态------多态
💧补充:反过也就明白为什么实现多态要满足虚函数覆盖,对象的指针或引用调用虚函数这两个条件了
💧同时:也可以知道,子类对象给父类对象赋值时,会切割出子类对象中父类那一部分成员,拷贝给父类,但不会拷贝虚函数表指针,否则,指向父类时就不一定是父类的虚表
五、单继承和多继承的虚函数表
🍔单继承的虚函数表
🌟我们来看看单继承的虚函数表:
🍟多继承的虚函数表
🌟多继承的虚函数表:
六、结语
🫡你的点赞和关注是作者前进的动力!
🌞最后,作者主页有许多有趣的知识,欢迎大家关注作者,作者会持续更新有意思的代码,在有趣的玩意儿中成长!