C++11 關鍵字 const 到底怎麼用?

MushRain發表於2021-12-11

Const 的作用及歷史

const (computer programming) - Wikipedia

一、歷史

按理來說,要想了解一件事物提出的原因,最好的辦法就是去尋找當時的歷史背景,以及圍繞這件事所發生的故事。

可是非常抱歉,我並沒沒有找到C語言中const 提出的背景,但是一個可以參考的歷史是,常量這種資料形式早在組合語言中就有所體現,組合語言中的constant 是一個確定的數值,在彙編階段就可以確定直接編碼在於指令程式碼中,不是儲存在暫存器中的可以變化的量。

常量是需求,C 語言沒理由不保留這個傳統,自然而然的const 關鍵字出現了。

二、C和C++的異同點

顧名思義,const 最基礎的作用就是保證資料不會被修改,僅僅可讀而已。這就好比一份沒有write 許可權的檔案一樣,只能遠觀而已。

const 是C語言32個關鍵字(C++中有49個)中的一個,主要起型別修飾的作用,可以理解為變數的屬性,比方說const int a = 10 從右往左看int a = 10 是定義並初始化了一個變數a 等於10,隨後使用const 修飾這個變數,告訴編譯器,這個變數是不可修改的。為了維護程式的安全性,由於const 一旦修飾就無法再更改了,那麼const int a; 會生成一個值隨機且永遠無法修改的量,這樣非常不安全,所以C 的編譯器會要求你必須在定義的時候就立馬初始化。從這裡也可以看出const關鍵字的強硬之處。

到底const該放在哪?

在詳細討論const的用法之前,必須首先明白,const 是C語言中的一個型別限定符(type quailier),是型別的一個部分,且const 越靠近誰,就修飾誰是常量型別。

從C語言的基礎資料型別來看,基本上可以抽象為一下幾個類別

  • 基礎資料型別(整型,浮點)

    • 對於基礎資料型別,使用const就單純定義為一個不可修改的量,此時由於不涉及其他的型別限制符,所以const放在哪裡都是有效的
    • const int i = 10 \(\Leftrightarrow\) int const i = 10 但是一般const在前。
  • 指標型別(指標)

    • 相比於基礎資料型別,指標型別存在很大的不同。
    1. 不使用const修飾的指標,此時表示該指標一定指向一個變數,當指向const修飾的變數是就會報錯

      const int a = 10;
      int * ptr = a; // error
      
    2. const 放在 int *前時,表示指標型別是 const int 型別,那麼依據指標型別的定義,該指標必定指向一個const int 型別的量,即常量。

      const int b = 100;
      const int * a = &b;
      
    3. const 放在 int * 後面時,int * const a ,顯然根據常規的指標型別的定義,我們只能推測出這是一個指向int型別的指標,那麼const起什麼作用呢?(見如下程式碼)

      int c = 10, b = 20;
      const int b = 30;
      int * const a = &c; // 此處沒有報錯,證明*號前面是指標型別,這條真理沒錯
      //1.  可當我們嘗試修改指向的時候
      a = &b; 
      // 此處會報錯!這表明const靠近變數名的時候表示指標指向一個變數後就無法更改了
      
      //2. 如果一開始就不初始化int * const a呢?
      int * const a; // 此處會報錯
      
      //3. 如果嘗試讓他指向一個const量呢?
      int * const a = &b; // 此處會報錯
      
    4. 以此類推可以得到一個指向const變數的無法修改指向的指標

      const int b = 10;
      const int * const a = &b;
      

      所以可以給出總結

      1. const 靠近變數名的時候表示指標必須指向一個型別與指標型別相同的變數
      2. 一旦指向就無法更改指向
      3. 無法指向常量
  • 複雜資料型別(列舉,結構,共用)

    針對複雜型別,由於出現了簡單型別的巢狀,自然會有const 的巢狀關係,下面以結構體來舉例子

    1. const巢狀在結構體內部時。
    typedef struct a {
    	const int b;
    	int c;
    }A;
    
    int main() {
    	A aa;
    	aa.b = 10; // 此處會報錯
    }
    

    在C語言中,在結構體內部使用const修飾不會報錯的,但是此變數再也無法修改,意味著這是一個無效量,既無法初始化,也無法修改(但是得益於C++的物件導向機制,即使如此我們還是可以定義const並且給他賦值)。

    1. 當定義結構體的時候使用const
    const A bb;
    

    此時也會報錯,而且相對來說比上面還嚴重,此時結構體內部的所有值都是亂的,且無法修改。

