使用重载运算符
基础示例 1
现在尝试实现text * 2
这样的操作,使得运算得到结果你好你好
。其中text
是std::u32string
类型的字符串对象,并且保存的值是你好
。也就是"你好" * 2
得"你好你好"
。
#include <iostream> // std::cout std::endl
#include <string> // std::string
std::string operator*(const std::string &text, unsigned long long n);
int main(void)
{
std::string text = "你好";
std::cout << "你好 × 0:" << (text * 0) << std::endl;
std::cout << "你好 × 1:" << (text * 1) << std::endl;
std::cout << "你好 × 2:" << (text * 2) << std::endl;
std::cout << "你好 × 3:" << (text * 3) << std::endl;
std::cout << "你好 × 4:" << (text * 4) << std::endl;
return 0;
}
std::string operator*(const std::string &text, unsigned long long n)
{
if (n == 0)
{
return std::string();
}
std::string result;
for (unsigned long long i = 0; i < n; ++i)
{
result += text;
}
return result;
}
输出结果:
你好 × 0:
你好 × 1:你好
你好 × 2:你好你好
你好 × 3:你好你好你好
你好 × 4:你好你好你好你好
基础讲解 1
*
是一个运算符,现在给*
一个新的功能,就是使它能够用在上面这种情况。从代码可以看出,需要以函数的形式,才能给运算符添加新功能。由于*
已经有如数学乘法等其他功能,所以要用一个新的函数重新定义它的功能,这是我们熟悉的重载函数;而且由于重载的是运算符,所以应该叫做重载运算符函数。
重载运算符函数的函数名称:关键字operator
+ 需要重载的运算符,如上面的operator*
。
参数和返回值都需要看情况。如上面的*
,由于乘法肯定是需要两个参数,所以operator*
必须有两个参数。至于参数和返回值的类型就要按实际情况,因为我需要上面的功能,所以第一个参数是std::string
,而第二个参数就是无符号整数,返回值也是std::string
。
当使用text * 2
时,*
左边的值作为operator*
的第一个参数,*
右边的值作为operator*
的第二个参数,然后根据这两个参数选择适当的重载函数。也因此,假如我写成了2 * text
,那么,由于没有适当的重载函数,所以会导致编译报错。
至于重载函数operator*()
的定义就不需要过多地解释了,相信你会看得懂。
温馨提示:重载函数operator*()
中,std::string result;
声明后,如果马上使用成员函数reserve()
来指明字符串的字节数,那么就可以提高运行性能。
基础拓展 1
除了使用*
外,实际上也可以直接使用它的函数名称,例如下面用法:
operator*(text, 2);
上面这行代码跟下面这行代码等价:
text * 2;
基础示例 2
二维坐标点point_t
的对象乘以2,使对象的成员变量x
和y
分别乘以2。
#include <iostream> // std::cout std::endl
class point_t
{
public:
point_t(int a, int b);
point_t operator*(int n);
public:
int x;
int y;
};
int main(void)
{
point_t point1(5, 6);
auto point2 = point1 * 5;
std::cout << "point1横坐标的值是:" << point1.x << std::endl;
std::cout << "point1纵坐标的值是:" << point1.y << std::endl;
std::cout << "point2横坐标的值是:" << point2.x << std::endl;
std::cout << "point2纵坐标的值是:" << point2.y << std::endl;
return 0;
}
point_t::point_t(int a, int b)
: x(a)
, y(b)
{
}
point_t point_t::operator*(int n)
{
return point_t(x * n, y * n);
}
输出结果:
point1横坐标的值是:5
point1纵坐标的值是:6
point2横坐标的值是:25
point2纵坐标的值是:30
基础讲解 2
从代码中可以看出,重载运算符除了可以定义成全局函数外,也可以定义成类成员函数。
当重载运算符函数作为类成员函数时,只需要一个参数。当使用point1 * 5
时,就会调用类的重载运算符函数,并且将*
右边的值作为参数。同样,如果写成了5 * point1
,那么编译器就会找不到对应重载函数而报错,如果需要5 * point1
这样的功能,那么可以再写一个对应的重载函数。
基础拓展 2
除了使用*
外,实际上也可以直接使用它的成员函数名称,例如下面用法:
point1.operator*(5);
上面这行代码跟下面这行代码等价:
point1 * 5;
可重载的运算符
可以重载的符号只有下列这38个:+
-
*
/
%
ˆ
&
|
~
!
=
<
>
+=
-=
*=
/=
%=
ˆ=
&=
|=
<<
>>
>>=
<<=
==
!=
<=
>=
&&
||
++
--
,
->*
->
()
[]
。
其中,=
()
[]
->
只能作为成员函数,而其他运算符既可以作为成员函数来重载,也可以作为普通函数来重载。
举个例子,假设表达式a + b;
,如果+
是某类的成员函数而且a
是该类的对象,那么a + b;
就可以写成a.operator+(b);
,它们是等价的,这个时候成员函数operator+
只能接收一个参数;而如果运算符+
是普通函数,那么a + b;
就可以写成operator+(a, b);
,它们是等价的,这个时候成员函数operator+
只能接收两个参数。其他运算符也是同理。
比较特殊的运算符是++
和--
,因为它们有前置和后置两种,这里以++
作为成员函数为例子说明。假设a
是类testclass
的对象,那么++a;
的函数声明就是testclass & operator++(void);
,而a++;
的函数声明就是testclass operator++(int);
。作为区别,规定前置++
的重载函数是没有参数的;而后置++
的重载函数是只有一个参数而且必须是int
类型的,由于这个参数只是用于区分,所以使用这个参数的值是没有意义的。至于返回值是没有严格规定的,但是按照惯例,前置++
返回该对象的引用或者返回void
,而后置++
则返回该对象的复制,所以使用前置++
一般会比后置++
快。
在类中可以使用这样的成员函数operator 类型(void);
,如operator bool(void);
,这种函数叫做转换函数。顾名思义,当一个类定义了转换函数,那么这个类的对象都可以转换成定义的类型。假设类testclass
定义了转换函数operator bool(void);
,那么testclass
的对象a
就可以这样玩bool b = static_cast<bool>(a);
或者这样bool b = a;
;而如果强制调用者必须显式转换,那么转换函数的声明可以这样写explicit operator bool(void);
,那么这时候只能使用bool b = static_cast<bool>(a);
。当然啦,转换函数是自己定义的,那么就要返回自己定义的类型的返回值。
关键字new
、delete
、new []
、delete []
这四个也是运算符,也可以被重载。重载operator new
和operator new []
的参数可以任意数量,但第一个参数必须是std::size_t
类型,返回值必须是void *
,即void * operator new(std::size_t size);
;而重载operator delete
和operator delete []
,必须只有一个参数,而且必须是void *
类型,而且返回类型必须是void
,即void operator delete(void *p);
。
巩固练习
现在为simple_vector
完成以下成员函数:
// 查看指定位置的值
// 参数: pos 索引位置, 从0开始
// 返回值: 指定位置的值
const int & operator[](std::size_t pos) const;
// 查看指定位置的值
// 参数: pos 索引位置, 从0开始
// 返回值: 指定位置的值
int & operator[](std::size_t pos);
提示:
重载符号[]
和成员函数at()
没有什么区别,就是少了越界的检测。而实际上std::vector
和std::string
等,重载的[]
都不检测边界,需要调用者保证不会错误,而成员函数at()
都检测边界,如果超出范围则抛出异常。