CC++的const常量總結

所謂常量,就是在程式碼執行過程中值恆定不變的識別符號,該識別符號的值可以是一個常數,也可以是字串。

在C/C++中,通常使用define宏定義或者const來定義常量,比如:

#define PI 3。1415926const int a = 5;

接下來我們來總結const常量的相關用法與注意事項。

01

為什麼使用常量?

1. 使用常量可增加程式的可讀性。

程式設計師應該都有這種經歷:

看別人的程式碼時為程式碼中某個數字而糾結,不知道這個數字表示什麼意義,甚至自己寫的程式碼,過一段時間再回頭看,有可能都不記得程式碼裡某個數字代表什麼意思

。比如計算一天包含的秒數,如果按照下式計算,雖然沒錯,但很可能會讓人懵逼。24代表什麼?兩個60又分別代表什麼?

int s = 24*60*60;

比較推薦的做法是類似這樣的,這樣看起來就比較清晰了:

const int oneday_hour = 24;const int onehour_minute = 60;const int oneminute_second = 60;const int oneday_second = oneday_hour * onehour_minute * oneminute_second;

2. 使用常量可減少出錯的機率。

程式設計師寫程式碼時,經常需要在多個地方使用同一個數字或者同一個字串,如果都是直接輸入數字或者字串,萬一某個地方寫錯了,造成該處與其它地方的數字或字串不一致,很可能導致嚴重bug。

比如以下程式碼,多個地方要用到Π值3。1415926,如果把3。1415926定義為一個常量,使用起來就方便多了,也不容易出錯:

//不好的使用示例double a = sin(3。1415926*5);double b = cos(3。1415926*10);double b = sin(3。1415926*100)*cos(3。1415926/2);//推薦的使用示例const double PI = 3。1415926;double a = sin(PI*5);double b = cos(PI*10);double b = sin(PI*100)*cos(PI/2);

3. 使用常量可以減少程式碼維護的工作量。

使用常量的情況下,程式設計師維護程式碼時如果要修改程式碼中某個數值,他只需要修改常量的值,那麼程式碼中多處用到該常量的地方也就自己改了過來,而不需要程式設計師去一個地方一個地方地修改。

比如以下程式碼,計算b、c、d都要要到5。0這個數值,如果哪一天需求改變,需要把5。0改為20。0,在不好的示例中需要修改三行程式碼,在我們推薦的示例中只需要修改一行程式碼——把常量a的值改為20。0即可:

//不好的使用示例float b = 5。0*3;float c = 5。0 + 10;float d = exp(5。0);//推薦的使用示例const float a = 5。0;float b = a*3;float c = a + 10;float d = exp(a);

4. 使用常量可提升程式的健壯性

編寫程式碼時,我們經常希望某一個變數的值不被意外改變,或者某指標指向的物件不被意外改變,又或者某指標指向的物件的值不被意外改變(這裡可能有點懵逼,讀者暫時不用糾結,下文我們再詳說)。然而在程式碼的汪洋大海中,難免會有意外發生,我們能做的就是使用常量來減小這種意外發生的機率。

比如以下函式,指標p作為輸入引數,在該函式內部我們不希望p由其原本指向的地址被改為指向別的地址,同時希望指標p指向地址的值是可以改變的,那麼可以使用const來修飾p,這樣p指向的地址就不會被意外改變啦~

