C++ 多态:面向对象的动态行为核心机制
C++ 多态的概念与分类,涵盖静态多态(函数重载、运算符重载)与动态多态(虚函数、基类指针)。详细讲解了虚函数定义、虚析构函数防止内存泄漏、纯虚函数与抽象类的使用,以及虚函数表底层原理。通过动物叫声、图形计算及计算器扩展等实战案例,展示了多态在代码复用与扩展性中的应用。

C++ 多态的概念与分类,涵盖静态多态(函数重载、运算符重载)与动态多态(虚函数、基类指针)。详细讲解了虚函数定义、虚析构函数防止内存泄漏、纯虚函数与抽象类的使用,以及虚函数表底层原理。通过动物叫声、图形计算及计算器扩展等实战案例,展示了多态在代码复用与扩展性中的应用。

多态是 C++ 面向对象三大特性之一,指同一行为在不同对象上表现出不同的形态,核心是'一个接口,多种实现'。
多态主要分为两大类,二者的实现原理和触发时机截然不同:
生活中的多态示例:同样是'动物叫'这个行为,猫的叫声是'喵喵喵',狗的叫声是'汪汪汪',不同动物对象表现出不同的行为形态。
静态多态的调用关系在编译阶段就已确定,编译器会根据参数列表的差异匹配对应的函数。
这是最常见的静态多态形式,同一作用域内的同名函数,通过参数的类型或数量区分。
#include <iostream>
using namespace std;
// 静态多态:函数重载
void print(int a) { cout << "整数:" << a << endl; }
void print(double b) { cout << "浮点数:" << b << endl; }
void print(string c) { cout << "字符串:" << c << endl; }
int main() {
// 编译阶段确定调用哪个 print 函数
print(10);
print(3.14);
print("C++ Static Polymorphism");
return 0;
}
通过重载运算符,让自定义类型支持内置运算符的操作,本质也是编译时多态。
#include <iostream>
using namespace std;
class Point {
public:
int x, y;
Point(int x = 0, int y = 0) : x(x), y(y) {}
// 重载 + 运算符,实现点的坐标相加
Point operator+(const Point& p) {
return Point(this->x + p.x, this->y + p.y);
}
};
int main() {
Point p1(1, 2), p2(3, 4);
// 编译阶段确定调用重载的 + 运算符
Point p3 = p1 + p2;
cout << "p3.x = " << p3.x << ", p3.y = " << p3.y << endl;
return 0;
}
动态多态是面向对象编程的核心,其关键是虚函数和基类指针/引用指向派生类对象,调用关系在程序运行时才确定。
实现动态多态必须同时满足三个条件:
虚函数的定义语法是在基类的成员函数前加 virtual 关键字,派生类重写时可以省略 virtual,但建议保留以增强可读性。
#include <iostream>
#include <string>
using namespace std;
// 基类:动物
class Animal {
public:
// 虚函数:动物叫
virtual void bark() { cout << "动物发出叫声" << endl; }
};
// 派生类:猫
class Cat : public Animal {
public:
// 重写基类的虚函数
void bark() override {
// override 关键字显式声明重写,建议添加
cout << "猫:喵喵喵" << endl;
}
};
// 派生类:狗
class Dog : public Animal {
public:
// 重写基类的虚函数
void bark() override {
cout << "狗:汪汪汪" << endl;
}
};
int main() {
// 基类指针指向派生类对象
Animal *animal1 = new Cat();
Animal *animal2 = new Dog();
// 运行时确定调用哪个类的 bark 函数
animal1->bark(); // 输出猫的叫声
animal2->bark(); // 输出狗的叫声
// 释放内存
delete animal1;
delete animal2;
return 0;
}
猫:喵喵喵 狗:汪汪汪
注意事项
override 关键字用于显式标记派生类对基类虚函数的重写,编译器会检查重写的合法性,建议添加。当基类指针指向派生类对象并通过 delete 释放时,如果基类析构函数不是虚函数,会导致派生类的析构函数无法被调用,从而引发内存泄漏。
解决方案:将基类的析构函数声明为虚析构函数。
#include <iostream>
using namespace std;
class Base {
public:
Base() { cout << "Base 构造函数" << endl; }
~Base() { cout << "Base 析构函数" << endl; }
// 非虚析构
};
class Derived : public Base {
public:
Derived() { cout << "Derived 构造函数" << endl; }
~Derived() { cout << "Derived 析构函数" << endl; }
// 无法被调用
};
int main() {
Base *p = new Derived();
delete p; // 仅调用基类析构函数,派生类析构未调用
return 0;
}
Base 构造函数 Derived 构造函数 Base 析构函数
#include <iostream>
using namespace std;
class Base {
public:
Base() { cout << "Base 构造函数" << endl; }
virtual ~Base() { cout << "Base 析构函数" << endl; }
// 虚析构函数
};
class Derived : public Base {
public:
Derived() { cout << "Derived 构造函数" << endl; }
~Derived() override { cout << "Derived 析构函数" << endl; }
// 重写虚析构
};
int main() {
Base *p = new Derived();
delete p; // 先调用派生类析构,再调用基类析构
return 0;
}
Base 构造函数 Derived 构造函数 Derived 析构函数 Base 析构函数
纯虚函数是没有函数体的虚函数,包含纯虚函数的类称为抽象类,抽象类无法实例化对象,只能作为基类被继承。
virtual 返回值类型 函数名 (参数列表) = 0;
#include <iostream>
using namespace std;
// 抽象类:图形
class Shape {
public:
// 纯虚函数:计算面积
virtual double getArea() = 0;
// 纯虚函数:计算周长
virtual double getPerimeter() = 0;
};
// 派生类:矩形
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
// 必须重写所有纯虚函数
double getArea() override { return width * height; }
double getPerimeter() override { return 2 * (width + height); }
};
// 派生类:圆形
class Circle : public Shape {
private:
double radius;
const double PI = 3.1415926;
public:
Circle(double r) : radius(r) {}
double getArea() override { return PI * radius * radius; }
double getPerimeter() override { return 2 * PI * radius; }
};
// 通用函数:打印图形信息
void printShapeInfo(Shape *shape) {
cout << "面积:" << shape->getArea() << endl;
cout << "周长:" << shape->getPerimeter() << endl;
}
int main() {
// 抽象类不能实例化对象
// Shape s; // 编译错误
Shape *rect = new Rectangle(5, 3);
Shape *circle = new Circle(4);
cout << "矩形信息:" << endl;
printShapeInfo(rect);
cout << "----------------" << endl;
cout << "圆形信息:" << endl;
printShapeInfo(circle);
delete rect;
delete circle;
return 0;
}
矩形信息: 面积:15 周长:16 ---------------- 圆形信息: 面积:50.2655 周长:25.1327
需求:设计一个支持多种运算的计算器,利用多态特性,让计算器可以动态扩展新的运算类型,无需修改原有代码(符合开闭原则)。
Operation,包含纯虚函数 calculate,用于计算结果。Add、减法 Sub、乘法 Mul、除法 Div。Calculator,接收 Operation 指针,调用计算方法。#include <iostream>
#include <stdexcept>
using namespace std;
// 抽象基类:运算
class Operation {
public:
double num1, num2;
void setNum(double n1, double n2) { num1 = n1; num2 = n2; }
// 纯虚函数:计算
virtual double calculate() = 0;
};
// 加法运算
class Add : public Operation {
public:
double calculate() override { return num1 + num2; }
};
// 减法运算
class Sub : public Operation {
public:
double calculate() override { return num1 - num2; }
};
// 乘法运算
class Mul : public Operation {
public:
double calculate() override { return num1 * num2; }
};
// 除法运算
class Div : public Operation {
public:
double calculate() override {
if (num2 == 0) throw invalid_argument("除数不能为 0");
return num1 / num2;
}
};
// 计算器类
class Calculator {
private:
Operation *op;
public:
Calculator(Operation *operation) : op(operation) {}
double compute(double n1, double n2) {
op->setNum(n1, n2);
return op->calculate();
}
~Calculator() { delete op; }
};
int main() {
try {
// 加法计算
Calculator addCalc(new Add());
cout << "10 + 5 = " << addCalc.compute(10, 5) << endl;
// 减法计算
Calculator subCalc(new Sub());
cout << "10 - 5 = " << subCalc.compute(10, 5) << endl;
// 乘法计算
Calculator mulCalc(new Mul());
cout << "10 * 5 = " << mulCalc.compute(10, 5) << endl;
// 除法计算
Calculator divCalc(new Div());
cout << "10 / 5 = " << divCalc.compute(10, 5) << endl;
// 测试除数为 0
cout << "10 / 0 = " << divCalc.compute(10, 0) << endl;
} catch (const exception& e) {
cout << "错误:" << e.what() << endl;
}
return 0;
}
10 + 5 = 15 10 - 5 = 5 10 * 5 = 50 10 / 5 = 2 错误:除数不能为 0
C++ 动态多态的底层实现依赖虚函数表(vtable)和虚函数指针(vptr),理解其原理能帮助我们更好地使用多态。
vptr,指向所属类的虚函数表。vptr 会指向派生类的虚函数表。vptr 找到虚函数表,再根据表中的地址调用对应的函数,从而实现动态多态。vptr,指向对应类的虚函数表。多态分为静态多态和动态多态,静态多态编译时确定,动态多态运行时确定。 动态多态的实现条件是:继承关系 + 虚函数重写 + 基类指针/引用指向派生类对象。 虚析构函数可以解决基类指针释放派生类对象时的资源泄漏问题。 抽象类包含纯虚函数,无法实例化,用于定义接口规范,强制派生类实现具体功能。 多态的核心优势是代码复用、功能扩展、符合开闭原则,是大型项目设计的核心机制。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 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