自增、自減運算子的字首和字尾形式區別
原文連結:http://blog.xieyc.com/prefix-and-suffix-forms-of-the-increment-and-decrement-operators/
2013年10月23日,參加航天九院772所的面試,其實是筆試+面試,試卷中有這麼一道題目:
1 2 |
int
a = 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 |
class
UPInt { //
"unlimited precision int"
public :
UPInt&
operator++(); //
++ 字首
const
UPInt operator++( int );
//
++ 字尾
UPInt&
operator--(); //
-- 字首
const
UPInt 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字首與字尾的形式上的規範。