Skip to content

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 + bb + a),通常使用非成员函数
  • 赋值运算符 =、下标运算符 []、函数调用运算符 () 等通常使用成员函数

问题 4:可以重载所有运算符吗?

不可以,有些运算符不能被重载,如 ..*::sizeof?: 等。

6. 最佳实践

  1. 保持语义一致性:重载的运算符应该具有直观的语义
  2. 避免过度重载:不要滥用运算符重载
  3. 使用适当的形式:根据需要选择成员函数或非成员函数形式
  4. 注意运算符的优先级:重载不会改变运算符的优先级和结合性

总结

函数重载和运算符重载是 C++ 中非常重要的特性,它们提供了代码复用和语法糖,使代码更简洁和易于阅读。函数重载通过参数列表的不同来实现,而运算符重载允许自定义类型的操作更直观。


练习建议: 1. 实现一个 Rational 类,表示有理数 2. 重载加减乘除运算符 3. 重载比较运算符 4. 实现流输出运算符