C++中const關鍵字 理解


const:符號常量  使用符號常量寫出的代碼更容易維護;指針是邊讀邊移動,而不是邊寫邊移動,許多函數參數是只讀不寫的。

  const最常見用途是作為數組的界和switch分情況標號(也可以用枚舉符代替),分類如下:

 

  常變量:  const 類型說明符 變量名

  常引用:  const 類型說明符 &引用名

  常對象:  類名 const 對象名

  常成員函數:  類名::fun(形參) const

  常數組:  類型說明符 const 數組名[大小]    

  常指針:  const 類型說明符* 指針名 ,類型說明符* const 指針名

在常變量(const 類型說明符 變量名)、常引用(const 類型說明符 &引用名)、常對象(類名 const 對象名)、 常數組(類型說明符 const 數組名[大小]), const” 與 “類型說明符”或“類名”(其實類名是一種自定義的類型說明符) 的位置可以互換。如:

     const int a=5;   與   int const a=5; 等同

     類名 const 對象名  與   const 類名 對象名 等同

 

用法1:常量
    取代了C中的宏定義,聲明時必須進行初始化(!c++類中則不然)。const限制了常量的使用方式,並沒有描述常量應該如何分配。如果編譯器知道了某const的所有使用,它甚至可以不為該const分配空間。最簡單的常見情況就是常量的值在編譯時已知,而且不需要分配存儲。―《C++ Program Language》
    用const聲明的變量雖然增加了分配空間,但是可以保證類型安全。
    C標准中,const定義的常量是全局的,C++中視聲明位置而定。

用法2:指針和常量
    使用指針時涉及到兩個對象:該指針本身和被它所指的對象。將一個指針的聲明用const“預先固定”將使那個對象而不是使這個指針成為常量。要將指針本身而不是被指對象聲明為常量,必須使用聲明運算符*const。
    所以出現在 * 之前的const是作為基礎類型的一部分:
char *const cp; //到char的const指針
char const *pc1; //到const char的指針
const char *pc2; //到const char的指針(后兩個聲明是等同的)
    從右向左讀的記憶方式:
cp is a const pointer to char. 故cp不能指向別的字符串,但可以修改其指向的字符串的內容

pc2 is a pointer to const char. 故*pc2的內容不可以改變,但pc2可以指向別的字符串

且注意:允許把非 const 對象的地址賦給指向 const 對象的指針,不允許把一個 const 對象的地址賦給一個普通的、非 const 對象的指針。(C++語言強制要求指向const對象的指針也必須具有const特性)

用法3:const修飾函數傳入參數
    將函數傳入參數聲明為const,以指明使用這種參數僅僅是為了效率的原因,而不是想讓調用函數能夠修改對象的值。同理,將指針參數聲明為const,函數將不修改由這個參數所指的對象。
    通常修飾指針參數和引用參數:
void Fun( const A *in); //修飾指針型傳入參數
void Fun(const A &in); //修飾引用型傳入參數

用法4:修飾函數返回值
    可以阻止用戶修改返回值。返回值也要相應的付給一個常量或常指針。

若函數的返回值是指針,且用const修飾,則函數返回值指向的內容是常數,不可被修改,此返回值僅能賦值給const修飾的相同類型的指針。如:

1  const int * f1(){
2      int * p;
3      p = new int;
4      *p = 1;
5     return p;
6  }
7  int main(){
8     const int * p1;
9     p1 = f1();
10    return 0;
11 }

 

若第8行改為int *p1;則編譯時報錯:“[8] error: invalid conversion from 'const int*' to 'int*'” (編譯器code::block);

若主函數改為:

7  int main(){
8     const int * p1;
9     p1 = f1();
10    *p1 = 2;
11    return 0;
12 }

 


則編譯時報錯:"[10] error: assignment of read-only location '* p1'"  (編譯器code::block);

如果函數返回值是數值(by value),因C++中,返回值會被復制到外部臨時的存儲單元中,故const 修飾,中沒有任何價值的。例:不要把函數int fun1() 寫成const int func1()。
同理不要把函數A GetA(void) 寫成const A GetA(void),其中A 為用戶自定義的數據類型。

如果返回值是對象,將函數A fun2() 改寫為const A & fun2()的確能提高效率。但此要注意,要確定函數究竟是想返回一個對象的“copy”,還是僅返回對象的“別名”即可,否則程序會出錯。

 

用法5:const修飾成員函數(c++特性)
const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數;
const對象的成員是不能修改的,而通過指針維護的對象確實可以修改的;
const成員函數不可以修改對象的數據,不管對象是否具有const性質。編譯時以是否修改成員數據為依據進行檢查。

 

  面向對象程序設計中,為了體現封裝性,通常不允許直接修改類對象的數據成員。若要修改類對象,應調用公有成員函數來完成。為了保證const對象的常量性,編譯器須區分不安全與安全的成員函數(即區分試圖修改類對象與不修改類對象的函數)。例如,

1 const Screen blankScreen;
2 blankScreen.display(); // 對象的讀操作
3 blankScreen.set(‘*’); // 錯誤:const類對象不允許修改

        在C++中,只有被聲明為const的成員函數才能被一個const類對象調用。

        要聲明一個const類型的類成員函數,只需要在成員函數參數列表后加上關鍵字const,例如,

1 class Screen {
2 public:
3 char get() const;
4 };

 


        在類體之外定義const成員函數時,還必須加上const關鍵字,例如

