一、概念 C++ 的多态机制主要通过三个核心概念实现,它们在编译和运行时有着截然不同的处理方式:
概念
定义
绑定时机
核心特征
函数重载
同一作用域内,函数名相同但参数列表不同的函数
编译时
静态多态,基于参数列表区分
函数重写
派生类中重新定义基类中的虚函数,函数签名完全相同
运行时
动态多态,基于对象实际类型调用
函数隐藏
派生类中定义的函数遮蔽基类中同名函数,无论参数是否相同
编译时
名称遮蔽,基类函数被隐藏
注:函数签名包括函数名、参数类型和顺序,不包括返回值类型
二、函数重载解析 函数重载是 C++ 实现静态多态的基础机制,允许在同一作用域内定义多个同名函数,通过参数列表的差异进行区分。
重载的实现原理 编译器在编译阶段会对重载函数进行名称修饰(Name Mangling),根据函数名和参数列表生成唯一的内部名称,因此重载函数在底层实际上拥有不同的标识符。
重载示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <string> class Calculator { public: // 整数加法 int add(int a, int b) { std::cout << "int add: "; return a + b; } // 双精度浮点数加法(参数类型不同) double add(double a, double b) { std::cout << "double add: "; return a + b; } // 三个整数加法(参数数量不同) int add(int a, int b, int c) { std::cout << "int add 3 params: "; return a + b + c; } // 字符串拼接(参数类型不同) std::string add(std::string a, std::string b) { std::cout << "string add: "; return a + b; } }; int main() { Calculator calc; std::cout << calc.add(2, 3) << std::endl; // 调用int版本 std::cout << calc.add(2.5, 3.7) << std::endl; // 调用double版本 std::cout << calc.add(1, 2, 3) << std::endl; // 调用3参数版本 std::cout << calc.add("Hello, ", "World!") << std::endl; // 调用string版本 return 0; }
执行结果 1 2 3 4 int add: 5 double add: 6.2 int add 3 params: 6 string add: Hello, World!
重载的规则与限制
必须满足 :参数个数、类型或顺序至少有一个不同
不能仅通过 返回值类型不同来重载函数
作用域限制 :重载函数必须在同一作用域内
三、函数重写(覆盖)解析 函数重写是实现动态多态的核心机制,允许派生类重新实现基类中声明的虚函数。
重写的实现原理 C++ 通过虚函数表(vtable)和虚表指针(vptr)实现重写机制:
重写示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 #include <iostream> // 基类 class Shape { public: // 虚函数,可被重写 virtual void draw() const { std::cout << "绘制基本形状" << std::endl; } // 纯虚函数,必须被派生类重写 virtual double area() const = 0; // 虚析构函数,确保正确析构派生类对象 virtual ~Shape() { std::cout << "Shape析构函数" << std::endl; } }; // 派生类Circle class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} // 重写基类的draw函数 void draw() const override { // 使用override关键字明确表示重写 std::cout << "绘制圆形" << std::endl; } // 重写基类的area函数 double area() const override { return 3.14159 * radius * radius; } ~Circle() override { std::cout << "Circle析构函数" << std::endl; } }; // 派生类Rectangle class Rectangle : public Shape { private: double width; double height; public: Rectangle(double w, double h) : width(w), height(h) {} // 重写基类的draw函数 void draw() const override { std::cout << "绘制矩形" << std::endl; } // 重写基类的area函数 double area() const override { return width * height; } ~Rectangle() override { std::cout << "Rectangle析构函数" << std::endl; } }; int main() { // 基类指针指向派生类对象 Shape* shape1 = new Circle(5.0); Shape* shape2 = new Rectangle(4.0, 6.0); // 动态绑定:调用的是对象实际类型的函数 shape1->draw(); // 输出"绘制圆形" std::cout << "面积: " << shape1->area() << std::endl; shape2->draw(); // 输出"绘制矩形" std::cout << "面积: " << shape2->area() << std::endl; delete shape1; delete shape2; return 0; }
执行结果 1 2 3 4 5 6 7 8 绘制圆形 面积: 78.5397 绘制矩形 面积: 24 Circle析构函数 Shape析构函数 Rectangle析构函数 Shape析构函数
重写的规则与限制
基类函数必须声明为virtual
派生类函数必须与基类函数有完全相同的函数签名 (名称、参数列表)
派生类函数的返回值类型必须与基类函数相同,或为协变返回类型
C++11 引入override关键字,显式指明函数是重写基类虚函数,增强代码可读性并让编译器检查是否符合重写规则
四、函数隐藏解析 函数隐藏指派生类中的函数遮蔽了基类中同名函数,无论它们的参数列表是否相同。这是一种名称查找机制导致的现象。
隐藏的实现原理 编译器在查找函数名称时,会先在当前类的作用域中查找,如果找到匹配的名称,则不会继续在基类中查找,从而导致基类中的同名函数被隐藏。
隐藏示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <iostream> class Base { public: void print(int x) { std::cout << "Base::print(int): " << x << std::endl; } void show() { std::cout << "Base::show()" << std::endl; } }; class Derived : public Base { public: // 情况1:函数名相同,参数不同 - 隐藏基类的print(int) void print(double x) { std::cout << "Derived::print(double): " << x << std::endl; } // 情况2:函数名相同,参数相同 - 隐藏基类的show() void show() { std::cout << "Derived::show()" << std::endl; } }; int main() { Derived d; // 调用Derived的print(double) d.print(3.14); // 正确 // 尝试调用Base的print(int),但被隐藏 // d.print(10); // 编译错误:无法将int转换为double // 必须显式指定作用域才能调用基类被隐藏的函数 d.Base::print(10); // 正确 // 调用Derived的show() d.show(); // 正确 // 显式调用Base的show() d.Base::show(); // 正确 // 基类指针指向派生类对象 Base* b = &d; b->print(20); // 调用Base的print(int),因为非虚函数,静态绑定 b->show(); // 调用Base的show(),因为非虚函数,静态绑定 return 0; }
执行结果 1 2 3 4 5 6 Derived::print(double): 3.14 Base::print(int): 10 Derived::show() Base::show() Base::print(int): 20 Base::show()
隐藏的规则与限制
只要派生类中定义的函数与基类中的函数同名,无论参数是否相同,基类函数都会被隐藏
通过派生类对象直接调用同名函数时,只会调用派生类中的版本
要访问基类中被隐藏的函数,必须使用作用域解析运算符::
对于非虚函数,即使通过基类指针调用,也只会根据指针类型(静态类型)调用相应类的函数
五、三者的核心区别对比
特性
函数重载
函数重写
函数隐藏
作用域
同一类中
基类与派生类之间
基类与派生类之间
函数名
相同
相同
相同
参数列表
不同
必须相同
可以相同或不同
基类函数要求
无特殊要求
必须是虚函数
无特殊要求
绑定方式
静态绑定(编译时)
动态绑定(运行时)
静态绑定(编译时)
调用依据
函数参数列表
对象实际类型
指针 / 引用的静态类型
关键字
无
override(C++11)
无
实现机制
名称修饰
虚函数表
名称查找规则
六、实际开发中的应用建议 函数重载的最佳实践
一致性原则 :重载函数应实现相似或相关的功能
1 2 3 4 // 推荐:功能相似的重载 void print(int x); void print(double x); void print(const std::string& s);
避免过度重载 :过多的重载版本会降低代码可读性
优先使用重载而非默认参数 :当参数组合复杂时,重载更清晰
函数重写的最佳实践
始终使用override 关键字 :明确表示重写意图,让编译器帮助检查错误
1 2 3 4 5 // 推荐 void draw() const override; // 不推荐 void draw() const; // 无法确定是重写还是新函数
基类析构函数应声明为虚函数 :确保删除基类指针时能正确调用派生类析构函数
保持函数签名完全一致 :包括const修饰符等细节
函数隐藏的注意事项
避免无意识的隐藏 :派生类中定义与基类同名的函数时要格外小心
明确指定作用域 :当需要调用基类中被隐藏的函数时,使用Base::function()
区分隐藏与重写 :如果希望实现多态,应使用虚函数重写而非隐藏
总结 C++ 的重载、重写和隐藏机制是实现代码复用和多态的重要工具,它们各自有明确的应用场景和行为特征:
重载 用于在同一类中实现功能相似但参数不同的操作,提供编译时多态
重写 用于在继承体系中实现动态多态,使派生类可以自定义基类的行为
隐藏 是名称查找机制的自然结果,需谨慎使用以避免意外行为