05 - 运算符重载

本文介绍了C++中的重载运算符操作,并提供了一些重载一元和二元运算符的例子。

作用

很多时候我们想让自己写的类对象也能像基础数据类型那样,可以进行加减乘除读写文件等基本操作,但对于一般的类即使编译器可以识别这些运算符,类对象也无法对这些运算符做出应对,我们必须对类对象定义处理这些运算符的方式。

C++提供了定义这些行为的方式,就是通过“operator 运算符”来定义运算符的行为, operator是一个关键字,告诉编译器我要重载运算符了。

注意点

  1. 只能重载C++已有的运算符
  2. C++重载运算符不能改变运算符的元数,“元数”这个概念就是指一个运算符对应的对象数量。比如“+”必须为“a + b”,也就是说“+”必须有两个对象,那么“+” 就是二元运算符。比如“++”运算符,必须写为“a++”,也就是一元运算符。

举例说明

以下面的Test类为例,介绍如何进行运算符重载操作:

class Test
{
public:
    /* 运算符重载的位置 */
private:
    unsigned count;
    std::vector<int> ivec{1, 2, 3};
    std::string name;
};

int main()
{
    Test test;
    /* main函数内容 */
    return 0;
}

一元运算符重载

++与–

++为例:

void operator++ () 
{
    ++count;
}

// main函数
++test;

--同理,注意这里应该是++test而不是test++,它俩不一样。

[ ]

就是数组的索引取值:

int operator[] (int i) const
{
    // 要做是否越界等判断,这里不写了
    return ivec[i];
}

// main函数
std::cout << test[1];

()

就是之前提到过的 仿函数

void operator() () const
{
    std::cout << "Hi!" << std::endl;
}
// 还能再重载
void operator() (const std::string& str) const
{
    std::cout << str << std::endl;
}

// main函数
test();
test("Hi");

<< 和 >>

对于输入和输出运算符,必须使用 友元函数 的方式进行运算符重载,这样可以不必创建使用这些函数:

friend std::ostream& operator<< (std::ostream& os, const Test& test)
{
    os << test.name << std::endl;
    return os;
}

friend std::istream& operator>> (std::istream& is, Test& test)
{
    is >> test.name;
    return is;
}

// main函数
std::cin >> test;
std::cout << test;

二元运算符重载

加减乘除

以加法为例:

Test operator+ (const Test& test)
{
    count += test.count;
    return *this;
}

// main
Test test2;
++test2;
++test2;
std::cout << test + test2;

=

类会默认进行重载,如果不需要可以用 delete 进行修饰。

手动写如下:

Test& operator= (const Test& test)
{
    // 重复赋值的情况
    if (this == &test)
        return *this;
    
    count = test.count;
    // .....
    return *this;
}

// main
test = test2;

对于重载赋值运算符,有一种 复制与交换 的惯用方法,它可以安全的处理异常:

class Test
{
public:
    Test& operator= (const Test& rhs);
    // 定义一个swap成员函数,声明没有异常
    void swap(Test& other) noexcept;
private:
    // ...
}

// 成员函数实现
void Test::swap(Test& other) noexcept
{
    // 使用标准库高效swap
    std::swap(......);
}

// 重载赋值运算符
Test& Test::operator= (const Test& rhs)
{
    // 先创建rhs的副本
    Test temp(rhs);
    // 与副本交换
    swap(temp);
    return *this;
}

// 声明一个外部函数,如果需要的话
void swap(Test& first, Test& second) noexcept
{
    first.swap(second);
}

如果使用 复制与交换 方式实现赋值运算符,就不需要像上边那样检查自我赋值了。

>, <, ==

bool operator< (const Test& test)
{
    return count < test.count;
}

bool operator> (const Test& test)
{
    return count > test.count;
}

bool operator== (const Test& test)
{
    return count == test.count;
}

PS:在C++20中,重载==后,会自动添加对!=的支持。

[C++20] <=>

在C++20中,实现operator<=>可以让编译器自动提供对>, <, <=, >= 的支持。

std::strong_ordering operator<=> (const Test& test) const
{
    return count <=> test.count;
}

当然,也能通过显式标注 default ,让编译器自己生成:

[[nodiscard]] auto operator<=> (const Test& test) const = default;

PS: [[nodiscard]] 就是提醒编译器不能忽略操作符的结果。

重载运算符非常重要,C++类中几乎都要定义各种各种的重载运算符。

参考资料

  • 飘零的落花 - 现代C++详解
  • C++20高级编程