演示代码的运行环境:VS 2017
- 基本概念
- 定义
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。 - 分类
动态多态:运行时多态,需要根据引用的对象确定
静态多态:编译时多态,编译期就可以确定 - 实现方法
虚函数,抽象类,覆盖,模板 - 意义
可以根据需求做出不同的反应,增加了代码的通用性
- 实现条件
- 继承
- 重写父类的虚函数
- 函数参数使用父类指针或引用
- 一些特殊情况
- 协变
父类和子类的虚函数返回值不同,但是某对父类和子类的指针或引用类型,如:
这种情况构成多态class A {}; class B :public A {}; class Father { public: virtual A& test(int a) { cout << "Father" << endl; return *(new A); } virtual void print() { cout << "Father" << endl; } }; class Son :public Father { public: B& test(int a) { cout << "Son" << endl; return *(new B); } };
- 构造函数加virtual声明
构造函数不能加virtual声明,编译器不支持这种操作,原因就是因为构造函数必须明确的构造类型 - 析构函数加virtual声明
析构函数可以加virtual声明,但是没有必要,因为析构函数本身就构成多态(通过delete函数实现)
- 抽象类
特点
- 不能实例化对象
- 包含纯虚函数(虚函数名=0)
继承了抽象类的派生类也不能实例化对象,除非重写纯虚函数,因此抽象类实际上是一种编程规范,它告诉我们派生类哪些接口需要实现多态。类中所有函数都是纯虚函数的类也叫接口类。
- 虚函数原理
废话不多哔哔,直接上代码
class AbsVir {
public:
virtual void report() = 0;
virtual void sign() = 0;
virtual ~AbsVir() = 0;
};
class Student1 : public AbsVir{
public:
virtual void report() {
cout << "我是张三" << endl;
}
virtual void sign() {
cout << "张三打卡" << endl;
}
virtual void address() {
cout << "我住张三家" << endl;
}
};
class Parent1: public Student1{
public:
virtual void report() {
cout << "我是张三的家长" << endl;
}
virtual void sign() {
cout << "张三家长打卡" << endl;
}
};
class Student2 : public AbsVir{
public:
virtual void report() {
cout << "我是李四" << endl;
}
virtual void sign() {
cout << "李四报到" << endl;
}
};
然后分别实例化这几个类的对象,编译运行。
我们可以在自动窗口中看到这三个类对象的展开情况:
可以看到,每个类中都会有一个叫__vfptr的二级指针,这个指针我们称作虚表指针,虚表指针本质上是一个函数指针数组,它所指向的地址空间中保存的内容称为虚函数表,子类在继承父类的虚函数时会在这个指针数组中保存子类中对应虚函数的函数地址。
因此,我们可以在运行时传入不同的对象来实现不同的功能,这就是多态的实现原理。
- 建议的代码风格
- 不能再被继承的虚函数加final修饰
例:
类Son的私房钱不想被Son的子类重写
如果有一个类继承了Son,那么它不能重写money,否则会报错class Father { public: virtual int money() { return 5; } }; class Son :public Father { public: int money() final{ return 10; } };
- 子类中需要重写的函数加override修饰
还是上面那个例子:
如果Son的函数money没有构成重写或协变则会报错class Father { public: virtual int money() { return 5; } }; class Son :public Father { public: int money() override{ return 10; } };