多态
什么是多态?
多态(Polymorphism)是面向对象编程的三大核心特性之一(封装、继承、多态),源于希腊语"多种形态"。在 C++ 中,它允许我们使用统一的接口处理不同类型的对象,显著提高了代码的灵活性和可扩展性。
核心概念
C++ 多态是面向对象编程的核心特性之一,允许通过统一接口处理不同类型对象。其实现依赖于基类指针或引用调用虚函数,并通过虚函数表(vtable)和动态绑定在运行时确定具体方法。文章详细阐述了多态构成条件、虚函数重写规则(含协变)、析构函数注意事项以及 override/final 关键字用法,并深入解析了 vtable 内存布局与静态/动态绑定的区别。

多态(Polymorphism)是面向对象编程的三大核心特性之一(封装、继承、多态),源于希腊语"多种形态"。在 C++ 中,它允许我们使用统一的接口处理不同类型的对象,显著提高了代码的灵活性和可扩展性。
核心概念
多态是一个继承关系下的类对象,去调用同一函数,产生了不同的行为。 实现多态还有两个必须重要条件:
类成员函数前面加 virtual 修饰,那么这个成员函数被称为虚函数。注意非成员函数不能加 virtual 修饰。
class A {
public:
virtual void func() {}
};
虚函数的重写/覆盖:派生类中有一个跟基类完全相同的虚函数 (即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称派生类的虚函数重写了基类的虚函数。
注意:在重写基类虚函数时,派生类的虚函数在不加 virtual 关键字时,也可以构成重写 (因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性)。
vptr 指针指向 vtable。override 关键字明确重写意图。virtual。派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
核心概念
协变的条件
基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加 virtual 关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成 destructor,所以基类的析构函数加了 virtual 修饰,派生类的析构函数就构成重写。
class A {
public:
virtual ~A() { cout << "~A()" << endl; }
};
class B : public A {
public:
~B() { cout << "~B()->delete:" << _p << endl; delete _p; }
protected:
int* _p = new int[10];
};
int main() {
A* p1 = new A;
A* p2 = new B;
delete p1;
delete p2;
return 0;
}
如果 ~A() 不加 virtual,那么 delete p2 时只调用的 A 的析构函数,没有调用 B 的析构函数,就会导致内存泄漏问题,因为 ~B() 中在释放资源。
只有派生类 Student 的析构函数重写了 Person 的析构函数,下面的 delete 对象调用析构函数,才能构成多态,才能保证 p1 和 p2 指向的对象正确的调用析构函数。
在虚函数的后面写上=0,则这个函数为纯虚函数,纯虚函数不需要定义实现 (实现没啥意义因为要被派生类重写,但是语法上可以实现),只要声明即可。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,如果派生类继承后不重写纯虚函数,那么派生类也是抽象类。纯虚函数某种程度上强制了派生类重写虚函数,因为不重写实例化不出对象。
virtual,并且没有实现的函数,形式为 virtual void func() = 0;。Shape 是一个抽象类,Circle 是派生类,重写了 draw() 函数。多态的使用:
Shape* shape = new Circle();
shape->draw(); // 输出:Drawing Circle
示例:
class Shape {
public:
virtual void draw() = 0; // 纯虚函数
};
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing Circle" << endl;
}
};
在 C++ 中,多态主要通过虚函数(virtual functions)和继承机制来实现。以下是多态实现的详细过程和原理:
class Base {
public:
virtual void func1() {}
protected:
int a = 1;
};
class Derive : public Base {
public:
virtual void func1() {}
protected:
int b = 2;
};
int main() {
int i = 0;
static int j = 1;
int* p1 = new int;
const char* p2 = "xxxxxxxx";
printf("栈:%p\n", &i);
printf("静态区:%p\n", &j);
printf("堆:%p\n", p1);
printf("常量区:%p\n", p2);
Base b;
Base* p3 = &b;
printf("虚表地址:%p\n", *(int*)p3);
return 0;
}
//通过比较虚表地址,找到其最近的内存片段来确定
Base 类的 show() 函数被声明为虚函数,Derived 类重写了这个虚函数。在类中使用 virtual 关键字声明函数为虚函数。
Base 为基类,Derived 为继承类。
class Base {
public:
virtual void show() {
cout << "Base show()" << endl;
}
};
class Derived : public Base {
public:
void show() override {
// override 表示重写基类的虚函数
cout << "Derived show()" << endl;
}
};
Derived 类的对象 d 时,对象中会包含一个 vptr,指向 Derived 类的 vtable。Derived 类的 vtable 中存储了 Derived 类重写的虚函数 show() 的地址。ptr 是一个指向 Base 类的指针,但它指向的是 Derived 类的对象。ptr->show() 时,程序会通过 ptr 找到对象的 vptr,然后通过 vptr 找到 Derived 类的 vtable,最终调用 Derived 类的 show() 函数。通过基类指针或引用调用虚函数:
Base* ptr = &d;
ptr->show();
创建对象:
Derived d;

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online