05 - 运算符重载
本文介绍了C++中的重载运算符操作,并提供了一些重载一元和二元运算符的例子。
作用
很多时候我们想让自己写的类对象也能像基础数据类型那样,可以进行加减乘除读写文件等基本操作,但对于一般的类即使编译器可以识别这些运算符,类对象也无法对这些运算符做出应对,我们必须对类对象定义处理这些运算符的方式。
C++提供了定义这些行为的方式,就是通过“operator 运算符”来定义运算符的行为, operator是一个关键字,告诉编译器我要重载运算符了。
注意点
- 只能重载C++已有的运算符。
- 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高级编程