跳转至

10 Overloaded Operator

说明

本文档仅涉及部分内容,仅可用于复习重点知识

1 概念

运算符重载是 C++ 提供的一种功能,允许程序员为用户定义的类型(如类或结构体)重新定义标准运算符的行为

  • 目的:使用户定义的类型能够像内置类型一样使用运算符
  • 限制:不能创建新的运算符,只能重载已有的运算符

运算符重载通过定义一个特殊的成员函数或友元函数实现

  1. 成员函数形式:运算符作为类的成员函数时,左操作数必须是类的对象。ReturnType operatorOp(Arguments);
  2. 友元(全局)函数形式:运算符作为友元函数时,可以访问类的私有成员,且左操作数不必是类的对象。friend ReturnType operatorOp(Arguments);

可重载的运算符:

  1. 算术运算符:+, -, *, /, %
  2. 关系运算符:==, !=, <, >, <=, >=
  3. 逻辑运算符:&&, ||, !
  4. 位运算符:&, |, ^, ~, <<, >>
  5. 赋值运算符:=, +=, -=, *=, /=, %=, <<=, >>=, &=, |=, ^=
  6. 类型转换运算符:Type()
  7. 其他运算符:++, --, [], (), ->, ->*, new, delete

不可重载的运算符:

  1. ::(作用域解析运算符)
  2. .*(成员指针访问运算符)
  3. .(成员访问运算符)
  4. ?:
  5. sizeof(大小运算符)
  6. typeid(类型信息运算符)
  7. static_cast, dynamic_cast 等(类型转换)

