详细介绍 C++ 中的函数重载(Overload)和函数重写(Override),并附上代码示例。
1. 函数重载 (Overload)
- 定义: 在同一个作用域(例如,同一个类或同一个命名空间)中,可以定义多个名称相同但参数列表不同的函数。这些函数被称为重载函数。
- 参数列表不同: 参数列表的不同体现在以下几个方面:
- 参数的个数不同。
- 参数的类型不同。
- 参数的顺序不同(如果类型也不同)。
- 返回类型: 函数的返回类型与函数重载无关。也就是说,仅仅返回类型不同,不能构成函数重载。
- 编译器如何区分: 编译器在编译时会根据函数调用时提供的实参的类型和数量,来选择匹配的重载函数。这个过程被称为重载决议(Overload Resolution)。
- 目的: 函数重载的主要目的是提高代码的可读性和可复用性。通过使用相同的函数名来处理不同的数据类型或不同数量的参数,可以使代码更简洁、更易于理解。
代码示例:
#include <iostream>
class MyClass {
public:
// 重载函数 print
void print(int x) {
std::cout << "Printing integer: " << x << std::endl;
}
void print(double x) {
std::cout << "Printing double: " << x << std::endl;
}
void print(int x, int y) {
std::cout << "Printing two integers: " << x << ", " << y << std::endl;
}
//以下不是有效的重载,仅仅返回类型不同不能构成重载
//int print(int x){
// return x;
//}
};
int main() {
MyClass obj;
obj.print(10); // 调用 print(int)
obj.print(3.14); // 调用 print(double)
obj.print(5, 20); // 调用 print(int, int)
return 0;
}
输出:
Printing integer: 10
Printing double: 3.14
Printing two integers: 5, 20
2. 函数重写 (Override)
- 定义: 函数重写(也称为覆盖)发生在继承关系中。当派生类(子类)定义了一个与其基类(父类)中虚函数具有相同名称、相同参数列表和相同返回类型(或协变返回类型)的函数时,就说派生类的函数重写了基类的虚函数。
- 虚函数 (Virtual Function): 在基类中,需要被重写的函数必须声明为虚函数。这是通过在函数声明前加上
virtual
关键字来实现的。 - 目的: 函数重写的目的是实现多态性(Polymorphism)。多态性允许通过基类指针或引用来调用派生类对象的函数,并在运行时动态地确定要调用的实际函数(基类版本还是派生类版本)。
- 运行时绑定 (Dynamic Binding): 当通过基类指针或引用调用虚函数时,C++ 使用运行时绑定(也称为后期绑定或动态分派)来确定要调用的函数。这意味着在程序运行时,而不是编译时,才确定要调用的函数。
override
关键字 (C++11): C++11 引入了override
关键字,它可以显式地声明一个函数是重写基类虚函数的。这有助于编译器检查是否真的重写了虚函数,避免潜在的错误(例如,参数列表不匹配)。- 协变返回类型: 从C++11起,允许基类虚函数返回基类指针(引用),派生类重写函数返回派生类指针(引用)。
代码示例:
#include <iostream>
class Animal {
public:
virtual void makeSound() { // 虚函数
std::cout << "Generic animal sound" << std::endl;
}
virtual Animal* clone() {
return new Animal(*this); // 协变返回类型的基类版本
}
};
class Dog : public Animal {
public:
void makeSound() override { // 重写基类的 makeSound 函数
std::cout << "Woof!" << std::endl;
}
Dog* clone() override {
return new Dog(*this); // 协变返回类型的派生类版本
}
};
class Cat : public Animal {
public:
void makeSound() override {
std::cout << "Meow!" << std::endl;
}
};
int main() {
Animal* animal1 = new Animal();
Animal* animal2 = new Dog(); // 基类指针指向派生类对象
Animal* animal3 = new Cat();
animal1->makeSound(); // 输出 "Generic animal sound"
animal2->makeSound(); // 输出 "Woof!" (动态绑定)
animal3->makeSound(); // 输出 "Meow!"
Animal* clonedAnimal = animal2->clone(); // 返回的是Dog*,但是可以赋值给Animal*
clonedAnimal->makeSound(); //输出"Woof!", 因为clonedAnimal实际是Dog*
delete animal1;
delete animal2;
delete animal3;
delete clonedAnimal;
return 0;
}
输出:
Generic animal sound
Woof!
Meow!
Woof!
总结
特性 | 函数重载 (Overload) | 函数重写 (Override) |
---|---|---|
作用域 | 同一个作用域 | 继承关系(基类和派生类) |
函数名 | 相同 | 相同 |
参数列表 | 不同(个数、类型、顺序) | 相同 |
返回类型 | 与重载无关 | 相同(或协变返回类型) |
关键字 | 无特殊关键字 | virtual (基类), override (派生类, C++11) |
目的 | 提高代码可读性和可复用性 | 实现多态性,运行时动态绑定 |
绑定方式 | 编译时绑定(静态绑定) | 运行时绑定(动态绑定、后期绑定) |
发生位置 | 编译期 | 运行期 |
关键区别:
- 函数重载发生在同一个作用域内,通过不同的参数列表来区分不同的函数。
- 函数重写发生在继承关系中,通过
virtual
和override
关键字来实现多态性,允许在运行时动态地确定要调用的函数。