void func(int *const p){    int a = 3;        p = &a;  //這樣的操作是無效的,因為指標p被const修飾,其指向的地址不能被改變     *p = a;  //這樣的操作是有效的,p指向地址的值會被修改為a的值}

02

const常量與define常量的區別

在C/C++中,有const和define兩種定義常量的方式,不過const常量相比define常量有更多的優點:

1

。 const常量有資料型別,define宏定義常量沒有,編譯器會對const常量可以進行資料型別的安全檢查,但是對於define宏定義,編譯器只是將其進行字元替換,這樣的字元替換很容易出錯。比如以下程式碼,如果使用不加括號的宏定義,將不能正確計算(a+b)/5。0,而是變成計算a+(b/5。0)了。

//define宏定義的做法#define a 2。0#define b 9。0#define c1 a+b   //不好的定義方法#define c2 ((a)+(b))  //推薦的定義方法void func1(void){    float d1 = c1/5。0;  //本意是想計算(a+b)/5。0,但字元替換使計算變成了a+b/5。0    float d2 = c2/5。0; //正確計算了((a)+(b))/5。0 = (a+b)/5。0}//推薦的const常量的做法const float a = 2。0;const float b = 9。0;const float c = a + b;void func2(void){    float d = c/5。0;  //正確計算了(a+b)/5。0}

2

。 有一些除錯工具可以對const常量進行除錯,但卻無法除錯define宏定義常量。

因此,我們還是建議儘量使用const常量。

03

建議的const常量定義規則

1

。 const常量應在。c或。cpp檔案中定義,儘量不要在標頭檔案中定義,因為假如標頭檔案被多個。c或。cpp檔案包含,那麼定義於標頭檔案中的常量將被多次重複定義,很可能造成嚴重錯誤。比如以下程式碼:

推薦的做法,在。c或。cpp檔案中定義:

const int x = 15;

不建議的做法,在標頭檔案中定義:

#ifndef _TEST_H_#define _TEST_H_const int x = 15;#endif

雖然不建議在標頭檔案中定義常量,但是對於全域性常量,也就是我們不僅希望該常量能在其定義的。c或。cpp檔案中使用,還可以在別的。c或。cpp檔案中使用,那麼可以在標頭檔案中對其進行extern宣告,這樣一來只要別的。c或。cpp檔案include了該標頭檔案,它也就可以使用該常量了,比如以下程式碼:

#ifndef _TEST_H_#define _TEST_H_extern const int x;#endif

2

。 如果一個常量與別的常量有聯絡,在定義的時候儘量把它們的聯絡包含進去:

//不推薦的做法const int a = 5;const int b = 5*100;const int c = 5 + 5*100 - 64;//建議的做法,在定義時把a、b、c的聯絡包含了進去const int a = 5;const int b = a*100;const int c = a + b - 64;

04

容易混淆的const常量定義

1

。 某個量的值恆定不變:

const float x = 5。0;  //定義x並將x的值初始化為5。0之後,在程式執行期間x的值將保持5。0不變

2

。 指標指向的地址恆定不變,但可以透過指標來修改其指向地址的值:

float x = 1。23;//初始化指標p指向x的地址之後,p將不能改為指向其它地址float *const p = &x;  //無效操作float a = 6。3;p = &a;//有效操作*p = 100。0; //等效於x = 100。0

3

。 指標指向的地址可以改變,但不能透過指標來修改其指向地址的值,該情況與上述第2種情況相反:

float x = 1。23;//初始化指標p指向x的地址之後,將不能透過指標p來修改x的值float const *p = &x;  //有效操作float a = 6。3;p = &a;//無效操作*p = 100。0;  //這樣並不能修改x的值,x的值還是1。23

另外,以下兩種定義方式是等效的,都屬於此種情況:

float const *p;

const float *p;

05

const常量與類

在類內部定義的const常量,僅在該類的某個物件的生命週期內是恆定不變的,對於整個類而言卻是可以改變的,也即該類的不同物件可以將該常量初始化為不同的值。

宣告類時,其內部的const常量成員是不能被初始化的,比如以下程式碼是有問題的,宣告類A時len的值並不能被初始化為50,而是一個不確定的值。

class A{ const int len = 50; //實際len的值並不能被初始化為50,而是一個不確定的值 int x[len];}

不過類內部的const常量成員可以透過建構函式的初始化表進行初始化:

class A {   const int len;  A(int length);  //建構函式  };A::A(int length):len(length)  //建構函式的初始化表{ } A  a(100); // 物件a的len值被初始化為100 A  b(200); // 物件b的len值被初始化為200

如果非要定義在類的所有物件中都恆定不變的常量,可以透過以下兩種方法:

1

。 使用列舉來實現,如以下程式碼,不過這樣做的缺陷是列舉定義的len1和len2只能是整型數,不能是浮點數。

class A {  enum { len1 = 20, len2 = 58}; //len1和len2的值在所有物件中都是不變的 int x1[len1];   int x2[len2]; };

2

。 使用static關鍵字,加上static修飾之後,常量len不再儲存在儲存物件的區域(堆、棧等),而是儲存到了靜態儲存區,因此對所有該類的物件來說len是恆定不變的常量。

class A{   static const int len = 30;   int x[len];}

歡迎關注“萌萌噠程式猴”微信公眾號,接下來會不定時更新更加精彩的內容,敬請期待~