注意事项:

  1. 不能改变运算符的优先级和结合性:运算符的优先级和结合性是固定的,无法通过重载改变
  2. 避免滥用运算符重载:运算符重载应符合直觉,避免引入令人困惑的行为
  3. 返回值类型:

    1. 对于赋值运算符(如 =),应返回当前对象的引用(*this
    2. 对于算术运算符(如 +),通常返回一个新对象
  4. 自定义赋值运算符时注意自赋值:在实现赋值运算符时,应检查 this == &other,以避免自赋值导致的问题

运算符类别 运算符 可重载类型 通常返回类型 备注
算术运算符 +, -, *, /, % 成员或友元 新对象(如Tconst T 通常返回新对象,不修改操作数
+(正), -(负) 成员 Tconst T 一元运算符,成员函数无参数
自增/自减 ++a(前置) 成员 T& 返回修改后的对象引用
a++(后置) 成员 T(旧值副本) 需添加 int 哑参数区分(如 operator++(int)
--a(前置) 成员 T& 同前置++
a--(后置) 成员 T(旧值副本) 需添加 int 哑参数(如 operator--(int)
关系运算符 ==, !=, <, >, <=, >= 成员或友元 bool 通常成对重载(如==!=
逻辑运算符 &&, || 成员或友元 bool 短路行为可能丢失,谨慎使用
! 成员 bool 一元运算符
位运算符 &, |, ^ 成员或友元 新对象(如T 按位操作
~ 成员 T 一元运算符(按位取反)
<<, >> 友元 ostream&/istream& 输入输出流操作符通常为友元
赋值运算符 = 成员 T& 返回当前对象的引用(支持链式赋值)
+=, -=, *=, /=, %= 等复合赋值 成员 T& 修改当前对象并返回引用
类型转换运算符 Type() 成员 任意目标类型 无返回类型声明(如 operator int() const
其他运算符 [] 成员 T&const T& 需重载const和非const版本
() 成员 任意类型 函数对象(仿函数)
->, ->* 成员 指针或代理对象 用于智能指针或迭代器
new, delete 成员(静态) void*new), voiddelete 静态成员函数,即使不显式声明
,(逗号) 成员或友元 任意类型 不推荐重载(易混淆)
&(取地址) 成员 指针类型 极少需要重载

建议

  1. 对称运算符(如 +, ==)推荐用全局函数:支持 a + bb + a 的互换性
  2. 复合赋值运算符(如 +=)用成员函数:直接修改对象状态
  3. 输入/输出运算符(<<, >>)必须全局函数:因为左操作数是流对象

2 运算符重载的实现

2.1 算术运算符

#include <iostream>
using namespace std;

class Complex {
private:
    double real, imag;

public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // 重载加法运算符(成员函数形式)
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }

    void display() const {
        cout << real << " + " << imag << "i" << endl;
    }
};

int main() {
    Complex c1(1.0, 2.0), c2(3.0, 4.0);
    Complex c3 = c1 + c2; // 调用重载的 +
    c3.display();
    return 0;
}
output
4 + 6i

2.2 关系运算符

#include <iostream>
using namespace std;

class Box {
private:
    double volume;

public:
    Box(double v) : volume(v) {}

    // 重载小于运算符(友元函数形式)
    friend bool operator<(const Box& b1, const Box& b2) {
        return b1.volume < b2.volume;
    }
};

int main() {
    Box b1(10.0), b2(20.0);
    if (b1 < b2) {
        cout << "b1 is smaller than b2" << endl;
    }
    return 0;
}
output
b1 is smaller than b2

2.3 输入/输出运算符

输入/输出运算符通常以友元函数形式重载

#include <iostream>
using namespace std;

class Point {
private:
    int x, y;

public:
    Point(int x = 0, int y = 0) : x(x), y(y) {}

    // 重载输出运算符
    friend ostream& operator<<(ostream& os, const Point& p) {
        os << "(" << p.x << ", " << p.y << ")";
        return os;
    }

    // 重载输入运算符
    friend istream& operator>>(istream& is, Point& p) {
        is >> p.x >> p.y;
        return is;
    }
};

int main() {
    Point p1, p2;
    cout << "Enter coordinates for p1: ";
    cin >> p1;
    cout << "p1: " << p1 << endl;

    return 0;
}
input
Enter coordinates for p1: 3 4
output
p1: (3, 4)

2.4 下标运算符

下标运算符 [] 通常以成员函数形式重载

#include <iostream>
using namespace std;

class Array {
private:
    int arr[10];

public:
    Array() {
        for (int i = 0; i < 10; ++i) arr[i] = i;
    }

    // 重载下标运算符
    int& operator[](int index) {
        return arr[index];
    }
};

int main() {
    Array a;
    a[2] = 42; // 使用重载的 []
    cout << "a[2] = " << a[2] << endl;
    return 0;
}
output
a[2] = 42

2.5 函数调用运算符

函数调用运算符 () 可以重载,使对象像函数一样调用

#include <iostream>
using namespace std;

class Multiply {
public:
    int operator()(int a, int b) {
        return a * b;
    }
};

int main() {
    Multiply multiply;
    cout << "3 * 4 = " << multiply(3, 4) << endl; // 使用重载的 ()
    return 0;
}
output
3 * 4 = 12

2.6 特殊运算符

1.赋值运算符 =

1
2
3
4
5
6
7
8
class MyClass {
public:
    MyClass& operator=(const MyClass& other) {
        if (this == &other) return *this; // 防止自赋值
        // 赋值逻辑
        return *this;
    }
};

2.自增/自减运算符 ++--

前置版本
1
2
3
4
MyClass& operator++() {
    // 前置自增逻辑
    return *this;
}
后置版本
1
2
3
4
5
MyClass operator++(int) {
    MyClass temp = *this;
    // 后置自增逻辑
    return temp;
}

2.7 创建操纵符

manipulator:操纵符。操纵符的本质是一个函数,通常接受一个流对象(如 std::ostreamstd::istream)作为参数,并返回该流对象的引用

  1. 无参数操纵符:不需要额外的参数,直接影响流的行为。

    1. std::endl:插入换行符并刷新输出缓冲区
    2. std::flush:刷新输出缓冲区
    3. std::ws:跳过输入流中的空白字符
  2. 有参数操纵符:需要额外的参数来指定行为,通常通过 #include <iomanip> 提供

    1. std::setw(n):设置输出字段的宽度
    2. std::setprecision(n):设置浮点数的精度
    3. std::setfill(c):设置填充字符
无参数操纵符
#include <iostream>
using namespace std;

// 自定义操纵符,输出分隔线
ostream& separator(ostream& os) {
    return os << "--------------------" << endl;
}

int main() {
    cout << "Hello" << separator << "World" << endl;
    return 0;
}
output
1
2
3
Hello
--------------------
World

2.8 类型转换运算符

函数签名:operator type() const

  • type:目标类型(如 intdouble 或用户定义的类型)
  • 类型转换运算符没有返回类型,因为返回类型就是目标类型
将类对象转换为基本数据类型
#include <iostream>
using namespace std;

class Complex {
private:
    double real, imag;
public:
    Complex(double r, double i) : real(r), imag(i) {}

    // 重载类型转换运算符,将 Complex 转换为 double
    operator double() const {
        return real; // 返回实部
    }
};

int main() {
    Complex c(3.5, 2.5);
    double realPart = c; // 隐式调用类型转换运算符
    cout << "Real part: " << realPart << endl; // 输出: Real part: 3.5
    return 0;
}
将类对象转换为其他类类型
#include <iostream>
using namespace std;

class Point {
private:
    int x, y;
public:
    Point(int x, int y) : x(x), y(y) {}

    int getX() const { return x; }
    int getY() const { return y; }
};

class Polar {
private:
    double radius, angle;
public:
    Polar(double r, double a) : radius(r), angle(a) {}

    // 重载类型转换运算符,将 Polar 转换为 Point
    operator Point() const {
        return Point(static_cast<int>(radius), static_cast<int>(angle));
    }

    void display() const {
        cout << "Radius: " << radius << ", Angle: " << angle << endl;
    }
};

int main() {
    Polar p(5.5, 45.0);
    Point pt = p; // 隐式调用类型转换运算符
    cout << "Point: (" << pt.getX() << ", " << pt.getY() << ")" << endl; // 输出: Point: (5, 45)
    return 0;
}

注意事项:

  1. 类型转换运算符可以隐式调用,也可以通过显式转换调用(如 static_cast
  2. 如果不希望隐式调用,可以使用 explicit 关键字修饰类型转换运算符(C++11 引入)
  3. 类型转换运算符的返回值必须是目标类型的值,不能返回引用
class MyClass {
public:
    explicit operator int() const {
        return 42;
    }
};

int main() {
    MyClass obj;
    // int x = obj; // 错误:explicit 禁止隐式转换
    int x = static_cast<int>(obj); // 正确:显式转换
    return 0;
}

3 Single Argument Constructors

单参数构造函数(Single Argument Constructor)是指只有一个参数的构造函数。它通常用于将一个类型的值转换为类对象。由于其特殊性,单参数构造函数可能会引发隐式类型转换,因此需要特别注意其用法

当创建类对象时,如果提供了一个与构造函数参数类型匹配的值,编译器会调用该构造函数来初始化对象

3.1 隐式类型转换

#include <iostream>
using namespace std;

class MyClass {
private:
    int value;
public:
    // 单参数构造函数
    MyClass(int v) : value(v) {}

    void display() const {
        cout << "Value: " << value << endl;
    }
};

int main() {
    MyClass obj = 42;  // 隐式调用单参数构造函数
    MyClass obj2(42);  // 显式调用构造函数
    obj2 = 30;  // 隐式转换
    obj.display();  // 输出: Value: 42
    return 0;
}

在上面的代码中,MyClass obj = 42 会隐式调用单参数构造函数,将 42 转换为 MyClass 类型的对象

obj2 = obj

  1. 调用构造函数:用 30 创建一个临时对象(这里会发生隐式转换,从 intMyClass
  2. 调用默认拷贝赋值运算符:将这个临时对象赋值给 obj2 这个对象

3.2 explicit

隐式类型转换可能会导致意外的行为。为了避免这种情况,可以使用 explicit 关键字修饰单参数构造函数(C++11 引入)

#include <iostream>
using namespace std;

class MyClass {
private:
    int value;
public:
    explicit MyClass(int v) : value(v) {}

    void display() const {
        cout << "Value: " << value << endl;
    }
};

int main() {
    // MyClass obj = 42; // 错误:explicit 禁止隐式转换
    MyClass obj(42);     // 正确:显式调用构造函数
    // obj2 = 30;        // 错误:explicit 禁止隐式转换
    obj.display();       // 输出: Value: 42
    return 0;
}

通过添加 explicit,只能通过显式调用构造函数来创建对象,从而避免隐式类型转换

C++ 类型转换机制

1.内置类型转换(Built-in conversions):适用于基本数据类型(primitive types)

  1. charshortintfloatdouble
  2. charshortintlong

这些是 C++ 标准定义的隐式类型提升规则

2.隐式类型转换(Implicit conversions):适用于任何类型 T 的隐式转换:

  1. TT&
  2. T&T
  3. T*void*
  4. T[]T*
  5. T*T[]
  6. Tconst T

3.用户定义类型转换(User-defined):两种方式实现用户定义类型转换:

  1. 构造函数转换:如果类 C 有接受类型 T 的构造函数 C(T)
  2. 转换运算符:如果类 T 定义了 operator C()

4.使用建议

  1. 一般不建议使用隐式类型转换,因为可能导致函数被意外调用
  2. 建议使用显式转换函数
1
2
3
4
5
class Rational {
public:
    explicit operator double() const;  // C++11 风格显式转换
    double toDouble() const;         // 更明确的转换函数
};

评论区

欢迎在评论区指出文档错误,为文档提供宝贵意见,或写下你的疑问