C++ 多态 纯干货讲解 复制可调试(1)-CSDN博客
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
博客内容多态
作 者陈大大陈
个人简介一个正在努力学技术的准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的testA的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);
它是一个函数指针数组。
父类和派生类的虚表指针不尽相同重写了哪个虚函数哪个虚表指针就会被覆盖。
也就是说
至此我们明白了多态的本质
多态的本质
多态为什么能指向父类调父类指向子类调子类
虚函数的重写也叫虚函数的覆盖。
编译器首先在对象里找到虚函数表的地址。
查找虚函数表中存储的地址是父类就调用父类是子类就切割成父类对象。
是因为虚表指针会去寻找所调用的类的地址。
为什么对象不能实现多态
因为指针和引用可以指向子类对象中切割出来的父类的一部分。
而对象的拷贝不会拷贝虚表指针。
如果拷贝虚表指针的话父类和子类的虚函数以及析构函数极易混淆造成报错。