C++函数重写是面向对象编程中实现运行时多态的关键机制,指的是在派生类中重新定义基类中已经存在的虚函数,使得通过基类指针或引用调用该函数时,能够根据实际指向的对象类型执行对应的派生类实现。这种特性让代码具备更好的扩展性和灵活性,是很多复杂系统架构设计的基础。

函数重写的核心条件
要实现合法的函数重写,必须满足以下几个核心条件,缺少任何一个都可能导致重写失败,甚至变成普通的函数隐藏:
- 基类中被重写的函数必须声明为
virtual虚函数,否则无法实现运行时多态绑定。 - 派生类中的重写函数必须与基类的函数签名完全一致,包括函数名、参数列表、返回类型(协变返回类型除外)。
- 派生类的重写函数访问权限不能比基类的更严格,通常建议保持和基类一致的访问级别。
- 重写函数不能抛出比基类函数更宽泛的异常类型,符合异常规范的兼容性要求。
基础实战示例
下面通过一个简单的图形面积计算的案例,展示函数重写的基本实现方式:
#include <iostream>
using namespace std;
// 基类:图形类
class Shape {
public:
// 声明为虚函数,支持重写
virtual double getArea() {
return 0.0;
}
// 虚析构函数,确保派生类对象正确析构
virtual ~Shape() {}
};
// 派生类:圆形类
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 重写基类的getArea方法
double getArea() override {
return 3.14159 * radius * radius;
}
};
// 派生类:矩形类
class Rectangle : public Shape {
private:
double width;
double height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
// 重写基类的getArea方法
double getArea() override {
return width * height;
}
};
int main() {
// 基类指针指向派生类对象
Shape* shape1 = new Circle(5.0);
Shape* shape2 = new Rectangle(4.0, 6.0);
cout << "圆形面积:" << shape1->getArea() << endl;
cout << "矩形面积:" << shape2->getArea() << endl;
delete shape1;
delete shape2;
return 0;
}上述代码中,Circle和Rectangle都重写了Shape类的getArea方法,当通过Shape类型的指针调用getArea时,程序会自动根据实际指向的对象类型调用对应的实现,这就是运行时多态的体现。代码中使用了override关键字,这是C++11引入的特性,可以让编译器帮助检查重写是否合法,避免因为函数签名不一致导致的隐藏问题。
进阶应用场景
在实际开发中,函数重写常用于框架扩展、接口统一等场景,比如日志系统的不同输出方式实现:
#include <iostream>
#include <string>
using namespace std;
// 日志基类
class Logger {
public:
virtual void log(const string& message) {
cout << "默认日志:" << message << endl;
}
virtual ~Logger() {}
};
// 控制台日志派生类
class ConsoleLogger : public Logger {
public:
void log(const string& message) override {
cout << "[控制台]" << message << endl;
}
};
// 文件日志派生类(模拟)
class FileLogger : public Logger {
public:
void log(const string& message) override {
// 实际场景中会写入文件,这里仅做输出模拟
cout << "[文件]" << message << endl;
}
};
int main() {
Logger* logger;
ConsoleLogger consoleLogger;
FileLogger fileLogger;
logger = &consoleLogger;
logger->log("这是一条测试日志");
logger = &fileLogger;
logger->log("这是另一条测试日志");
return 0;
}这种写法让日志模块的核心调用逻辑不需要关心具体的日志输出方式,后续如果需要新增网络日志、数据库日志等类型,只需要新增派生类重写log方法即可,不需要修改原有调用代码,符合开闭原则。
常见注意事项
在使用函数重写时,需要注意避开以下几个常见误区:
- 不要忘记在基类函数中加
virtual关键字,否则派生类的同名函数会隐藏基类函数,无法实现多态。 - 重写函数的参数列表必须完全一致,如果参数不同,即使函数名相同也不是重写,而是函数隐藏。
- 基类的析构函数建议声明为虚函数,否则当用基类指针删除派生类对象时,可能导致派生类的析构函数没有被调用,出现内存泄漏。
- 避免在构造函数和析构函数中调用虚函数,此时多态机制不会生效,调用的是当前类版本的函数。
| 对比项 | 函数重写 | 函数重载 |
|---|---|---|
| 作用范围 | 基类和派生类之间 | 同一作用域内 |
| 函数签名要求 | 必须完全一致 | 函数名相同,参数列表不同 |
| 多态类型 | 运行时多态 | 编译时多态 |
| 是否需要virtual | 基类函数需要 | 不需要 |
掌握函数重写的使用方法,能够帮助开发者更好地利用C++的面向对象特性,写出扩展性更强、可维护性更高的代码,这也是很多C++项目开发中的必备技能。