多态
子类中满足函数名相同,参数相同,返回值相同的虚函数(跟虚继承无关,只是关键字相同),叫做重写
在继承中构成多态有两个条件
1.必须通过父类的指针或者引用调用虚函数
2.被调用的函数必须是虚函数,且子类必须对父类的虚函数进行重写
//构成多态,跟p的类型没有关系,传的是哪个类型的对象,调用的就是这个类型的虚函数--跟对象有关
//不构成多态,调用的就是p类型的函数 -- 跟类型有关
void func(Person& p)
{
p.BuyTicket();//多态
}
虚函数和重写
虚函数:被virtual修饰的类的非静态成员函数称为虚函数,其他函数不能称为虚函数。
重写:子类中满足函数名相同,参数相同,返回值相同
class Person {
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
class Student : public Person {
public:
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
}
};
重写要求返回值相同有一个例外:协变–要求返回值是父子关系的指针或者引用
析构函数是虚函数,构成重写
析构函数名被特殊处理,处理成destructor
普通对象,析构函数是否是虚函数,是否完成重写,都正确调用
普通对象调用析构函数不需要是虚函数,因为不符合多态的条件(没有用到父类的指针或者引用)
动态申请的父子对象如果给了父类指针管理,需要析构函数是虚函数,完成重写,构成多态,才能正确使用析构函数(并不是所有场景都需要)
因为这里我们希望p1调用父类的析构,p2调用子类的析构,不符合多态就跟类型有关,只把父类的成员销毁,子类资源还没销毁;其他场景析构函数是不是虚函数,都可以正确调用析构函数。
虚函数的重写允许两个都是虚函数或者父类是虚函数,再满足三同,就构成重写。
子类虽然没写virtual
,但是他继承了父类的虚函数的属性,再完成重写,那么他也算虚函数
final 和 override
设计一个不能被继承的类
方法一:构造函数私有了就不能被继承了,因为子类要初始化父类,只有一种方式,就是调用父类的构造函数,虽然子类继承了,但是构造函数私有了,语法上子类就用不了,无法构造。
//间接限制,子类构造函数无法调用父类构造函数初始化成员,没办法实例化对象
//子类创建对象就会报错
class A
{
private:
A(int a=0)
:_a(a)
{}
protected:
int _a;
};
class B:public A
{
};
方法二:父类后+final
class A final
{
protected:
int _a;
};
//子类定不定义对象都无法被继承
class B:public A
{
};
final
也可以修饰虚函数,限制他不能被子类中的虚函数重写
override
override
放在子类重写的虚函数后面,检查是否完成重写,没有重写就会报错。
重写,重载,隐藏区别
抽象类
在虚函数后面写上=0
,则这个函数为纯虚函数,包含纯虚函数的类叫做抽象类
,抽象类不能实例化出对象,纯虚函数一般只声明不实现,因为实现没有价值。
一个类型,在现实世界中,没有具体的对应实物就定义成抽象类
如果子类继承后重写虚函数,父类才能实例化出对象,调用的也只是子类的虚函数
class Car
{
public:
virtual void Drive() = 0
{
cout << "virtual void Drive() = 0" << endl;
}
void f()
{
cout << "void f()" << endl;
}
};
class Bus :public Car
{
public:
virtual void Drive()
{
cout << "virtual void Drive()" << endl;
}
};
int main()
{
Car* p = new Bus;
p->Drive();
return 0;
}
纯虚函数的类,本质上强制子类去完成虚函数的重写
虚表指针
先来看一道题目
//类A占几个字节
class A
{
public:
virtual void func()
{};
virtual void func2()
{};
private:
int _a ;
char _b;
};
int main()
{
cout << sizeof(A) << endl;
return 0;
}
答案不是8而是12字节,原因是类A多了一个虚表指针
多态的原理:父类指针/引用,指向谁,就去谁的虚函数表中找到对应位置的虚函数进行调用
同类型的对象,虚表指针是相同的,指向同一张虚表
普通函数和虚函数存储的位置一样,都在代码段,只是虚函数要把地址存一份到虚表,方便实现多态。
而虚函数表存放在常量区,而不是栈
不是多态,编译时确定地址;是多态,运行时确定地址,会到虚函数表中去找,再调用
首先子类继承父类,对父类的虚函数进行重写,指向虚表的指针就改变了,所以原理层,重写也叫覆盖
为什么实现多态只能用引用或者指针,不能用对象?
子类切片给父类类型的p2,进行了切片,只拷贝成员,没有拷贝虚表
指针和引用为什么可以?
因为指针和引用切片是不一样的,指针和引用不会进行拷贝,变成子类对象中父类部分的别名/指针。
多继承,子类重写了Person和Student虚函数func1,但是虚表中的func1地址却不一样,不过最终调到还是同一个函数
打印虚表
//函数指针跟常规typedef不一样
typedef void(*VF_PTR)();
//void PrintfVFTable(VF_PTR table[])
void PrintfVFTable(VF_PTR* table)
{
for (int i = 0; table[i] != nullptr; i++)
{
printf("vft[%d]:%p\n", i, table[i]);
}
}
int main()
{
Person p;
//虚表指针是指针,第一个存放
//所以传就是传头四个字节
PrintfVFTable((VF_PTR*)(*(int*)&p));
Student s;
return 0;
}