定义递增和递减运算符的类应该同时定义前置版本和后置版本。这些运算符通常应该被定义成类的成员。
定义前置递增/递减运算符
class StrBlobPtr {
public:
StrBlobPtr& operator++();
StrBlobPtr& operator--();
};
为了与内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。
递增和递减运算符的工作机理非常相似:它们首先调用check函数检验StrBlobPtr是否有效,如果是,接着检查给定的索引值是否有效。如果check函数没有抛出异常,则运算符返回对象的引用。
StrBlobPtr& StrBlobPtr::operator++() {
//如果curr已经指向了容器的尾后位置,则无法递增它
check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
StrBlobPtr& StrBlobPtr::operator--() {
//如果curr是0,则继续递减它将产生一个无效下标
--curr;
check(curr, "decrement past begin of StrBlobPtr");
return *this;
}
区分前置和后置运算符
后置版本接受一个额外的(不被使用)int类型的形参。这个形参唯一的作用是区分前置版本和后置版本的函数,而不是真的要在实现后置版本时参与运算。
class StrBlobPtr {
public:
StrBlobPtr& operator++(int); //后置运算符
StrBlobPtr& operator--(int);
};
为了与内置版本保持一致,后置运算符应该返回对象的原值(递增或递减之前的值),返回的形式是一个值而非引用。
对于后置版本来说,在递增对象之前需要记录对象的状态:
//后置版本:递增和递减运算符
StrBlobPtr StrBlobPtr::operator++(int) {
//此处无需检查有效性,调用前置递增运算时才需要检查
StrBlobPtr ret = *this; //记录当前的值
++* this; //向前移动一个元素,前置++需要检查递增的有效性
return ret; //返回之前记录的状态
}
StrBlobPtr StrBlobPtr::operator--(int) {
//此处无需检查有效性,调用前置递减运算时才需要检查
StrBlobPtr ret = *this; //记录当前的值
--* this; //向后移动一个元素,前置--需要检查递减的有效性
return ret; //返回之前记录的状态
}
后置运算符调用各自的前置版本来完成实际的工作。例如后置递增运算符执行
++*this
该表达式调用前置递增运算符,前置递增运算符首先检查递增操作是否安全,根据检查的结果抛出一个异常或者执行递增curr的操作。后置函数返回之前存储的ret的副本。因此,最终的效果是,对象本身向前移动了一个元素,而返回的结果仍然反应对象在未递增之前的原始的值。
显式地调用后置运算符
可以显式地调用一个重载地运算符,其效果与在表达式中以运算符号的形式使用它完全一样。如果我们想通过函数调用的方式调用后置版本,则必须为它的整型参数传递一个值:
StrBlobPtr p(a1); //P指向a1中的vector
p.operator++(0); //调用后置版本的operator++
p.operator++(); //调用前置版本
为你的StrBlobPtr类添加加法和减法运算符,使其可以实现指针的算术运算
class StrBlobPtr {
friend StrBlobPtr operator+(int n);
friend StrBlobPtr operator-(int n);
};
StrBlobPtr StrBlobPtr::operator+(int n) {
auto ret = *this;
ret.curr += n;
return ret;
}
StrBlobPtr StrBlobPtr::operator-(int n) {
auto ret = *this;
ret.curr -= n;
return ret;
}
为什么不定义const版本的递增和递减运算符?
对于++和- -运算符,无论它是前缀版本还是后缀版本,都会改变对象本身的值,所以不能定义成const的。