程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Effective C++讀書筆記(2)

Effective C++讀書筆記(2)

編輯:C++入門知識

條款03:盡可能使用const

Use const whenever possible.

         const允許你告訴編譯器和其他程序員某值應該保持不變。

         如果關鍵字const出現在星號左邊,表示被指物是常量;如果出現在星號右邊,表示指針自身是常量;如果出現在星號兩邊,表示被指物和指針兩者都是常量。

1. char greeting[] = "Hello";

2. char* p = greeting;         //non-const pointer, non-const data

3. const char* p = greeting;       //non-const pointer, const data

4. char* const p = greeting;       //const pointer, non-const data

5. const char* const p = greeting; //const pointer, const data

         如果被指物是常量,既可以關鍵字const寫在類型之前,又可以把它寫在類型之後、星號之前。兩種寫法的意義等價:

1.  void f1(const Widget* pw);    //f1獲得一個指針,指向一個常量Widget對象.. 

2.  void f2(Widget const * pw);   //f2也是

         STL迭代器系以指針為底層塑模出來,所以迭代器的作用就像個T*指針。聲明迭代器為const就像聲明指針為const一樣(即聲明一個T* const 指針),表示這個迭代器不得指向不同的東西,但它所指的東西的值是可以改動的。如果希望迭代器所指的東西(數據)不可被改動(即希望STL模擬一個const T* 指針),則用const_iterator:

1.  std::vector<int> vec; 
2.  ... 
3.  const std::vector<int>::iterator iter = vec.begin( ); //T* const
4.  *iter = 10;                     //沒問題,改變iter所指物 
5.  ++iter;                         //錯誤!iter是const 
6.  std::vector<int>::const_iterator cIter = vec.begin( );// const T* 
7.  *cIter = 10;                        //錯誤! *cIter是const 
8.  ++cIter;                        //沒問題,改變cIter。
         令函數返回一個常量值,往往可以降低因客戶錯誤而造成的意外,而又不至於放棄安全性和高效性。

1.  class Rational { ... }; 
2.  const Rational operator* (const Rational& lhs,const Rational& rhs);
    為什麼返回一個const對象?原因是如果不這樣客戶就能實現這樣的行為:

1.  Rational a, b, c; 
2.  ... 
3.  (a * b) = c;        //在a * b的成果上調用operator=
         如果a和b都是內置類型,這樣的代碼直截了當就是不合法。而一個"良好的用戶自定義類型"的特征是它們避免無端地與內置類型不兼容。將operator* 的回傳值聲明為const可以預防那個荒唐的賦值動作。

 

const成員函數

         將const實施於成員函數的目的,是為了確認該成員函數可作用於const對象身上。這類成員函數可以得知哪個函數可以改動對象內容而哪個函數不行,很是重要。

         兩個成員函數如果只是常量性不同,可以被重載。這實在是一個重要的C++特性(前幾天的面試剛碰到過):

1.  class TextBlock { 
2.  public: 
3.    ... 
4.    const char& operator[](std::size_t position) const
5.    { return text[position]; }        // operator[] for const對象. 
6.    char& operator[](std::size_t position)      
7.    { return text[position]; }        // operator[] for non-const對象. 
8.  private: 
9.    std::string text; 
10. }; 
11. 
12. TextBlock tb("Hello"); 
13. std::cout << tb[0];     //調用non-const TextBlock::operator[] 
14. const TextBlock ctb("World"); 
15. std::cout << ctb[0];        //調用const TextBlock::operator[]
         只要重載operator[]並對不同的版本給予不同的返回類型,就可以令const和non-const TextBlocks獲得不同的處理:

1.  std::cout << tb[0];//沒問題 - 讀一個non-const TextBlock 
2.  tb[0] = 'x';        //沒問題 - 寫一個non-const TextBlock 
3.  std::cout << ctb[0];//沒問題 - 讀一個const TextBlock 
4.  ctb[0] = 'x';   //錯誤! - 寫一個const TextBlock
         上述錯誤只因operator[] 的返回類型以致,至於operator[] 調用動作自身沒問題。

         請注意,non-const operator[] 的返回類型是個reference tochar,不是char。如果operator[]只是返回一個char,下面這樣的句子就無法通過編譯:

1.  tb[0] = 'x';
         返回類型是內置類型的函數,改動函數返回值不合法。縱使合法,C++以值傳遞意味被改動的其實是tb.text[0]的一個副本,不是tb.text[0]自身。

l  將某些東西聲明為const可幫助編譯器偵測出錯誤用法。const可被施加於任何作用域內的對象、函數參數、函數返回類型、成員函數本體。

 

         成員函數如果是const意味什麼?

         bitwise const:成員函數只有在不更改對象之任何成員變量(static除外)時才可以說是const,也就是說它不更改對象內的任何一個bit。bitwise constness正是C++ 對常量性的定義,因此const成員函數不可以更改對象內任何non-static成員變量。

         不幸的是許多成員函數雖然不十足具備const性質卻能通過bitwise測試:

