、
多态的概念,多种形态,分为编译时的静态多态和运行时的动态多态。
静态多态主要是函数重载和函数模板。通过传不同的参数达成多态。
运行时多态,完成函数行动时,不同对象会完成不同的行为,就像买票一样,有学生票和成人票
多态的定义和条件
1.继承关系:必须存在一个基类(父类)和一个或多个派生类(子类),这是多态的基础 。
2.虚函数重写:基类中必须有被 virtual 关键字修饰的虚函数,并且派生类要对其进行重写(Override) 。(重写:派生类中必须有与基类函数名相同,返回值类型相同,参数列表相同,派生类的函数可以不加virtual仍构成重写,父类不能不加virtual)(重写的本质是重写虚函数的实)
3.指针或引用调用:必须通过基类的指针或引用来指向派生类的对象,并调用这个虚函数 。
协变
要实现协变,必须同时满足以下所有条件:
继承关系:基类和派生类的返回类型必须是指向或引用某个类的指针或引用 。
类型关联:派生类返回类型所指向/引用的类,必须是基类返回类型所指向/引用的类的公有派生类 。
仅限指针/引用:协变只适用于指针和引用,不适用于按值返回的对象 。
在继承中 基类的析构建议是虚函数,因为创建父类指针指向子类对象析构时,才会调用子类的析构;
#include<iostream> using namespace std; class a{ public: virtual void pf(){ cout<<"a"<<endl; } virtual ~a(){ cout<<"A"<<endl; } }; class b:public a{ public: virtual void pf(){ cout<<"b"<<endl; } virtual ~b(){ cout<<"B"<<endl; } }; void test(a* t){ t->pf(); } int main(){ a* t1=new a; a* t2=new b; test(t1); test(t2); delete t1; delete t2; }结果
a
b
A
B
A
多态与动态绑定:
- 在
main函数中,t1指向基类a的对象,t2指向派生类b的对象。- 当调用
test(t1)和test(t2)时,由于基类a中的pf()函数被声明为virtual(虚函数),程序会根据指针实际指向的对象类型来调用对应的函数(动态绑定)。t1实际指向a,因此输出a;t2实际指向b,因此输出b。虚析构函数与对象销毁:
- 当使用
delete释放对象时,如果基类的析构函数是虚函数(virtual ~a()),程序同样会根据实际对象的类型,先调用派生类的析构函数,再自动调用基类的析构函数。delete t1:t1是基类a的对象,直接调用~a(),输出A。delete t2:t2是派生类b的对象。因为基类析构函数是虚函数,会先调用派生类b的析构函数~b()(输出B),紧接着自动调用基类a的析构函数~a()(输出A)。
override和final
c++对虚函数的重写比较严格,因此c++11提供了override,可以帮助用户检测是否重写;
在函数名后加override;在编译时检查
cpp
virtual void pf()override{
.....}
如果不想让派生类重写虚函数可以使用final;
cpp
virtual void pf()final{
.....}
重载和重写和隐藏的区别

纯虚函数和抽象类
纯虚函数其实就是C++里定义接口的一种特殊方式。它最大的特点就是没有函数体,直接在声明后面加上 = 0。这种写法强制要求派生类必须给出自己的实现,否则派生类也会变成抽象类,无法实例化。
包含纯虚函数的类被称为抽象类。
- 不能实例化 :你不能创建抽象类的对象。例如,如果
Shape是一个包含纯虚函数draw()的抽象类,写Shape s;会导致编译错误。 - 只能作为基类:抽象类存在的意义就是被继承。它定义了一组接口规范。
cpp
class Animal {
protected:
string name;
public:
Animal(string n) : name(n) {}
// 纯虚函数:发出叫声 (对应图片:虚函数后面写 = 0)
// 这里的逻辑是:动物都会叫,但每种动物叫法不同,基类无法提供统一实现
virtual void makeSound() = 0;
// 普通虚析构函数(好习惯,防止内存泄漏)
virtual ~Animal() {}
};
// 2. 定义派生类:狗
class Dog : public Animal {
public:
Dog(string n) : Animal(n) {}
// 重写纯虚函数 (对应图片:强制派生类重写虚函数)
void makeSound() override {
cout << name << " 说: 汪汪汪!" << endl;
}
};
// 3. 定义派生类:猫
class Cat : public Animal {
public:
Cat(string n) : Animal(n) {}
// 重写纯虚函数
void makeSound() override {
cout << name << " 说: 喵喵喵!" << endl;
}
};
动态绑定和静态绑定
满足多态条件的函数调用在运行时绑定,在运行时到指定对象的虚函数表中找到调用函数的地址叫动态绑定
对于不满足多态条件的函数调用是在编译时绑定,在编译时确定调用函数的地址叫静态绑定。
- 访问指针 :程序拿到
ptr指针。 - 找到 vptr :通过
ptr找到对象内存开头的那个隐藏指针(vptr)。 - 查找虚函数表 :顺着 vptr 找到 Dog 类的虚函数表。
- 获取函数地址 :在表中查找
makeSound对应的槽位,拿到Dog::makeSound的地址。 - 调用函数:跳转并执行该地址的代码。
同类型的虚函数表相同