很久以前(八十年代),沒有辦法區分++和--操作符的前綴與後綴調用。這個問題遭到程序員的報怨,於是C++語言得到了擴展,允許重載increment 和 decrement操作符的兩種形式。
然而有一個句法上的問題,重載函數間的區別決定於它們的參數類型上的差異,但是不論是increment或decrement的前綴還是後綴都只有一個參數。為了解決這個語言問題,C++規定後綴形式有一個int類型參數,當函數被調用時,編譯器傳遞一個0做為int參數的值給該函數:
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前綴與後綴的形式上的規范。
// 前綴形式:增加然後取回值
UPInt& UPInt::operator++()
{
*this += 1; // 增加
return *this; // 取回值
}
// postfix form: fetch and increment
const UPInt UPInt::operator++(int)
{
UPInt oldValue = *this; // 取回值
++(*this); // 增加
return oldValue; // 返回被取回的值
}
後綴操作符函數沒有使用它的參數。它的參數只是用來區分前綴與後綴函數調用。如果你沒有在函數裡使用參數,許多編譯器會顯示警告信息,很令人討厭。為了避免這些警告信息,一種經常使用的方法時省略掉你不想使用的參數名稱;如上所示。
很明顯一個後綴increment必須返回一個對象(它返回的是增加前的值),但是為什麼是const對象呢?假設不是const對象,下面的代碼就是正確的:
UPInt i;
i++++; // 兩次increment後綴
// 運算
這組代碼與下面的代碼相同:
i.operator++(0).operator++(0);
很明顯,第一個調用的operator++函數返回的對象調用了第二個operator++函數。
有兩個理由導致我們應該厭惡上述這種做法,第一是與內置類型行為不一致。當設計一個類遇到問題時,一個好的准則是使該類的行為與int類型一致。而int類型不允許連續進行兩次後綴increment:
int i;
i++++; // 錯誤!
第二個原因是使用兩次後綴increment所產生的結果與調用者期望的不一致。如上所示,第二次調用operator++改變的值是第一次調用返回對象的值,而不是原始對象的值。因此如果:
i++++;
是合法的,i將僅僅增加了一次。這與人的直覺相違背,使人迷惑(對於int類型和UPInt都是一樣),所以最好禁止這麼做。
C++禁止int類型這麼做,同時你也必須禁止你自己寫的類有這樣的行為。最容易的方法是讓後綴increment 返回const對象。當編譯器遇到這樣的代碼:
i++++; // same as i.operator++(0).operator++(0);
它發現從第一個operator++函數返回的const對象又調用operator++函數,然而這個函數是一個non-const成員函數,所以const對象不能調用這個函數。如果你原來想過讓一個函數返回const對象沒有任何意義,現在你就知道有時還是有用的,後綴increment和decrement就是例子。(更多的例子參見Effective C++ 條款21)
如果你很關心效率問題,當你第一次看到後綴increment函數時, 你可能覺得有些問題。這個函數必須建立一個臨時對象以做為它的返回值,(參見條款19),上述實現代碼建立了一個顯示的臨時對象(oldValue),這個臨時對象必須被構造並在最後被結構。前綴increment函數沒有這樣的臨時對象。由此得出一個令人驚訝的結論,如果僅為了提高代碼效率,UPInt的調用者應該盡量使用前綴increment,少用後綴increment,除非確實需要使用後綴increment。讓我們明確一下,當處理用戶定義的類型時,盡可能地使用前綴increment,因為它的效率較高。
我們再觀察一下後綴與前綴increment 操作符。它們除了返回值不同外,所完成的功能是一樣的,即值加一。簡而言之,它們被認為功能一樣。那麼你如何確保後綴increment和前綴increment的行為一致呢?當不同的程序員去維護和升級代碼時,有什麼能保證它們不會產生差異?除非你遵守上述代碼裡的原則,這才能得到確保。這個原則是後綴increment和decrement應該根據它們的前綴形式來實現。你僅僅需要維護前綴版本,因為後綴形式自動與前綴形式的行為一致。
正如你所看到的,掌握前綴和後綴increment和decrement是容易的。一旦了解了他們正確的返回值類型以及後綴操作符應該以前綴操作符為基礎來實現的規則,就足夠了。