C/C++中const關鍵字的用法及其與宏常量的比較

1.const關鍵字的性質

簡單來講:const關鍵字修飾的變量具備常屬性。 即它所修飾的變量不能被修改。html

2.修飾局部變量

1 const int a = 10;
2 int const b = 20;

這兩種寫法是等價的,都是表示變量的值不能被改變,須要注意的是,用const修飾變量時,必定要給變量初始化,不然以後就不能再進行賦值了,並且編譯器也不容許不賦初值的寫法:安全

在C++中不賦初值的表達一寫出來,編譯器即報錯,且編譯不經過。函數

在C中不賦初值的表達寫出來時不報錯,編譯時只有警告,編譯能夠經過。而當你真正給它賦值時纔會報錯,那麼沒有初值也不能被賦值這樣的變量有什麼用哪?post

1 const chsr* p = "qwerty"; //const用於修飾常量靜態字符串

若是沒有const的修飾,咱們可能會在後面有意無心的寫p[4]=’x’這樣的語句,這樣會致使對只讀內存區域的賦值,而後程序會馬上異常終止。有了const,這個錯誤就能在程序被編譯的時候就當即檢查出來,這就是const的好處。讓邏輯錯誤在編譯期被發現。(這個特性在C/C++中相同)spa

3.修飾指針

常量指針是指針所指向的內容是常量,不可被修改。debug

1 const int * n = &a;
2 int const * n = &a;

上面兩種寫法也是等價的,性質以下:3d

1)常量指針經過不能這個指針改變變量的值,可是能夠經過其餘的引用來改變變量的值的。指針

1 const int *n = &a;
2     *n = b;

上面的寫法報錯調試

1 int c = 3;
2 const int *n = &a;
3 a = 10;
4 a = c;

這樣賦值是能夠的。code

2)常量指針指向的值不能改變,可是指針自己能夠改變,即常量指針能夠指向其餘的地址。

1   int a = 1;
2    int b = 2;
3     const int *n = &a;
4     n = &b;

指針常量是指指針自己是個常量,不能在指向其餘的地址,寫法以下:

1     int a = 1;
2     int b = 2;
3     int * const n = &a;
4     *n = b;
5 b = a;

而這麼寫是錯誤的

1 int a = 1;
2 int b = 2;
3 int c = 3;
4 int * const n = &a;
5 n = &b;

它們的區別在於const的位置,能夠這樣記憶:const在「*」前面時它修飾(*n),而*n是n所指向的變量,因此是常量指針,const在「*」後面時它修飾(n),使指針變爲常量,因此是指針常量。

指向常量的常指針

1 const int * const p= &a;
2 int const * const p= &a;

指針指向的位置不能改變而且也不能經過這個指針改變變量的值,可是依然能夠經過變量賦值,或其餘的普通指針改變變量的值。

(這種用法在C和C++中是相同的。)

4.修飾引用

1 int a = 1;
2 int const &a = b;
3 const int &a = b;

兩種定義形式在本質上是同樣的

5.函數中使用const

(1)修飾函數參數

根據const修飾指針的特性,const修飾函數的參數也是分爲三種狀況

1 void StrCopy(char *strdes, const char *strsrc);//防止修改指針指向的內容

其中 strsrc是輸入參數,strdes是輸出參數。給 strsrc 加上 const 修飾後,若是函數體內的語句試圖改動 sresrc 的內容,編譯器將指出錯誤。

1 void swap ( int * const p1 , int * const p2 )  //防止修改指針指向的地址

 

指針p1和指針p2指向的地址都不能修改。

1 void test ( const int * const p1 , const int * const p2 )  //以上兩種的結合

另外當參數爲引用時

1 void function(const Class& Var); //引用參數在函數內不能夠改變 
2 void function(const TYPE& Var); //引用參數在函數內爲常量不可變 

(這樣的一個const引用傳遞和最普通的函數按值傳遞的效果是如出一轍的,他禁止對引用的對象的一切修改,惟一不一樣的是按值傳遞會先創建一個類對象的副本, 而後傳遞過去,而它直接傳遞地址,因此這種傳遞比按值傳遞更有效.另外只有引用的const傳遞能夠傳遞一個臨時對象,由於臨時對象都是const屬性, 且是不可見的,他短期存在一個局部域中,因此不能使用指針,只有引用的const傳遞可以捕捉到這個傢伙。)

(2)修飾函數返回值

若是給以「指針傳遞」方式的函數返回值加 const 修飾,那麼函數返回值(即指針)的內容不能被修改,該返回值只能被賦給加const 修飾的同類型指針。

1 const int * fun2()    //調用時 const int *pValue = fun2();  
2                             //咱們能夠把fun2()看做成一個變量,即指針內容不可變。 
3 c.int* const fun3()   //調用時 int * const pValue = fun2();  
4                             //咱們能夠把fun2()看做成一個變量,即指針自己不可變。 

const int fun1()   //這個其實無心義,由於參數返回自己就是賦值。

6.修飾類相關

(1)用const修飾的類成員變量,只能在類的構造函數初始化列表中賦值,不能在類構造函數體內賦值。

複製代碼
 1 class A
 2 {
 3 public:
 4     A(int x) : a(x)  // 正確
 5     {
 6          //a = x;    // 錯誤
 7     }
 8 private:
 9     const int a;
10 };
複製代碼

(2)const修飾成員函數

用const修飾的類成員函數,在該函數體內不能改變該類對象的任何成員變量, 也不能調用類中任何非const成員函數。通常寫在函數的最後來修飾。