1. class CTextBlock { 

2. public: 

3.    ...

4.    char& operator[](std::size_t position) const // bitwise const聲明, 

5.    { return pText[position]; }      // 但其實不適當.跟之前相比少了一個const!

6. private: 

7.    char* pText;

8. };

         operator[]實現代碼並不更改私有變量pText,於是編譯器為operator[]產出目標碼,並認定它是bitwiseconst。

1.  const CTextBlock cctb("Hello");//聲明一個常量對象。 
2.  char* pc = &cctb[0];//調用const operator[]取得一個指針, 指向cctb的數據。 
3.  *pc = 'J';              //cctb現在有了 "Jello" 這樣的內容。
         以上代碼沒有任何錯誤:創建一個常量對象並設以某值,而且只對它調用const成員函數。但終究還是改變了它的值。

         這種情況導出所謂的logical constness:一個const成員函數可以修改它所處理的對象內的某些bits,但只有在客戶端偵測不出的情況下才得如此,也就是自己可以改用戶不能改。

1.  class CTextBlock { 
2.  public: 
3.     ... 
4.     std::size_t length() const; 
5.  private: 
6.     char* pText; 
7.     std::size_t textLength; //最近一次計算的文本區塊長度。 
8.     bool lengthIsValid; //目前的長度是否有效。 
9.  }; 
10. std::size_t CTextBlock::length() const 
11. { 
12.    if (!lengthIsValid) { 
13.       textLength = std::strlen(pText);
14.       //錯誤!在const成員函數內不能對私有變量textLength進行修改 
15.       lengthIsValid = true;      
16.       //錯誤!在const成員函數內不能對私有變量lengthIsValid進行修改
17.    }                          
18.    return textLength;  }
         解決辦法很簡單:利用C++ 中的mutable(可變的)修飾符,mutable釋放掉non-static成員變量的bitwiseconstness約束:

1.  class CTextBlock { 
2.  public: 
3.     ... 
4.     std::size_t length() const; 
5.  private: 
6.     char* pText; 
7.     mutable std::size_t textLength; //這些成員變量可能總是會被更改,                
8.     mutable bool lengthIsValid;     //即使在const成員函數內。
9.  };                              //現在,剛才的length函數就可以了~
l  編譯器強制實施bitwise constness,但你編寫程序時應該使用"概念上的常量性"(conceptual constness)。

 

在const和non-const成員函數中避免重復

         對於"bitwise-constness非我所欲"的問題,mutable是個解決辦法,但它不能解決所有的const相關難題。舉個例子,假設TextBlock(和 CTextBlock)內的operator[] 不單只是返回一個reference指向某字符,也執行邊界檢驗(boundschecking)、志記訪問信息(loggedaccess info.)、甚至可能進行數據完善性檢驗。把所有這些同時放進const和non-const operator[] 中,導致兩個版本的operator[]及大量的代碼重復。

         真正該做的是實現operator[]的機能一次並使用它兩次,也就是說,令其中一個調用另一個。本例中constoperator[]完全做掉了non-const版本該做的一切,唯一的不同是其返回類型多了一個const資格修飾。

1.  class TextBlock { 
2.  public: 
3.    ... 
4.    const char& operator[](std::size_t position) const //一如既往 
5.    { 
6.      ... 
7.      return text[position]; 
8.    } 
9.    char& operator[](std::size_t position)    //現在只調用const op[] 
10.   { 
11.      return 
12.         const_cast<char&>(          //將op[]返回值的const轉除 
13.           static_cast<const TextBlock&>(*this)//為*this加上const 
14.               [position]                    //調用const op[] 
15.         ); 
16.   } 
17. ... 
18. };
         這裡共有兩次轉型:第一次用來為 *this添加const(這使接下來調用operator[]時得以調用const版本),第二次則是從constoperator[]的返回值中移除const。

         添加const的那一次轉型強迫進行了一次安全轉型(將non-const對象轉為const對象),所以我們使用static_cast。移除const的那個動作只可以藉由const_cast完成,沒有其他選擇。

         簡單來說就是non-const版本為了調用const版本先轉換常量性,應用const版本功能完畢後,為了符合non-const的返回值,再去除常量性。

         const成員函數承諾絕不改變其對象的邏輯狀態(logicalstate),non-const成員函數卻沒有這般承諾。如果在const函數內調用non-const函數,就是冒了這樣的風險:你曾經承諾不改動的那個對象被改動了。這就是為什麼"const成員函數調用non-const成員函數"是一種錯誤行為:因為對象有可能因此被改動。

l  當const和non-const成員函數有著實質等價的實現時,令non-const版本調用const版本可避免代碼重復。

 摘自 pandawuwyj的專欄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved