目录
[1. 重载 (Function Overloading)](#1. 重载 (Function Overloading))
[2. 重写/覆盖 (Function Overriding)](#2. 重写/覆盖 (Function Overriding))
[C++11 override关键字](#C++11 override关键字)
[3. 隐藏/重定义 (Function Hiding)](#3. 隐藏/重定义 (Function Hiding))
[1. 参数相同但非虚函数](#1. 参数相同但非虚函数)
[2. 虚函数但不构成重写](#2. 虚函数但不构成重写)
[1. 选择题:以下代码输出什么?](#1. 选择题:以下代码输出什么?)
[2. 选择题:以下代码输出什么?](#2. 选择题:以下代码输出什么?)
[3. 综合题](#3. 综合题)
一、概念对比总览
| 特性 | 重载 (Overload) | 重写/覆盖 (Override) | 隐藏/重定义 (Hide) |
|---|---|---|---|
| 作用域 | 同一作用域 | 不同作用域(继承关系) | 不同作用域(继承关系) |
| 函数名 | 相同 | 相同 | 相同 |
| 参数列表 | 必须不同 | 必须相同 | 可以不同 |
| 返回类型 | 可以不同 | 必须相同(协变除外) | 可以不同 |
| virtual关键字 | 不需要 | 基类必须有virtual | 不需要 |
| 访问权限 | 可以不同 | 可以不同 | 可以不同 |
| 多态性 | 编译时多态 | 运行时多态 | 无多态 |
二、详细解析
1. 重载 (Function Overloading)
定义
在同一作用域内,函数名相同但参数列表不同的多个函数。
特点
-
发生在同一类中或全局作用域
-
根据参数类型、个数、顺序不同来区分
-
编译器根据调用时的实参选择匹配的函数
-
属于编译时多态
示例代码
cpp
#include <iostream>
using namespace std;
class Calculator {
public:
// 重载add函数
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
// 参数类型顺序不同也可以重载
void print(int a, double b) {
cout << "int, double: " << a << ", " << b << endl;
}
void print(double a, int b) {
cout << "double, int: " << a << ", " << b << endl;
}
};
// 全局函数重载
void show(int x) {
cout << "int: " << x << endl;
}
void show(double x) {
cout << "double: " << x << endl;
}
int main() {
Calculator calc;
cout << calc.add(1, 2) << endl; // 调用int版本
cout << calc.add(1.5, 2.5) << endl; // 调用double版本
cout << calc.add(1, 2, 3) << endl; // 调用三参数版本
calc.print(1, 2.5); // 调用(int, double)版本
calc.print(1.5, 2); // 调用(double, int)版本
show(10); // 调用int版本
show(3.14); // 调用double版本
return 0;
}
重要规则
-
不能仅通过返回类型不同来重载
cpp
// 错误:仅返回类型不同 int func() { return 0; } double func() { return 0.0; } // 编译错误 -
const成员函数和非const成员函数可以重载
cpp
class Test { public: void display() { cout << "non-const" << endl; } void display() const { cout << "const" << endl; } // 可以重载 };
2. 重写/覆盖 (Function Overriding)
定义
派生类中重新定义基类的虚函数,要求函数签名完全相同。
特点
-
发生在继承关系中
-
基类函数必须声明为
virtual -
实现运行时多态
-
通过基类指针/引用调用时,根据实际对象类型决定调用哪个函数
示例代码
cpp
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() { // 虚函数
cout << "动物叫" << endl;
}
virtual void eat() {
cout << "动物吃东西" << endl;
}
void sleep() { // 非虚函数
cout << "动物睡觉" << endl;
}
};
class Dog : public Animal {
public:
virtual void speak() override { // 重写基类虚函数
cout << "汪汪汪" << endl;
}
virtual void eat() override {
cout << "狗吃骨头" << endl;
}
void sleep() { // 隐藏基类函数,不是重写
cout << "狗趴着睡" << endl;
}
};
class Cat : public Animal {
public:
virtual void speak() override {
cout << "喵喵喵" << endl;
}
};
void testAnimal(Animal& animal) {
animal.speak(); // 动态绑定:根据实际对象类型调用
animal.eat(); // 动态绑定
animal.sleep(); // 静态绑定:总是调用Animal::sleep()
}
int main() {
Dog dog;
Cat cat;
cout << "=== 通过对象调用 ===" << endl;
dog.speak(); // 输出:汪汪汪
dog.sleep(); // 输出:狗趴着睡
cout << "\n=== 通过基类引用调用(多态) ===" << endl;
testAnimal(dog);
// speak(): 汪汪汪 (动态绑定)
// eat(): 狗吃骨头 (动态绑定)
// sleep(): 动物睡觉 (静态绑定)
testAnimal(cat);
// speak(): 喵喵喵 (动态绑定)
// eat(): 动物吃东西 (动态绑定)
// sleep(): 动物睡觉 (静态绑定)
cout << "\n=== 通过基类指针调用 ===" << endl;
Animal* ptr = &dog;
ptr->speak(); // 输出:汪汪汪 (动态绑定)
ptr->sleep(); // 输出:动物睡觉 (静态绑定)
return 0;
}
C++11 override关键字
cpp
class Base {
public:
virtual void func(int) {}
};
class Derived : public Base {
public:
virtual void func(int) override {} // 正确:明确表示重写
// virtual void func(double) override {} // 错误:不是重写,编译报错
};
3. 隐藏/重定义 (Function Hiding)
定义
派生类定义了与基类同名的函数,无论参数是否相同,都会隐藏基类的同名函数。
特点
-
发生在继承关系中
-
基类函数被隐藏,无法通过派生类对象直接访问
-
可以使用作用域解析运算符
::显式调用基类函数 -
无多态性,根据指针/引用的静态类型决定调用哪个函数
示例代码
cpp
#include <iostream>
using namespace std;
class Base {
public:
void display() {
cout << "Base::display()" << endl;
}
void display(int x) {
cout << "Base::display(int): " << x << endl;
}
void show() {
cout << "Base::show()" << endl;
}
};
class Derived : public Base {
public:
// 隐藏了基类的所有display函数
void display() {
cout << "Derived::display()" << endl;
}
// 新增函数,与基类show参数不同
void show(int x) { // 隐藏Base::show()
cout << "Derived::show(int): " << x << endl;
}
};
int main() {
Derived d;
d.display(); // 调用Derived::display()
// d.display(10); // 错误:Base::display(int)被隐藏
d.show(5); // 调用Derived::show(int)
// d.show(); // 错误:Base::show()被隐藏
// 使用作用域解析运算符访问被隐藏的函数
d.Base::display(); // 调用Base::display()
d.Base::display(10); // 调用Base::display(int)
d.Base::show(); // 调用Base::show()
// 通过基类指针/引用
Base* ptr = &d;
ptr->display(); // 调用Base::display()(静态绑定)
ptr->display(20); // 调用Base::display(int)
ptr->show(); // 调用Base::show()
return 0;
}
三、对比分析
1. 参数相同但非虚函数
cpp
class Base {
public:
void func() { cout << "Base" << endl; } // 非虚函数
};
class Derived : public Base {
public:
void func() { cout << "Derived" << endl; } // 隐藏,不是重写
};
int main() {
Derived d;
Base* ptr = &d;
d.func(); // 输出:Derived
ptr->func(); // 输出:Base(静态绑定,因为不是虚函数)
return 0;
}
2. 虚函数但不构成重写
cpp
class Base {
public:
virtual void func(int) { cout << "Base::func(int)" << endl; }
};
class Derived : public Base {
public:
virtual void func(double) { cout << "Derived::func(double)" << endl; }
// 这是新虚函数,不是重写Base::func(int)
// Base::func(int)被隐藏
};
int main() {
Derived d;
Base* ptr = &d;
d.func(10.5); // 调用Derived::func(double)
// d.func(10); // 错误:Base::func(int)被隐藏
ptr->func(10); // 调用Base::func(int)
return 0;
}
四、记忆技巧与面试考点
记忆口诀
-
重载:同域同名不同参
-
重写:异域同名同参需虚函
-
隐藏:异域同名就隐藏,虚不虚函都无妨
常见面试题
1. 选择题:以下代码输出什么?
cpp
class A {
public:
virtual void f() { cout << "A"; }
};
class B : public A {
public:
void f() { cout << "B"; }
};
void test(A& a) {
a.f();
}
int main() {
B b;
test(b);
return 0;
}
答案:B(动态绑定)
2. 选择题:以下代码输出什么?
cpp
class A {
public:
void f() { cout << "A"; }
};
class B : public A {
public:
void f() { cout << "B"; }
};
void test(A& a) {
a.f();
}
int main() {
B b;
test(b);
return 0;
}
答案:A(静态绑定,隐藏)
3. 综合题
cpp
class Base {
public:
virtual void vfunc() { cout << "Base vfunc" << endl; }
void func() { cout << "Base func" << endl; }
};
class Derived : public Base {
public:
virtual void vfunc() override { cout << "Derived vfunc" << endl; }
void func() { cout << "Derived func" << endl; }
void func(int) { cout << "Derived func(int)" << endl; }
};
int main() {
Derived d;
Base* p = &d;
p->vfunc(); // 输出:Derived vfunc(重写,动态绑定)
p->func(); // 输出:Base func(隐藏,静态绑定)
d.func(); // 输出:Derived func
d.func(10); // 输出:Derived func(int)
// p->func(10); // 错误:Base没有func(int)
return 0;
}
五、最佳实践建议
-
使用override关键字
-
C++11开始,在派生类重写虚函数时使用
override -
让编译器检查是否正确重写,避免隐藏错误
-
-
虚析构函数
-
多态基类应该声明虚析构函数
-
确保通过基类指针删除派生类对象时正确调用析构函数
-
-
避免函数隐藏
- 使用
using声明引入基类函数
cpp
class Derived : public Base { public: using Base::display; // 引入Base的所有display函数 void display() { /* ... */ } }; - 使用
-
明确设计意图
-
需要多态时使用虚函数重写
-
只是提供不同实现时考虑函数重载
-
避免无意中的函数隐藏
-
总结
理解重载、重写和隐藏的区别对于掌握C++面向对象编程至关重要:
-
重载提供同一功能的不同接口(编译时决定)
-
重写实现运行时多态,是继承体系的核心
-
隐藏往往是无意中造成的错误,需要特别注意
正确使用这些特性可以让代码更清晰、更健壮,同时充分利用C++的多态能力。