试卷中有这么一道题目:
1
2
inta = 4;
(++a) += i;
求a的数值,正确答案是10。
如果你认为这道题重点只是考察运算符优先级,可能很容易得到正确的答案。
但是,考虑过为什么下面的代码无法编译么?
自己在笔试时,考虑到了关于表达式作为赋值运算符左值的问题,但是自己确实又对重载“++”操作符的实现机制和函数原型不很了解,就误认为“a++”和"++a"这两种写法都不能作为赋值运算符左值,从而以为这道题出错了,或者故意考察这一点,就直接写了个“无法编译”……
我回来后问了两个小伙伴,他们都能得到“(++a) += a;”的正确结果即 a = (a+1)+(a+1),但是无法解释为什么“(a++) += a;”无法编译,而且居然一致认为“++a”是“先执行自增后返回值,因此表达式是自增后的值”,而“a++”则是“先返回自增前的值,在这一条语句执行完之后a才自增”!
上述关于前自增运算符的认识基本是对的,但是关于后自增运算符的认识却大大的错了!在此鄙视一下这两个小伙伴,难道你们的C语言是体育老师教的么?居然会认为一个自增运算符能先执行一部分,在整条语句句执行后再执行另一部分……(哪种语言有这功能?)
今天上午花时间研究了一下这方面的内容,才恍然大悟,对自增/自减运算符的两种形式又加深了不少理解。不敢独享,特总结成文如下,也顺便纠正一下小伙伴们的错误认识@shasha,哼。
.
一、自增、自减运算符前缀和后缀形式的区别
我们都知道C/C++中大名鼎鼎的自增运算符(++操作符)具有两种形式:前置操作和后置操作。
从运算符的实现上来看,a++与++a的差别如下:
(1)前递增运算a++,在执行过程中,先将对象进行递增修改,而后返回该对象的引用。
(2)后递增运算++a,在运算符重载函数中采用值返回的方式编写,重载函数的内部创建一个用于临时存储原有对象值的对象,在执行完对a的自增后,函数返回该临时对象的值。
简单地讲,就是:
前自增操作生成左值,在给操作数加1后返回改变后的操作数值;而后自增操作生成右值,给操作数加1但返回未改变的操作数原值。
附左值与右值的概念:
左值:可以出现在赋值操作左边的值。非const左值可读可写。
右值:可用于赋值操作的右边但不能用于左边的值。右值只能读不能写。
因此,左值可以出现在赋值操作右端,但右值不可以出现在赋值操作左端,,将后自增操作置于赋值操作左端将会出现编译错误。
现在来分析为什么"(a++) += a;"无法编译的问题:对于“ (a++) += a; ”,先运算(a++),但(a++)返回的不是引用,而是原有a值的一个拷贝,而此时的拷贝不再是一个变量,还是一个常量,故不能当作左值继续参与运算。
同理,这也可以解释如下的几个表达式是否能够编译:
(1) ++++a;
(2) a++++;
(3) ++a++;
我们知道:自增运算符++是结合方向是自右自左(VC++6.0),所以++++a也写成++(++a),显然是正确的。而a++++写作(a++)++显然是错误的,这会导致发生编译错误:
error C2105: ‘++’ needs l-value.
至于++a++,依据结合性,写作++(a++),也是错误的,但是需要注意的是,(++a)++却是正确的写法。
.
二、关于++运算符的重载
很久以前(八十年代),没有办法区分++和–操作符的前缀与后缀调用。这个问题遭到程序员的报怨,于是C++语言得到了扩展,允许重载increment 和 decrement操作符的两种形式。
然而有一个句法上的问题,重载函数间的区别决定于它们的参数类型上的差异,但是不论是increment或decrement的前缀还是后缀都只有一个参数。为了解决这个语言问题,C++规定后缀形式有一个int类型参数,当函数被调用时,编译器传递一个0做为int参数的值给该函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
classUPInt { // "unlimited precision int"
public:
UPInt& operator++();// ++ 前缀
constUPInt operator++(int);// ++ 后缀
UPInt& operator–();// — 前缀
constUPInt operator–(int);// — 后缀
UPInt& operator+=(int);// += 操作符,UPInts 与 ints 相运算
…
};
UPInt i;
++i;// 调用 i.operator++();
i++;// 调用 i.operator++(0);
–i;// 调用 i.operator–();
i–;// 调用 i.operator–(0);
这个规范有一些古怪,不过你会习惯的。而尤其要注意的是这些操作符前缀与后缀形式返回值类型是不同的。前缀形式返回一个引用,后缀形式返回一个const类型。下面我们将讨论++操作符的前缀与后缀形式,这些说明也同样使用与–操作符。
但是从你开始做C程序员那天开始,你就应该记住increment的前缀形式有时叫做“增加然后取回”,后缀形式叫做“取回然后增加”。这两句话非常重要,因为它们是increment前缀与后缀的形式上的规范。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// prefix: increment and then fetch
UPInt& UPInt::operator++()
{
*this+= 1; // 增加
return*this;// 取回值
}
// postfix form: fetch the origin and increment
constUPInt UPInt::operator++(int)
{
UPInt oldValue = *this;// 取回值
++(*this);// 增加
returnoldValue; // 返回被取回的值
}
生活不要太劳累,弄得自己很疲惫,快乐幸福多体会,