C++ OOP 面试高频知识点 - 06
函数重载 overload、运算符重载
1. 函数重载(Overload)
函数重载是指在同一个作用域内,存在多个同名函数,但它们的参数列表不同(参数类型、参数个数、参数顺序不同)。
#include <iostream>
#include <string>
using namespace std;
// 函数重载
void print(int value) {
cout << "Integer: " << value << endl;
}
void print(double value) {
cout << "Double: " << value << endl;
}
void print(const string& value) {
cout << "String: " << value << endl;
}
int main() {
print(10); // 调用 print(int)
print(3.14); // 调用 print(double)
print("Hello"); // 调用 print(const string&)
return 0;
}
2. 函数重载的条件
要实现函数重载,必须满足以下条件: 1. 函数名称相同 2. 参数列表不同(参数类型、参数个数、参数顺序不同) 3. 位于同一个作用域内
3. 函数重载的注意事项
3.1 返回类型不影响重载
函数返回类型不影响重载,两个函数如果只有返回类型不同,是无法重载的:
int add(int a, int b) { return a + b; }
double add(int a, int b) { return a + b; } // 错误:无法重载,只有返回类型不同
3.2 const 修饰符影响重载
当函数参数是指针或引用时,const 修饰符会影响重载:
void print(const int* ptr) {
cout << "Constant pointer: " << *ptr << endl;
}
void print(int* ptr) {
cout << "Non-constant pointer: " << *ptr << endl;
}
void print(const int& ref) {
cout << "Constant reference: " << ref << endl;
}
void print(int& ref) {
cout << "Non-constant reference: " << ref << endl;
}
3.3 重载的调用决策
编译器根据参数匹配的最佳匹配原则选择调用哪个重载函数:
void foo(int x) { cout << "foo(int)" << endl; }
void foo(long x) { cout << "foo(long)" << endl; }
int main() {
int a = 10;
foo(a); // 调用 foo(int) - 精确匹配
long b = 20;
foo(b); // 调用 foo(long) - 精确匹配
foo(3.14); // 调用 foo(int) - 隐式转换
return 0;
}
4. 运算符重载(Operator Overloading)
运算符重载是指重新定义运算符的行为,使其能够操作自定义类型。
4.1 成员函数形式
class Complex {
public:
Complex(double real = 0, double imag = 0)
: real(real), imag(imag) {}
// 重载加法运算符(成员函数)
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
// 重载减法运算符(成员函数)
Complex operator-(const Complex& other) const {
return Complex(real - other.real, imag - other.imag);
}
// 重载输出运算符(通常使用友元函数)
friend ostream& operator<<(ostream& os, const Complex& c) {
os << c.real << " + " << c.imag << "i";
return os;
}
private:
double real, imag;
};
int main() {
Complex a(1, 2), b(3, 4);
Complex c = a + b; // 调用 operator+ 成员函数
cout << "a + b = " << c << endl;
Complex d = a - b; // 调用 operator- 成员函数
cout << "a - b = " << d << endl;
return 0;
}
4.2 非成员函数形式
class Complex {
public:
Complex(double real = 0, double imag = 0)
: real(real), imag(imag) {}
double getReal() const { return real; }
double getImag() const { return imag; }
private:
double real, imag;
};
// 重载加法运算符(非成员函数)
Complex operator+(const Complex& a, const Complex& b) {
return Complex(a.getReal() + b.getReal(),
a.getImag() + b.getImag());
}
// 重载乘法运算符(非成员函数)
Complex operator*(const Complex& a, const Complex& b) {
double real = a.getReal() * b.getReal() - a.getImag() * b.getImag();
double imag = a.getReal() * b.getImag() + a.getImag() * b.getReal();
return Complex(real, imag);
}
4.3 可重载和不可重载的运算符
可重载的运算符:
- 算术运算符:+, -, *, /, % 等
- 关系运算符:==, !=, <, >, <=, >= 等
- 逻辑运算符:&&, ||, ! 等
- 赋值运算符:=, +=, -=, *=, /= 等
- 其他:[], (), ->, ++, --, new, delete 等
不可重载的运算符:
- . 成员访问运算符
- .* 指针到成员访问运算符
- :: 作用域解析运算符
- sizeof 大小运算符
- ?: 条件运算符
5. 常见问题和回答
问题 1:重载和重写的区别?
- 重载(Overload):在同一个作用域内,函数名称相同但参数列表不同
- 重写(Override):在继承关系中,子类重新实现父类的方法,方法签名(名称、参数列表、返回类型)相同
问题 2:为什么要重载运算符?
运算符重载可以使自定义类型的操作更直观,类似于内置类型,提高代码可读性。
// 不使用运算符重载
Complex c = add(a, b);
// 使用运算符重载
Complex c = a + b;
问题 3:如何决定使用成员函数还是非成员函数重载运算符?
- 如果运算符操作的是对象内部状态,通常使用成员函数
- 如果需要支持交换操作(如
a + b和b + a),通常使用非成员函数 - 赋值运算符
=、下标运算符[]、函数调用运算符()等通常使用成员函数
问题 4:可以重载所有运算符吗?
不可以,有些运算符不能被重载,如 .、.*、::、sizeof、?: 等。
6. 最佳实践
- 保持语义一致性:重载的运算符应该具有直观的语义
- 避免过度重载:不要滥用运算符重载
- 使用适当的形式:根据需要选择成员函数或非成员函数形式
- 注意运算符的优先级:重载不会改变运算符的优先级和结合性
总结
函数重载和运算符重载是 C++ 中非常重要的特性,它们提供了代码复用和语法糖,使代码更简洁和易于阅读。函数重载通过参数列表的不同来实现,而运算符重载允许自定义类型的操作更直观。
练习建议:
1. 实现一个 Rational 类,表示有理数
2. 重载加减乘除运算符
3. 重载比较运算符
4. 实现流输出运算符