複製代碼
 1 class A
 2 {
 3 public:
 4     int& getValue() const
 5     {
 6         // a = 10;    // 錯誤
 7         return a;
 8     }
 9 private:
10     int a;            // 非const成員變量
11 };
複製代碼
a. const成員函數不被容許修改它所在對象的任何一個數據成員。
b. const成員函數可以訪問對象的const成員,而其餘成員函數不能夠。

(3)const修飾類對象/對象指針/對象引用

用const修飾的類對象表示該對象爲常量對象,該對象內的任何成員變量都不能被修改。對於對象指針和對象引用也是同樣。
所以不能調用該對象的任何非const成員函數,由於對非const成員函數的調用會有修改爲員變量的企圖。

複製代碼
 1 class A
 2 {
 3  public:
 4     void funcA() {}
 5     void funcB() const {}
 6 };
 7 int main
 8 {
 9     const A a;
10     a.funcB();    // 正確
11     a.funcA();    // X
12 
13     const A* b = new A();
14     b->funcB();    // 正確
15     b->funcA();    // X
16 }
複製代碼

(4)在類內重載成員函數

1 class A
2 {
3 public:
4     void func() {}
5     void func() const {}   // 重載
6 };

另外,const數據成員只在某個對象生存期內是常量,而對整個類而言是可變的,由於類能夠建立多個對象,不一樣對象的const數據成員值能夠不一樣。

複製代碼
class A
{
public:
    A(int size) 
         : _size(size)  // 正確
    {}
private:
    const int _size;
};
A a(10);  //對象a的_size值爲10
A b(20);  //對象b的_size值爲20
複製代碼

那麼,怎樣才能創建在整個類中都恆定的常量呢?用枚舉常量。

複製代碼
class A
{
public:
   enum{SIZE1 = 10, SIZE2 = 20};//枚舉常量
private:
     int arr1[SIZE1];
     int arr2[SIZE2];
};
複製代碼

枚舉常量不會佔用對象的存儲空間,它們在編譯時被所有求值。但缺點是隱含數據類型是隻能整數,最大值有限,且不能表示浮點數。

7.修飾全局變量

全局變量的做用域是整個文件,咱們應該儘可能避免使用全局變量,覺得一旦有一個函數改變了全局變量的值,它也會影響到其餘引用這個變量的函數,致使除了bug後很難發現,若是必定要用全局變量,咱們應該儘可能的使用const修飾符進行修飾,這樣方式沒必要要的覺得修改,使用的方法與局部變量是相同的。

8.const常量與宏常量的區別

(1).便於進行類型檢查

const常量有數據類型,而宏常量沒有數據類型。編譯器能夠對前者進行類型安全檢查,而對後者只進行字符替換,沒有類型安全檢查,而且在字符替換時可能會產生意料不到的錯誤(邊際效應)。

1 //例子:
2 void f(const int i) { .........}   //對傳入的參數進行類型檢查,不匹配進行提示

(2)能夠節省空間,避免沒必要要的內存分配

const定義常量從彙編的角度來看,只是給出了對應的內存地址,而不是象#define同樣給出的是當即數,因此,const定義的常量在程序運行過程當中只有一份拷貝,而#define定義的常量在內存中有若干個拷貝。

複製代碼
1 #define PI 3.14159         //常量宏
2 const doulbe  Pi=3.14159;  //此時並未將Pi放入ROM中
3               ......
4 double i=Pi;   //此時爲Pi分配內存,之後再也不分配!
5 double I=PI;  //編譯期間進行宏替換,分配內存
6 double j=Pi;  //沒有內存分配
7 double J=PI;  //再進行宏替換,又一次分配內存!
複製代碼

(3)提升了效率 

 宏定義是一個「編譯時」概念,在預處理階段展開,不能對宏定義進行調試,生命週期結束於編譯時期。const常量是一個「運行時」概念,在程序運行時使用,相似於一個只讀數據。

編譯器一般不爲普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成爲一個編譯期間的常量,沒有了存儲與讀內存的操做,使得它的效率也很高

(4)能夠保護被它修飾的東西

防止意外的修改,加強程序的健壯性。

1 void f(const int i) { i=10;//error! } //若是在函數體內修改了i,編譯器就會報錯
2      

(5)爲函數重載提供了一個參考

複製代碼
1 class A
2 {
3            ......
4   void f(int i)       {......} //一個函數
5   void f(int i) const {......} //上一個函數的重載
6            ......
7 };
複製代碼

(6)定義域不一樣

複製代碼
 1 void f1 ()
 2 {
 3     #define N 12
 4     const int n 12;
 5 }
 6 void f2 ()
 7 {
 8     cout<<N <<endl; //正確,N已經定義過,不受定義域限制
 9     cout<<n <<endl; //錯誤,n定義域只在f1函數中。若想在f2中使用需定義爲全局的
10 }
複製代碼

(7)作函數參數

   宏定義不能做爲參數傳遞給函數;const常量能夠在函數的參數列表中出現。

9.const_cast

const_cast運算符用來修改類型的const或volatile屬性。
(1)常量指針被轉化成很是量的指針,而且仍然指向原來的對象;
(2)常量引用被轉換成很是量的引用,而且仍然指向原來的對象。

複製代碼
1 void func()
2 {
3     const int a = 10;
4     int* p = const_cast<int*> (&a);
5     *p = 20;
6     std::cout<<*p;    // 20
7     std::cout<<a;     // 10
8 }
複製代碼

注:C++中使用const 常量而不使用宏常量,即const 常量徹底取代宏常量。

ps:高質量C/C++第5章、第11章。

 
分類:  C++ , 個人C
標籤:  const#define
相關文章
相關標籤/搜索