C++中const與指針、引用的分析(轉自china_unix GP-King)

    C++中函數的參數相比C語言中的函數參數要複雜的多,其中主要的緣由是C++中引入了引用以及const限定符。這兩個對象的引入,使得C++中的函數參數變得異常的複雜多變,每一種類型都具備比較適合的使用範圍。
本文詳細的分析const與指針、引用在一塊兒存在狀況下的狀況分析。
 
首先介紹引用
    引用是對象的別名,必須在初始化的過程當中與一個具體的對象綁定起來,綁定完成之後就不再可以修改了,引用貌似和指針有很大的類似性,可是引用是引用,是一個別名,而指針是一個變量,只是變量中保存的是對象的地址,引用並不分配新的內存空間。所以一個引用與一塊內存區域綁定,是這塊內存區域的別名,就如同數組名同樣,數組是內存中的一塊區域,數組名是這塊區域的名字,可是在內存中並不存在額外的區域保存數組名。引用就是一塊內存區域的別名。C++中變量實質上就是一塊內存區域,咱們在用&i就能夠獲得變量i的地址,也就是說變量是這塊區域的名字。對變量的操做實質上就是對這塊內存中內容的操做。別名是這塊區域的另外一個名字。採用任何一個名字對這塊區域修改都會影響區域的內容。


  1. int vari = 10;
  2. int &refr = vari;
  3. vari = 20;
  4. cout << refr << " " << vari << endl;
  5. refr = 30;
  6. cout << refr << " " << vari << endl;
上面的 int &refr = vari實質上就是變量vari的別名,也就是保存vari變量地址的別名,這個地址中保存的是一個int類型的數據,數據能夠經過vari來操做,能夠修改。由於refr是一個別名,也能夠經過該別名對這塊內存區域進行操做。也就是說別名的操做就是針對一塊特定的內存區域,這個經過變量操做的效果是一致的。
其次說說const限定符
    const的引入使得在函數操做中的一些問題複雜了,可是更加的合理了,const限定符是指上是指一個常量。這個常量在通常的狀況下就是限定變量不能修改,可是當這個限定符與引用、指針結合在一塊兒時,使得不少的問題不在明朗。
 
     一、const與指針


  1. int *ptr;
  2. const int *ciptr;
  3. int const *icptr;
  4. int * const cptr;
  5. const int * const cicptr;
上面是關於const與指針結合時的各類狀況,這並不僅是C++中常常遇到的問題,在C語言中也會有相似的討論,雖然const並非C語言中的關鍵字。
int * ptr         是指定義一個指向int 類型的指針ptr。
const int *ciptr 是指定義一個指向const int 類型的指針ciptr,這是const 限定的是(* ciptr),也就是對指針解引用,即const限定的就是數據自己,而非指針。因此ciptr是一個指向常int型數據的指針。
int const * icptr其實和上面的 const int *ciptr是一致的由於const只是一個限定符,在int前仍是後都 沒有影響,他限定的仍然是(*icptr),並非icptr,也就是icptr也是指向常int型數據的指針, 也就是說在icptr這個指針看來,它指向的數據是常數,是不能改變的,可是是否真的不能改變,須要依據實際的類型的分析,只是不能經過這個指針來改變。也就是說該指針是一個自覺得本身指向常量的指針。
int * const cptr 這時候咱們仍是結合const的位置分析,const限定的是cptr,cptr咱們能夠知道是一個指針,也就是說const限定的是一個指針,而不是指針指向的數據,也就是說這種定義實質上是定義一個常指針,也就是指針指向的地址是不變的,也就是cptr是不能被賦值的,不能採用這個指針指向其餘的對象或者地址。可是這個地址中的數據並非常數,是能夠改變的,能夠經過對*cptr進行修改。這種指針實質上也就使得指針的效果大大減少,並不經常使用。
const int * const cicptr 這種定義結合上面的分析咱們知道是一個指向常量的常指針,也就說這個指針指向的地址是一個常地址,指針不能指向其餘的對象或者地址。 同時對指針cicptr來講,我指向的這個地址中的內容也是不能修改的,是一個常量,是不能修改的,可是該地址的數據可否修改還須要進行實際的分析。
 
     二、關於指針的初始化、賦值的問題
    對於const類型的數據,若是須要定義指針,這時候只能定義const類型的數據,這是爲何呢?由於對於const對象來講,數據是確定不能修改的,若是定義指向非const的指針,程序員可能就會經過指針來修改對象的值,可是const對象是不能被修改的,確定會出現錯誤。所以const的變量只能採用指向常量的指針來指向。通常來講若是將一個非const指針指向了一個const對象,編譯的過程當中就會拋出以下的錯誤:
  1. invalid conversion from ‘const int*’ to ‘int*
    可是對於指向常量的指針並不必定指向的數據就是常量,這是一個 很是重要的技術點,指向常量的指針指向的數據只是針對這個指針而言,他認爲本身指向的數據是常量,休想經過他來修改指向的對象。也就是說指向常量的指針在初始化、賦值的時能夠初始化或者賦值爲非const變量的地址,便可以指向非const的對象,只是不能經過該指針來修改對象罷了。
    同時須要注意:對於指向const的指針,初始化過程比較方便,不要求是const對象的地址,能夠採用非const對象的地址初始化,甚至還能夠採用指向非const的指針直接賦值初始化,這時指針本身認爲本身指向的對象是常量。可是不能將指向const的指針直接賦值給指向非const的指針,若是不當心賦值也會出現上面出現的問題。下面參看一段小的代碼,說明其中的一些問題。


  1. #include <iostream>
  2. #include <string>
  3. #include <vector>

  4. using namespace std;

  5. int main()
  6. {
  7.         int num = 20; 
  8.         const int array_size = 10; 

  9.     
  10.         int *pnum = &num;
  11.         const int * cpnum = &num;
  12.         /*const int *指針能夠採用int *指針直接初始化*/
  13.         const int *csize1 = pnum; 


  14.         /*可是int * 指針不能採用const int *製做初始化*/
  15.         //int *psize = &array_size;
  16.         /*const類型數據只能採用指向const的指針來指向*/
  17.         const int *csize = &array_size;

  18.         cout << "Before change..." << endl;
  19.         cout << "The num of num = " << num << endl;
  20.         cout << "*pnum = " << *pnum << " " 
  21.                 << "*cpnum = " << *cpnum << " " 
  22.                 << "csize1 = " << *csize1 << endl;

  23.         num = 30; 
  24.     
  25.         cout << "After changed..." << endl;
  26.         cout << "The num of num = " << num << endl;
  27.         cout << "*pnum = " << *pnum << " " 
  28.                 << "*cpnum = " << *cpnum << " " 
  29.                 << "csize1 = " << *csize1 << endl;

  30.         return 0;
  31. }
 
    從上面的結果咱們能夠知道指向const的指針能夠採用非const變量的地址進行初始化或者賦值操做,同時也能夠採用指向非const指針直接初始化指向const的指針。同時指向const的指針不能賦值給指向非const的指針,會出現const int *不能轉換到int *的問題。

     三、const與引用
    關於const和引用在一塊兒狀況下也存在一些相似於指針的狀況,可是畢竟引用相比指針要簡單,這時候狀況也比較簡單.可是我認爲分析引用應該與分析指針是同樣也存在相似的問題。可是引用就只有兩種,非const的引用和const的引用。


  1. int num = 10;

  2. int &newname = num;
  3. const int &othername = num;
   引用主要是上面的兩種,這兩種的區別相對來講比較大,並且加入了const限定符之後,引用的能力每每變的更加的強大。
   通常來講對於const對象而言,通常只能採用const引用,這與前面的const對象只能採用指向const對象的緣由是同樣的,若是對引用沒有進行限定,可能會經過引用修改數據,這是不容許的。也就是說const引用與指向const對象的指針有必定的類似性,即不能經過這個引用或者指針來修改原來的數據。保證數據的const特性。也就是說非const引用不能引用const對象,若是不當心引用編譯器會出現下面的錯誤:
  1. invalid initialization of reference of type ‘int&’ from expression of type ‘const int’
所以非const引用只能針對非const的同類型數據。這是須要注意的。好比string,和字符串字面值都不能直接引用。由於類型不相同,這是在C++函數定義中常常出現的問題,在後期的博文中再分析。 在引用中加入const的就是對於這個引用而言,不能經過本身來修改原始的數據,這與指向const的指針有很大的類似性,
 
    可是每每const引用的初始化並不必定要去對象是const的,甚至能夠是不一樣類型的對象,這種優越性是連指針(指針只能指向同一類型的數據,若是必定要指向須要強制類型轉換)都沒有的,也就是能夠將不一樣類型的非const或者const對象來初始化一個const引用。可是這個const限定符就限定了該引用指向的對象是不能經過該引用來修改的。若是嘗試採用const的引用進行修改,編譯器會出現以下的錯誤:
  1. error: assignment of read-only reference...
    綜合上面的描述可知: 非const引用只能綁定到該引用同類型(string和字符串字面值(const char *)之間都不能夠)的非const對象,而const引用則能夠綁定到任意的一種對象上(非const、const、甚至不一樣類型),這種差異在函數的參數中有較大的體現。
    經過下面的例子來講明一下上面的分析:


  1. #include <iostream>
  2. #include <string>
  3. #include <vector>

  4. using namespace std;

  5. int main()
  6. {
  7.         int num = 20;
  8.         const int array_size = 10;

  9.         int &pnum = num;
  10.         const int &cpnum = num;
  11.         /*採用引用直接初始化const類型的引用*/
  12.         const int &csize1 = pnum;

  13.         /*const的變量不能採用非const的引用*/
  14.         //int &psize = array_size;
  15.         /*const類型數據只能採用指向const的指針來指向*/
  16.         const int &csize = array_size;

  17.         cout << "Before change..." << endl;
  18.         cout << "The num of num = " << num << endl;
  19.         cout << "pnum = " << pnum << " "
  20.                 << "cpnum = " << cpnum << " "
  21.                 << "csize1 = " << csize1 << endl;

  22.         num = 30;
  23.         cout << "After the first changed..." << endl;
  24.         cout << "The num of num = " << num << endl;
  25.         cout << "pnum = " << pnum << " " 
  26.                 << "cpnum = " << cpnum << " " 
  27.                 << "csize1 = " << csize1 << endl;
  28.    
  29.         /*經過引用修改變量的值*/
  30.         pnum = 40;
  31.         cout << "After the second changed..." << endl;
  32.         cout << "The num of num = " << num << endl;
  33.         cout << "pnum = " << pnum << " "
  34.                 << "cpnum = " << cpnum << " "
  35.                 << "csize1 = " << csize1 << endl;

  36.         /*不能採用const的引用修改對象,
  37.         *這與指向const的指針特性的類似處*/
  38.         /*
  39.         csize1 = 50;
  40.         cout << "After the second changed..." << endl;
  41.         cout << "The num of num = " << num << endl;
  42.         cout << "pnum = " << pnum << " " 
  43.                 << "cpnum = " << cpnum << " " 
  44.                 << "csize1 = " << csize1 << endl;
  45.         */

  46.         double dnum = 10.1;
  47.         /*非const的引用只能綁定相同類型的對象*/
  48.         //int &dname = dnum;

  49.         /******************************************
  50.         *const引用能夠綁定不一樣類型的對象,
  51.         *所以const引用就能更加方便的做爲函數的形參
  52.         *******************************************/
  53.         const int &dothername = dnum;

  54.         return 0;
  55. }

上面的實驗結果基本上符合分析的結論。
 
總結
    const的使得引用與指針的變化更加複雜,整體而言,const主要是保證了經過指針或者引用不修改原始的數據,可是至於原始的數據是否能夠修改,這就須要參看數據的類型。
    在存在const的對象中,只能採用包含限定符const的引用或者指向const的指針來操做。
    const的引用比較強大,初始化的過程當中能夠採用任意的對象,const對象,非const對象,甚至其餘類型的數據。const引用支持隱式類型轉換。而指向const的指針則不能,只能指向同一類型的數據,可是能夠採用強制類型轉換,初始化或者賦值過程當中對數據類型沒有要求,能夠是const對象的地址,也能夠是非const對象的地址。
     const引用和指向const對象的指針都是本身覺得本身指向的對象是不能修改的,採用const的指針或者引用就能避免原始數據修改。
相關文章
相關標籤/搜索