c++多态知识树
多态的概念:
完成某个行为,不同对象去完成将会产生不同的状态。
多态的分类:
多态分为静态多态和动态多态。
静态多态:
程序在编译期间已经确定了函数的行为。
例:函数重载、模板
动态多态:
在程序运行时才可以确定函数的行为,即在编译阶段无法确定到底要调用哪个函数。
C++中的动态多态实现条件
1.必须要处于继承的体系下
2.基类中必须要有虚函数(virtual关键字修饰的函数称之为虚函数)且在子类中必须要对基类中的虚函数进行重写)
3.虚函数的调用:必须要通过基类的指针或者引用来调用
4.多态的体现:在程序运行时,根据基类的指针或引用指向不同类的对象选择适合的虚函数进行调用
注意事项
如果不将基类的成员函数设置为虚函数,那么使用基类的指针相当于对子类发生了截断只截取了子类从基类中继承的部分
#include<iostream>
using namespace std;
class animal {
public:
animal():color("red"),name("Bob")
{
}
void Bark() {
cout << "动物叫"<<endl;
}
virtual void sleep() {
cout << "动物睡觉" << endl;
}
char name[4];
char color[4];
};
class dog:public animal{
public:
dog():high(100) {
}
void Bark() {
cout << "汪汪"<<endl;
}
virtual void sleep() {
cout << "趴着睡" << endl;
}
int high;
};
什么是重写:
1.必须在继承的体系中
2.基类的成员函数必须是虚函数
3.子类和基类虚函数的原型(返回值类型 函数名字 参数列表)必须一模一样
子类成员函数(与基类已经声明为虚函数相对应的子类成员函数)之前是否加虚函数的关键字,子类成员函数都是虚函数
4.与访问权限没有关系。
比较同名隐藏(重定义)函数重载与重写(覆盖)
override关键字
被override修饰的函数首先必须是虚函数,然后并在该类中进行了重写,如果没有重写,或者不是虚函数,编译器在编译阶段就会报错。
final关键字
class animal {
public:
virtual void bark() {
cout << "动物叫" << endl;
}
virtual void walk() {
cout << "nihao" << endl;
}
int a = 1;
};
class Dog :public animal {
virtual void bark() {
cout << "汪汪汪" << endl;
}
virtual void walk()final {
//cout << "小狗" << endl;
}
int b = 2;
};
class Jinmao :public Dog {
virtual void bark() {
cout << "汪汪汪" << endl;
}
virtual void walk()final {
//cout << "小狗" << endl;
};
int c = 3;
};
被final关键字修饰的即表示虚函数的重写在该层就终止了。
抽象类
概念:
在虚函数的后面写上=0,则这个函数为纯虚函数。包含纯虚函数的类叫作抽象类(也叫接口类)
抽象类不能实例化对象
派生类继承后也不能实例化对象,只有在派生类中重写虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外虚函数更体现了接口继承。
接口继承与实现继承
普通函数的继承属于实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是进行重写。达成多态,继承的是接口。所以如果不实现多态,不要把函数设置为虚函数。
多态的实现原理
虚表
vs2019中通过监视窗口看到的_vfptr即虚表指针。
虚表是一个指针数组,存放的是函数指针,指针指向虚函数地址,虚函数存放在代码区
如果派生类重写了虚函数,那么虚表中对应的位置的函数指针就指向派生类重写的虚函数地址,
如果没有重写,那么虚表中对应位置函数指针还是指向基类的虚函数地址
注意:
非虚函数不会放到虚表中,直接通过this指针访问该函数;
多态实现原理
包含有虚函数类的模型
如果类中包含有虚函数,不论虚函数有多少个类对象,大小都多了四个字节。
这四个字节为虚表指针,指向虚表。
而且多增加的四个字节在对象的起始位置(vs2019下是如此的),内存存放的是虚表的地址,虚表地址是在构造函数时进行填充的
如果构造函数显示定义,编译器会对用户实现的构造函数进行修改(增加给对象前四个字节存放虚表的语句)