1 char Screen::get() const {
2 return _screen[_cursor];
3 }

 


        若將成員成員函數聲明為const,則該函數不允許修改類的數據成員。例如,

1 class Screen {
2 public:
3 int ok() const {return _cursor; }
4 int error(intival) const { _cursor = ival; }
5 };

 


        在上面成員函數的定義中,ok()的定義是合法的,error()的定義則非法。

        值得注意的是,把一個成員函數聲明為const可以保證這個成員函數不修改數據成員,但是,如果據成員是指針,則const成員函數並不能保證不修改指針指向的對象,編譯器不會把這種修改檢測為錯誤。例如,

 1 class Name {
 2   public:
 3     void setName(const string &s) const;
 4   private:
 5     char *m_sName;
 6 };
 7 
 8 void setName(const string &s) const {
 9   m_sName = s.c_str(); // 錯誤!不能修改m_sName;
11   for (int i = 0; i < s.size(); ++i) 
12     m_sName[i] = s[i]; // 不好的風格,但不是錯誤的
13 }

 


        雖然m_Name不能被修改,但m_sName是char *類型,const成員函數可以修改其所指向的字符。

        const成員函數可以被具有相同參數列表的非const成員函數重載,例如,

1 class Screen {
2 public:
3 char get(int x,int y);
4 char get(int x,int y) const;
5 };

 


        在這種情況下,類對象的常量性決定調用哪個函數。

const Screen cs;
Screen cc2;
char ch = cs.get(0, 0); // 調用const成員函數
ch = cs2.get(0, 0); // 調用非const成員函數

 


小結:

1)const成員函數可以訪問非const對象的非const數據成員、const數據成員,也可以訪問const對象內的所有數據成員;

2)非const成員函數可以訪問非const對象的非const數據成員、const數據成員,但不可以訪問const對象的任意數據成員;

3)作為一種良好的編程風格,在聲明一個成員函數時,若該成員函數並不對數據成員進行修改操作,應盡可能將該成員函數聲明為const 成員函數。

 

用const 修飾函數的參數 

如果參數作輸出用,不論它是什么數據類型,也不論它采用“指針傳遞”還是“引用傳遞”,都不能加const 修飾,否則該參數將失去輸出功能。const 只能修飾輸入參數:
如果輸入參數采用“指針傳遞”,那么加const 修飾可以防止意外地改動該指針,起到保護作用
例如StringCopy 函數:
 
void StringCopy(char *strDestination, const char *strSource);
 
其中strSource 是輸入參數,strDestination 是輸出參數。給strSource 加上const修飾后,如果函數體內的語句試圖改動strSource 的內容,編譯器將指出錯誤。
如果輸入參數采用“值傳遞”,由於函數將自動產生臨時變量用於復制該參數,該輸入參數本來就無需保護,所以不要加const 修飾。
例如不要將函數void Func1(int x) 寫成void Func1(const int x)。同理不要將函數void Func2(A a) 寫成void Func2(const A a)。其中A 為用戶自定義的數據類型。
對於非內部數據類型的參數而言,象void Func(A a) 這樣聲明的函數注定效率比較底。因為函數體內將產生A 類型的臨時對象用於復制參數a,而臨時對象的構造、復制、析構過程都將消耗時間。
為了提高效率,可以將函數聲明改為void Func(A &a),因為“引用傳遞”僅借用一下參數的別名而已,不需要產生臨時對象。但是函數void Func(A &a) 存在一個缺點:
“引用傳遞”有可能改變參數a,這是我們不期望的。解決這個問題很容易,加const修飾即可,因此函數最終成為void Func(const A &a)。
以此類推,是否應將void Func(int x) 改寫為void Func(const int &x),以便提高效率?完全沒有必要,因為內部數據類型的參數不存在構造、析構的過程,而復制也非常快,“值傳遞”和“引用傳遞”的效率幾乎相當。
問題是如此的纏綿,我只好將“const &”修飾輸入參數的用法總結一下。
對於非內部數據類型的輸入參數,應該將“值傳遞”的方式改為“const 引用傳遞”,目的是提高效率。例如將void Func(A a) 改為void Func(const A &a)。
 對於內部數據類型的輸入參數,不要將“值傳遞”的方式改為“const 引用傳遞”。否則既達不到提高效率的目的,又降低了函數的可理解性。例如void Func(int x) 不應該改為void Func(const int &x)。

 

2 用const 修飾函數的返回值

如果給以“指針傳遞”方式的函數返回值加const 修飾,那么函數返回值(即指針)的內容不能被修改,該返回值只能被賦給加const 修飾的同類型指針。
例如函數:
const char * GetString(void);
如下語句將出現編譯錯誤:
char *str = GetString();
正確的用法是
const char *str = GetString();
如果函數返回值采用“值傳遞方式”,由於函數會把返回值復制到外部臨時的存儲單元中,加const 修飾沒有任何價值。
例如不要把函數int GetInt(void) 寫成const int GetInt(void)。
同理不要把函數A GetA(void) 寫成const A GetA(void),其中A 為用戶自定義的數據類型。
如果返回值不是內部數據類型,將函數A GetA(void) 改寫為const A & GetA(void)的確能提高效率。但此時千萬千萬要小心,一定要搞清楚函數究竟是想返回一個對象的“拷貝”還是僅返回“別名”就可以了,否則程序會出錯。
函數返回值采用“引用傳遞”的場合並不多,這種方式一般只出現在類的賦值函數中,目的是為了實現鏈式表達。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM