💯 博客内容:多态
😀 作 者:陈大大陈
🚀 个人简介:一个正在努力学技术的准C++后端工程师,专注基础和实战分享 ,欢迎私信!
💖 欢迎大家:这里是CSDN,我总结知识和写笔记的地方,喜欢的话请三连,有问题请私信 😘 😘 😘
目录
虚函数继承的条件
1.虚函数重写
2.必须是父类的引用或者指针来调用
普通调用
调用函数的类型是谁,就调用哪个类型的函数
多态调用
调用指针或者引用指向的对象,指向父类就调用父类,指向子类就调用子类。
例:
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:
virtual void BuyTickets()
{
cout << "全价买票" << endl;
}
};
class Student :public Person
{
virtual void BuyTickets()
{
cout << "半价买票" << endl;
}
};
void Func(Person& p)
{
p.BuyTickets();
}
int main()
{
Student st;
Func(st);
Person p;
Func(p);
}
上面的代码要是把引用去了,就是两个全价买票,因为普通调用只看参数类型。
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:
virtual void BuyTickets()
{
cout << "全价买票" << endl;
}
~Person()
{
cout << "~Person" << endl;
}
};
class Student :public Person
{
public:
virtual void BuyTickets()
{
cout << "半价买票" << endl;
}
~Student()
{
cout << "~Student" << endl;
}
};
void Func(Person& p)
{
p.BuyTickets();
}
int main()
{
Student st;
Func(st);
Person p;
Func(p);
}
一般情况下,析构没有问题。
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:
virtual void BuyTickets()
{
cout << "全价买票" << endl;
}
~Person()
{
cout << "~Person" << endl;
}
};
class Student :public Person
{
public:
virtual void BuyTickets()
{
cout << "半价买票" << endl;
}
~Student()
{
cout << "~Student" << endl;
}
};
void Func(Person& p)
{
p.BuyTickets();
}
int main()
{
/*Student st;
Func(st);
Person p;
Func(p);*/
Person* ptr = new Person;
delete ptr;
ptr = new Student;
delete ptr;
}
在new的情况下就会有问题,因为析构没有满足多态的条件,所以析构是普通调用。
加上virtual即可。
接下来来看一道易错练习题。
易错练习题
答案是B,容易选成D,需要注意的是,虚函数继承声明,重写实现。
派生类可以不加virtual,所以B的func接口继承了A。
所以val的值是1。
之后B对象切片后传参调用A的test,A的test调用B的func。
如果调用的语句是,p->func(),就不构成多态,答案会是B->0。
override 和 final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
1. final:修饰虚函数,表示该虚函数不能再被重写
2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Car
{ public:
virtual void Drive() final {}
};
class Benz :public Car
{ public:
virtual void Drive() {cout << "Benz-舒适" << endl;}
};
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};
纯虚函数
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
class Car
{ public:
virtual void Drive() = 0;
};
class Benz :public Car
{ public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
} };
class BMW :public Car
{ public:
virtual void Drive()
{
cout << "BMW-操控" << endl;
} };
void Test()
{
Car* pBenz = new Benz;
pBenz->Drive();
Car* pBMW = new BMW;
pBMW->Drive(); }
#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
class base
{
public:
virtual void func1()
{
cout << "func1()" << endl;
}
virtual void func2()
{
cout << "func2()" << endl;
}
void func3()
{
cout << "func3()" << endl;
}
private:
int _b = 1;
char _ch;
};
int main()
{
cout << sizeof(base) << endl;
base b;
return 0;
}
虚函数里会存一个虚函数表指针来保存地址。
vfptr(virtual function pointer);
它是一个函数指针数组。
父类和派生类的虚表指针不尽相同,重写了哪个虚函数,哪个虚表指针就会被覆盖。
也就是说:
至此,我们明白了多态的本质
多态的本质
多态为什么能指向父类调父类,指向子类调子类?
虚函数的重写也叫虚函数的覆盖。
编译器首先在对象里找到虚函数表的地址。
查找虚函数表中存储的地址,是父类就调用父类,是子类就切割成父类对象。
是因为虚表指针会去寻找所调用的类的地址。
为什么对象不能实现多态?
因为指针和引用可以指向子类对象中切割出来的父类的一部分。
而对象的拷贝不会拷贝虚表指针。
如果拷贝虚表指针的话,父类和子类的虚函数以及析构函数极易混淆,造成报错。