而C++中由於引入了幾種新的程式設計模式,const 的作用範圍又進一步被擴充。

  • 類中屬性與成員函式

    1. 結構體的遺留問題(即類的常量屬性)

    這裡先來解決前面C語言中的結構體問題,需求是想在結構體內部定義const變數,知道結構體內部的變數是無法直接初始化的,而C++中結構體可以理解為類,只不過許可權不同而已,同樣可以擁有建構函式。

    那麼是不是可以在建構函式中初始化呢?(下面程式碼會報錯)

    struct a {
    	a() {
    		b = 100;
    	}
    	const int b;
    };
    

    不是我們想的那樣,不過也非常接近,對於初始化類的變數還有一種方法,使用初始化列表(類的初始化列表的優先順序是非常高的)

    struct a {
    	a() : b(100) {;}
    
      const int b;
    };
    

    或者還有

    struct a {
        const int b = 100;
    };
    

    利用C++特性直接賦值,而此段程式碼在C語言中會報錯,這也是C與C++不同的一個地方。

    如此就完美解決了結構體const量 問題。

    1. 類的靜態變數vs const 變數

    static 也是一個修飾符,確定的是變數的生存期。const覺得變數的可讀性,有這樣一條語句在類中和main函式中存在不同的意義

    static const int a; 
    // 此語句在main中會報錯,由於未初始化
    // 在類中不會
    

    這是由於static不會影響const的表達,在main函式中說明此變數就是const型別,確實需要立馬賦值。而在類中可以不那麼著急,可以把類中的static變數理解為一個申明,在類的外面或者裡面直接定義都可以,不會報錯。

    1. 函式const 以及類成員函式的const修飾

    普通函式的const

    函式const 首先想到的是const 變數返回值。但是這其實是沒太大意義的

    const修飾返回值其實完全沒有發揮作用,屬於無效修飾。同樣的使用const修飾形式引數的時候也是如此,並不會限定你傳入的是const還是普通變數,本質在於這一過程發生原因是由於值傳遞,不論是返回const 還是使用const 修飾形式引數,內部都發生了變數的建立與賦值

    const修飾形參的例子,

    int fun(const int a) {
    	// a = 10 會報錯
    	return a
    }
    
    int main() {
    	int c = 10;
    	int d = fun(c);  // 不會報錯
    }
    

    如上,c傳入的時候是把c的值拿到,然後函式壓棧,建立一個const int 變數a 且立馬初始化為c的值,如此就在函式內部生成了一個const 變數。跟傳入什麼值完全沒有關係。

    成員函式的const尾修飾

    這屬於C++的特性,成員函式尾巴加上一個const 限制此函式對物件的修改,且提高了程式碼的可讀性。

    class A {
    private:
    	int a;
    
    public:
    	static int B;
    	int getA() const {
    		A::B = 100; // 此處不會報錯
    		a = 100; // 這裡會報錯
    		return a;
    	}
    };
    
    int A::B = 100;
    

    使用const修飾成員函式會使該函式變成const member function 此型別無法修改物件的資料,但是可以修改可修改的靜態變數。

  • 引用

    引用相對來說沒有指標那麼多的變種,引用的const 修飾也僅僅侷限於讓引用變數無法修改指向這一點上。

  • 在補充一點
    const修飾類靜態整型變數的時候可以在類內部直接初始化(浮點數仍然是不行的)。

相關文章