1.虚函数
这个地方的虚函数的virtual和前面虚拟继承的关键字virtual没有任何关联
只有成员函数才可以变成虚函数
虚函数可以完成重写(覆盖)
虚函数就是在函数前面+一个virtual
我们先来看这串代码 很神奇
func接受Person匿名对象和Student的匿名对象的切片(接收类型是Person)
我们前面讲过
派生类的切片是不能调用派生类的函数 也就是不能调用 Student::BuyTicket
也就是这个地方基类和派生类都只能调用基类的Person::BuyTicket;
但是调用的确是不同的BuyTicket
正常来说这个地方是p去调用BuyTicket()
但是这个地方p是Person类型 所以按照隐藏来算 调用的也应该是Person::BuyTicket;
但是这个地方我们不+Student::它也会自动调用Student::BuyTicket
2.多态
多态的条件
1.调用函数重写的虚函数
2.基类指针或者引用
多态 :不同对象传过去 调用不同函数
多态调用的是看的指向的对象
普通对象 看到是它的类型!
重写的条件本来是虚函数+三同(返回值相同 函数名相同 参数相同) 但是有一些例外
这个地方的三同不包括this指针
1.派生类的函数前面可以不+virtual (但是最好还是加上)
2.协变 返回值可以不同 但是要求返回值必须是父子关系和引用
(1)必须同时是父子关系 不可颠倒过来()
比如: 父类的virtual函数返回值子类 子类的virtual函数返回值是父类 不可以
(2)要同时为指针和引用 不可以一个是指针 一个是引用
(3)可以不是本身类的父子类 比如 如下图
void func(const Person& p)
{
p.Person::BuyTicket();
}
指定调用也会导致多态不满足
3.析构函数
析构函数可以是虚函数吗?为什么要是虚函数?
析构函数+virtual 是不是虚函数重写?
是因为析构函数都被处理成destructor这个统一的名字了
为什么要构成重写呢?看下面这种场景
这个地方我们为什么不会去调用派生类的析构函数?
delete 本质上是调用p的析构函数 再调用 operator delete(p)
但是这个地方p是普通Person 类型 所以它只会调用Person的析构函数
不会调用Student析构函数 如果Student自己特有的指针没有通过Student析构函数释放 就会导致内存泄漏
我们期望上面两个delete p能够调用正确的析构函数 而不是像普通类型一样
去调用其类型所属的析构函数
我们期望Person类和Student类的析构函数是一个多态调用 而不是普通调用
所以这个地方要构成多态 就要构成重写
要构成重写 原本的析构函数可不可以?
当然不可以(要求三同 函数名不同) 所以要统一处理成destructor
4.C++ 11 override和final
1.final :修饰虚函数不能再被重写
修饰类 +final的类叫最终类 最终类不能被继承
2.override:检查派生类虚函数是否重写了某个虚函数,如果没有重写编译器会报错
5.重载,覆盖(重写),隐藏(重定义)的对比
6.虚函数表
这个地方为什么sizeof(Base)是8?
因为这个地方有虚函数指针+对齐
这个地方和虚继承无关
虚表里面存的是虚函数的地址
虚函数本质上还是被放在代码段的
我们来看一下重写!
如果被重写了Johnson的虚函数指针就变成重写的虚函数指针(也就是把原来的虚函数指针覆盖了)
原本Johnson(子类)应该是父类继承过来的虚表地址 应该指向父类的虚函数
但是被重写覆盖成新的虚表地址 指向的是子类的虚函数!
因此调用的是不同的虚函数!
多态的本质就是根据不同的虚表去调用不同的虚函数
子类就是把它的切片 也就是子类中父类那一部分
无论是父类还是被切片的子类 看起来都是一样的
普通调用 在编译的时候确定地址
多态则在运行时到指向对象的虚函数表中找调用的函数地址
7. 设计不能被继承的类
如何设计出一个无法被继承的类?
思路1:
把构造函数/析构函数 私有化(这里以构造函数为例)
class A
{
public:
static A Creat()
{
return A();
}
private:
A()
{
}
};
int main(void)
{
A::Creat();
return 0;
}
但是这个地方就引发了一个问题 就是先有鸡还是先有蛋的问题?
这个地方是现有对象 才能用Creat函数 但是你要有对象就要先构造先调用Creat
静态的可以解决这个问题(上图和下图但是代码都是静态的方法)
#include <iostream>
using namespace std;
class A {
public:
static A Creat() {
return A();
}
private:
A() {}
};
class B {};
int main() {
// 使用静态函数创建非匿名对象
A nonAnonymousObj = A::Creat();
return 0;
}
析构函数也可以
#include<iostream>
using namespace std;
class A {
public:
static void Destroy(A* obj) {
delete obj;
}
private:
~A() {
cout << "析构" << std::endl;
}
};
int main() {
A* p = new A;
A::Destroy(p);
p = nullptr;
return 0;
}
#include <iostream>
class A {
public:
void Destroy() {
// delete this; 这两种方法本质上是一致的
this->~A();
operator delete(this);
}
private:
~A() {
std::cout << "析构" << std::endl;
}
};
int main() {
A* p = new A;
p->Destroy();
p = nullptr;
return 0;
}
思路2:
最终类 +final
8.虚函数重写的是什么?
虚函数重写的是实现!
我们来看这个题目
A是基类 B是派生类
p是B类型的指针 p首先访问test()
test的函数体内是func()
这个时候func构成虚函数重写吗?
重写是虚函数+协同/三同
首先 虚函数没问题
三同也没问题(三同是参数类型 返回值 函数名相同 和参数名 缺省值无关
多态的条件是
虚函数重写+基类的指针或引用
这个地方test传的参数是this指针 this指针是A类型的(指向的是B类中A类的那部分)
这个地方的test函数不是拷贝一份给派生类 而是在公共代码段
A类型的指针 也就是基类的指针 加上虚函数重写
所以构成多态
函数重写的是实现
函数头用的都是父类的(无论是基类还是派生类的指针/引用)
这个地方指针指向的是派生类的对象
所以调用的是
但是函数体却是派生类的(原本继承的是基类的函数体 但是被重写/覆盖了!)
所以这题最后选A
这个地方派生类调用重写的虚函数 函数头用的是父类的 但是其this指针还是子类的
因为派生类重写的虚函数里面可能会用到派生类特有的成员