1.有時候返回引用可以提高效率,有些情況不能返回引用
類的賦值運算子函數中,返回對象的引用可以顯著提高效率。
class String<br />{<br />public:<br />String & operate=(const String &other);<br />// 相加函數,如果沒有friend修飾則只許有一個右側參數<br />friendString operate+(const String &s1, const String &s2);<br />private:<br />char *m_data;<br />}</p><p>String & String::operate=(const String &other)<br />{<br />if (this == &other)<br />return *this;<br />delete m_data;<br />m_data = new char[strlen(other.data)+1];<br />strcpy(m_data, other.data);<br />return *this;// 返回的是 *this的引用,無需拷貝過程<br />}
上面如果用“值傳遞”的方式,雖然功能仍然正確,但由於return語句要把 *this拷貝到儲存傳回值的外部儲存單元之中,增加了不必要的開銷,降低了賦值函數的效率。
String的相加函數operate + 的實現如下:
String operate+(const String &s1, const String &s2)<br />{<br />String temp;<br />delete temp.data;// temp.data是僅含‘/0’的字串<br />temp.data = new char[strlen(s1.data) + strlen(s2.data) +1];<br />strcpy(temp.data, s1.data);<br />strcat(temp.data, s2.data);<br />return temp;<br />}
對於相加函數,應當用“值傳遞”的方式返回String對象。如果改用“引用傳遞”,那麼函數傳回值是一個指向局部對象temp的“引用”。由於temp在函數結束時被自動銷毀,將導致返回的“引用”無效。
設想一下,相加運算子多載函數應該是參數列表中的兩個對象相加,必須產生一個新的對象並返回它。這區別於+=運算子,+=運算子是將右邊的對象累加到左邊的對象上,因此+=的實現應當返回運算子左邊對象的引用。
2. 返回靜態局部變數的引用往往會帶來問題
上面已經說了operator+函數中不能返回臨時對象的引用,那麼返回靜態局部變數的引用呢?靜態局部變數的生命不會隨著函數消失而結束,返回其引用似乎可行,但這樣做往往會給程式帶來問題。
const Rational& operator*(const Rational& lhs, const Rational& rhs)<br />{<br />static Rational result; // static object to which a reference will be returned<br />result = ... ; // multiply lhs by rhs and put the product inside result<br />return result;<br />}
下面if恒為真,靜態局部變數對全域可見,每次operator*返回的是它的引用,相當於是返回它的“新值”。
bool operator==(const Rational& lhs, const Rational& rhs); // an operator== for Rationals<br />Rational a, b, c, d;<br />...<br />if ((a * b) == (c * d))<br />{<br />do whatever's appropriate when the products are equal;<br />}<br />else<br />{<br />do whatever's appropriate when they're not;<br />}<br />
3. 返回臨時對象,不要定義局部對象返回
String temp(s1 + s2);
return temp;
上述代碼將發生三件事。首先,temp對象被建立,同時完成初始化;然後拷貝建構函式把temp拷貝到儲存傳回值的外部儲存單元中;最後,temp在函數結束時被銷毀(調用解構函式)。然而,建立一個臨時對象並返回它:return temp(s1 + s2);,編譯器直接把臨時對象建立並初始化在外部儲存單元中,省去了拷貝和析構的化費,提高了效率。
由於內部資料類型如int,float,double的變數不存在建構函式與解構函式,雖然該“臨時變數的文法”不會提高多少效率,但是程式更加簡潔易讀。
4. 注意不要返回棧指標和常量字串
不要用return語句返回指向“棧記憶體”的指標,因為該記憶體在函數結束時自動消亡。
char *GetString(void)<br />{<br />char p[] = "hello world";<br />return p;// 編譯器將提出警告<br />}</p><p>void Test4(void)<br />{<br />char *str = NULL;<br />str = GetString();// str 的內容是垃圾<br />cout<< str << endl;<br />}
函數Test5運行雖然不會出錯,但是函數GetString2的設計概念卻是錯誤的。因為GetString2內的“hello world”是常量字串,位於靜態儲存區,它在程式生命期內恒定不變。無論什麼時候調用GetString2,它返回的始終是同一個“唯讀”的記憶體塊。
char *GetString2(void)<br />{<br />char *p = "hello world";<br />return p;<br />}</p><p>void Test5(void)<br />{<br />char *str = NULL;<br />str = GetString2();<br />cout<< str << endl;<br />}<br />
5. 謹慎返回對象內部成員對象的引用
有時候希望使得類對象佔用空間較小,通常可以使用一個指向對象資料的指標作為該類的資料成員,如Rectangle類的成員指標pData。類的成員函數upperLeft和lowerRight僅僅是想擷取類的資料內容而不想修改其內容,因此函數結尾使用了const表示類的資料成員不可更改。然而卻不能達到設計的目的,雖然成員是私人,然而卻通過公有函數返回了可以修改它的引用。如可以這樣修改它:Rectangle r(&rd); r.upperLeft().setX(5);
class Point<br />{<br />public:<br />Point(float p1, float p2){ x = p1; y = p2; }<br />void setX(float f){ x = f; }<br />void setY(float f){ y = f; }<br />private:<br />float x, y;<br />};</p><p>struct RectData<br />{<br />RectData(Point p1, Point p2) : ulhc(p1), lrhc(p2){}<br />Point ulhc, lrhc;<br />};</p><p>class Rectangle<br />{<br />public:<br />Rectangle(RectData *p) { pData = p; }<br />Point& upperLeft() const { return pData->ulhc; }<br />Point& lowerRight() const { return pData->lrhc; }<br />private:<br />RectData *pData;<br />};
因此需要將兩函數改為,這樣想試圖修改點的資料內容時,編譯器會報錯。
const Point& upperLeft() const { return pData->ulhc; }<br />const Point& lowerRight() const { return pData->lrhc; }
參考文獻:
Effective C++
高品質 C/C++ 編程指南