【C++基础】第八十课:[重载运算与类型转换]递增和递减运算符

重载递增和递减运算符

Posted by x-jeff on August 2, 2023

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.递增和递减运算符

在迭代器类中通常会实现递增运算符(++)和递减运算符(--),这两种运算符使得类可以在元素的序列中前后移动。C++语言并不要求递增和递减运算符必须是类的成员,但是因为它们改变的正好是所操作对象的状态,所以建议将其设定为成员函数。

对于内置类型来说,递增和递减运算符既有前置版本也有后置版本。同样,我们也应该为类定义两个版本的递增和递减运算符。

1.1.定义前置递增/递减运算符

我们在StrBlobPtr类中定义递增和递减运算符:

1
2
3
4
5
6
7
class StrBlobPtr {
public:
	//递增和递减运算符
	StrBlobPtr& operator++(); //前置运算符
	StrBlobPtr& operator--();
	//其他成员和之前的版本一致
};

为了与内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//前置版本:返回递增/递减对象的引用
StrBlobPtr& StrBlobPtr::operator++()
{
	//如果curr已经指向了容器的尾后位置,则无法递增它
	check(curr, "increment past end of StrBlobPtr");
	++curr; //将curr在当前状态下向前移动一个元素
	return *this;
}
StrBlobPtr& StrBlobPtr::operator--()
{
	//如果curr是0,则继续递减它将产生一个无效下标
	--curr; //将curr在当前状态下向后移动一个元素
	check(curr, "decrement past begin of StrBlobPtr");
	return *this;
}

1.2.区分前置和后置运算符

要想同时定义前置和后置运算符,必须首先解决一个问题,即普通的重载形式无法区分这两种情况。前置和后置版本使用的是同一个符号,意味着其重载版本所用的名字将是相同的,并且运算对象的数量和类型也相同。

为了解决这个问题,后置版本接受一个额外的(不被使用)int类型的形参。当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参。尽管从语法上来说后置函数可以使用这个额外的形参,但是在实际过程中通常不会这么做。这个形参的唯一作用就是区分前置版本和后置版本的函数,而不是真的要在实现后置版本时参与运算。

接下来我们为StrBlobPtr添加后置运算符:

1
2
3
4
5
6
7
class StrBlobPtr {
public:
	//递增和递减运算符
	StrBlobPtr operator++(int); //后置运算符
	StrBlobPtr operator--(int);
	//其他成员和之前的版本一致
};

为了与内置版本保持一致,后置运算符应该返回对象的原值(递增或递减之前的值),返回的形式是一个值而非引用。

对于后置版本来说,在递增对象之前需要首先记录对象的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//后置版本:递增/递减对象的值但是返回原值
StrBlobPtr StrBlobPtr::operator++(int)
{
	//此处无须检查有效性,调用前置递增运算时才需要检查
	StrBlobPtr ret = *this; //记录当前的值
	++*this; //向前移动一个元素,前置++需要检查递增的有效性
	return ret; //返回之前记录的状态
}
StrBlobPtr StrBlobPtr::operator--(int)
{
	//此处无须检查有效性,调用前置递减运算时才需要检查
	StrBlobPtr ret = *this; //记录当前的值
	--*this; //向后移动一个元素,前置--需要检查递减的有效性
	return ret; //返回之前记录的状态
}

因为我们不会用到int形参,所以无须为其命名。

1.3.显式地调用后置运算符

此处介绍的,可以显式地调用一个重载的运算符,其效果与在表达式中以运算符号的形式使用它完全一样。如果我们想通过函数调用的方式调用后置版本,则必须为它的整型参数传递一个值:

1
2
3
StrBlobPtr p(a1); //p指向a1中的vector
p.operator++(0); //调用后置版本的operator++
p.operator++(); //调用前置版本的operator++

尽管传入的值通常会被运算符函数忽略,但却必不可少,因为编译器只有通过它才能知道应该使用后置版本。