重写
-
概念:
重写发生在类的继承体系中,是指在派生类中重新定义基类中已声明为虚函数(使用virtual
关键字修饰)的函数。其目的是让派生类根据自身的需求对基类的虚函数提供不同的具体实现,从而实现运行时多态。 -
规则及特点:
- 函数签名必须匹配:函数名、参数列表(参数个数、类型、顺序)、
const
修饰符等都要完全一致。例如,如果基类虚函数是void func(int a)
,派生类中重写的函数也必须是void func(int a)
形式。不过返回值类型存在一种特殊情况叫协变返回类型,即当基类虚函数返回基类指针或引用时,派生类重写的虚函数可以返回派生类对应的指针或引用(例如基类虚函数返回Base*
,派生类重写函数返回Derived*
,这里Derived
是Base
的派生类)。 - 访问权限限定符:派生类中重写的函数的访问权限可以和基类虚函数的访问权限相同或者更宽松(例如基类虚函数是
protected
,派生类重写函数可以是public
或者protected
),但不能更严格(不能将基类中public
的虚函数在派生类重写为private
)。 - 必须是虚函数重写:基类中被重写的函数必须声明为虚函数,这样编译器才会在运行时根据对象的实际类型进行动态绑定,来决定调用基类的还是派生类的该函数版本。
- 函数特性要一致:比如
volatile
、virtual
等函数特性也应保持对应(通常重写函数也保持virtual
特性,不过在派生类中virtual
关键字可写可不写,只要基类中是虚函数,它依然是重写行为)。
- 函数签名必须匹配:函数名、参数列表(参数个数、类型、顺序)、
-
示例代码:
#include <iostream>
using namespace std;class Base {
public:virtual void show() {cout << "Base class show function" << endl;}
};class Derived : public Base {
public:void show() override { // 使用override关键字,便于编译器检查是否正确重写,若不符合重写规则会报错cout << "Derived class show function" << endl;}
};int main() {Base* ptr;Base b;Derived d;ptr = &b;ptr->show(); // 调用Base类的show函数ptr = &d;ptr->show(); // 调用Derived类的show函数,体现运行时多态,根据对象实际类型决定调用派生类重写后的show函数return 0;
}
重定义
-
概念:
重定义通常也叫隐藏,是指在派生类中重新定义了与基类中同名的非虚函数(普通函数),或者改变了基类中同名函数(无论是否虚函数)的参数列表等情况,使得派生类中的同名函数将基类中的同名函数隐藏了起来。这并非是为了实现多态,而是在派生类中重新定义了一个和基类有同名情况的函数而已。 -
规则及特点:
- 只要同名就可能隐藏:只要派生类中有和基类同名的函数(无论参数是否相同),就会发生隐藏现象。如果想在派生类中访问被隐藏的基类同名函数,可以通过使用基类名加作用域限定符(
::
)来访问,例如Base::func()
。 - 参数不同也隐藏:哪怕派生类中同名函数的参数和基类中对应函数的参数不一样(与函数重载不同,函数重载要求在同一作用域内参数不同才构成重载),依然会隐藏基类的同名函数,导致通过派生类对象或指针(在某些情况)调用该同名函数时,优先调用派生类自己定义的这个函数,而不是基类中的同名函数。
- 只要同名就可能隐藏:只要派生类中有和基类同名的函数(无论参数是否相同),就会发生隐藏现象。如果想在派生类中访问被隐藏的基类同名函数,可以通过使用基类名加作用域限定符(
#include <iostream>
using namespace std;class Base {
public:void func() {cout << "Base class func function" << endl;}
};class Derived : public Base {
public:void func(int a) { // 和基类中的func函数同名,但参数不同,发生重定义(隐藏)cout << "Derived class func function with parameter: " << a << endl;}
};int main() {Derived d;d.func(5); // 调用派生类自己定义的func(int a)函数// 若想调用基类中被隐藏的func函数,需要这样做d.Base::func(); return 0;
}
重载
-
概念与目的:
重载是指在同一个作用域内(比如同一个类中),定义多个同名函数,但是这些函数的参数列表(参数个数、参数类型、参数顺序)不同。编译器会根据调用函数时实际传入的参数情况,在编译阶段就确定具体要调用的是哪个重载版本的函数,以此实现编译时多态,方便程序员针对不同的参数情况提供不同的处理逻辑。 -
规则与特点:
- 参数列表差异是关键:函数名相同,但参数列表必须有区别。返回值类型不同不能作为函数重载的依据(例如不能仅因为一个函数返回
int
,另一个返回double
,就认为它们是重载关系,编译器会报错)。例如:
- 参数列表差异是关键:函数名相同,但参数列表必须有区别。返回值类型不同不能作为函数重载的依据(例如不能仅因为一个函数返回
class Calculator {
public:int add(int a, int b) {return a + b;}double add(double a, double b) {return a + b;}
};
-
作用域限制:重载函数必须在同一个作用域内定义,常见于同一个类中定义多个同名但参数不同的方法,当然也可以在全局作用域下有符合重载规则的同名函数(不过要避免一些命名冲突等问题)。
-
示例代码:
#include <iostream>
using namespace std;class MathUtils {
public:int multiply(int num1, int num2) {return num1 * num2;}int multiply(int num1, int num2, int num3) {return num1 * num2 * num3;}
};int main() {MathUtils utils;cout << utils.multiply(2, 3) << endl; // 调用有两个参数的multiply函数cout << utils.multiply(2, 3, 4) << endl; // 调用有三个参数的multiply函数return 0;
}
不同点
- 从实现多态角度:
- 重写:用于实现运行时多态,依赖于虚函数和继承机制,根据对象实际类型在运行时动态决定调用哪个类中的函数版本。
- 重定义:并非为了实现多态,只是派生类对同名函数重新定义,导致隐藏了基类的同名函数,调用时默认优先调用派生类自己定义的函数,如需调用基类被隐藏函数要用作用域限定符。
- 重载:实现编译时多态,编译器根据函数调用时传入的参数情况在编译阶段就确定具体调用哪个重载版本的函数,和对象实际类型无关。
- 从函数签名要求角度:
- 重写:函数名、参数列表(除协变返回类型等特殊情况)等要严格匹配,且基类函数必须是虚函数。
- 重定义:只要派生类和基类有同名函数就可能发生隐藏,对函数签名是否一致没有像重写那样严格要求与多态关联起来,即便参数不同等也会隐藏基类同名函数。
- 重载:函数名相同,重点在于参数列表必须有差异,返回值类型不同不能单独作为重载依据。
- 从调用时机和绑定方式角度:
- 重写:在运行时基于对象实际类型通过动态绑定来决定调用版本,常见于通过基类指针或引用指向派生类对象后调用虚函数的场景。
- 重定义:在派生类对象调用同名函数时,默认优先调用派生类自身重定义的函数,是一种静态的隐藏关系,若要访问基类被隐藏函数需额外用作用域限定符操作。
- 重载:在编译阶段,编译器根据传入的实际参数情况进行静态绑定,确定具体调用的重载版本,调用时机在编译完成后的程序运行过程中按编译确定的逻辑执行。