[C++, CPP 教學 教程 教材 Tutorial] 指標(Pointer)&指涉器(Reference)
YehYeh\'s Notepad yehyeh@gmail.com 

[C++, CPP] 指標(Pointer)、指涉器(Reference)

指標(Pointer)

  • 指標:用來儲存記憶體位址的變數 所以初始化時只能指派位址給指標,而不是值
  • 宣告:
    • 型別* 變數名稱;
    • 型別 *變數名稱;
    • 型別* 變數名稱1, *變數名稱2;
    • 型別 *變數名稱1,*變數名稱2;
  • int* myIntPtr;              //宣告一個指標變數
    int *myIntPtr1, *myIntPtr2; //宣告兩個指標變數
    int *myIntptr1, myIntVar;   //宣告1個指標變數,1個整數變數
    int* myIntptr1, myIntVar;   //宣告1個指標變數,1個整數變數
    
  • 宣告時使用的 * 表示宣告指標變數,並無取值運算子 * 的效果
    • 宣告時只能指派位址,而不能指派值
    • int myInt = 123;
      
      int *myIntPtr1 = myInt;     // X 宣告時不能指派值
      int *myIntPtr2 = 123;       // X 宣告時不能指派值
      
      int *myIntPtr3 = &myInt;    // O 宣告時可以指派位址
      *myIntPtr3 = 123;           // O 利用取值運算子指派值給指標myIntPtr3
      *myIntPtr3 = myInt;         // O 利用取值運算子指派值給指標myIntPtr3
      
  • 指標的型態和位址儲存資料的型態要一致(EX:都是int),否則會產生編譯錯誤
  • 指標的功能
    1. 快速存取陣列
    2. 存取函式外的資料
    3. 動態分配使用的記憶體
Δ 回到最上方

取址運算子(Address-Of Operator)

  • 取址運算子(Address-Of Operator):「&
    • 取得變數的儲存位置
  • &運算元
  • int myInt = 123;
    
    int* pInt1;             //宣告一個指標
    pInt1 = &myInt;         //將myInt的位址指派給pInt1
    
    int* pInt2 = &myInt;    //宣告一個指標,並指派初始值為myInt的位址
    
    //顯示 pInt1儲存的位址 myInt的位址 myInt的值 pInt1儲存的位址記錄的值
    //顯示:0012FF60 0012FF60 123 123
    cout << pInt1 << " " << &myInt << " " << myInt << " " << *pInt1 << endl;
    

同樣的程式在不同電腦上配置到的位址可能是不同的

受硬體(如有的電腦裝2G RAM、有的裝4G RAM,32位元CPU、64位元CPU)、作業系統、安裝軟體不同(消耗不同記憶體)、...等影響,所以相同程式在不同電腦所配置到的記憶體可能會不同。

Δ 回到最上方

取值運算子(Dereference Operator)

  • 取值運算子(Dereference Operator):「*
    • 又叫間接運算子(Indirection Operator)
    • 配合指標變數,存取指標所指向的記憶體中記錄的內容
  • *運算元
  • int myInt = 123;
    int* pInt = &myInt;          //宣告pInt指標,並將myInt的位址指派給pInt
    
    cout << pInt << " = " << *pInt << endl;     //顯示:0012FF60 = 123
    
    *pInt = 321;                                //修改位址0012FF60上的值
    cout << &myInt << " = " << myInt << endl;   //顯示:0012FF60 = 321
    
Δ 回到最上方

指標的初始化

  • 指標沒有初始化時,不一定指向那裡,若改寫其值,會發生難以預料的後果
  • 使用0來初始化,表示不指向任意地方
  • 空指標(Null Pointer):不指向任意地方的指標
  • 型別* 變數名稱 = 0;
    • 判判是否為空指標
      int* pInt = 0;
      cout << (pInt == 0) << " " << !pInt; //顯示:1 1
      
  • C語言中可以使用NULL來初始化指標,但C++沒有定義NULL
  • 使用已存在的變數來初始代指標
  • 型別* 變數名稱1 = &變數名稱2;
    • int myNull = 0;
      int* pInt = &myNull;
      cout << (pInt == &myNull);          //顯示:1
      
Δ 回到最上方

字元與指標

  • 字元指標
    • 可使用字串字面來初始化字元指標,產生const char陣列,最後會自動加上'\0'
    • 常數字元陣列不允許修改
    • const char* 變數名稱 = "字串";
    • cout在處理字元指標變數時,如同C樣式字串,所以不需加上取值運算子*
    • cout如要印出字元指標的位址,則要加上取址運算子&
      • 印出字元指標的位址
        const char* pChar = "Hello Wolrd!!";
        cout << &pChar << " " << pChar; //顯示:0012FF60 Hello Wolrd!!
        *pChar = "M";                   // X 編譯錯誤,在記憶體中是常數,不允許修改
        
    • 可簡寫成
      • char* 變數名稱 = "字串";
    • 但若對常數字串做修改,則在編譯器中不一定可以檢查出來(視編譯器而定)
      • 不可對常數字串做修改
        char* pChar = "Hello Wolrd!!";
        
        *pChar = "M"; // X 編譯正確,但"Hello World!!"在記憶體中是常數,不允許修改
                      //  編譯錯誤 ⇒ '=' : 無法由 'const char [2]' 轉換為 'char'
        
  • 字元指標陣列
    • const char* 變數名稱[ ] = { "字串1", "字串2", ...,"字串n"};
      • 字元指標陣列的宣告及存取
        const char* pCharAr[ ] = { "Hello ", "World ", "C++ "};
        cout << (sizeof pCharAr / sizeof pCharAr[0]) << endl;  //顯示:3
        cout << pCharAr[0] << pCharAr[1] << endl;      //顯示:Hello World
        
    • const char*型別的陣列和char陣列的比較
      • const char*型別的陣列通常較char陣列節省記憶體空間
      • 對陣列操作時const char*型別的陣列速度較快
        • 例如2個元素的值對調,const char*型別只需對換位址即可,char陣列需要一個字元一個字元的搬動
Δ 回到最上方

常數與指標

  • 「指向常數的指標」(A Pointer to a Constant)
    • 「指向常數的指標」記錄位址,該位址的值是常數,不可被更改 不可更改「指向常數的指標」指向的值
    • 「指向常數的指標」記錄的位址可以指向不是常數的值,但不可更改,否則編譯時會出現錯誤
    • 不可將常數的位址存到非「指向常數的指標」內
    • const 型態* 變數名稱;
    • const char* pChar = "This is Constant String";
      char charAr[ ] = "Hello World!!";
      char* pChar2 = 0;
      pChar = charAr;
      cout << pChar << endl;  // 印出Hello World
      
      *pChar = 'A';           // X 不可更改「 指向常數的指標」指向的值
      *pChar[0] = 'A';        // X 不可更改「 指向常數的指標」指向的值
      pChar2 = pChar;         // X 不可將常數的位址存到非"指向常數的指標"內
      
  • 「常數指標」(A Constant Pointer)
    • 「常數指標」所記錄的位址不可更改,只可指向其初始化的位址,但位址指向的值可以被更改
    • 「常數指標」只能指向非const的變數
    • 型態* const 變數名稱1 = &變數名稱2;
    • int myInt1 = 0;
      int myInt2 = 0;
      int* const pInt = &myInt1;
      *pInt = 2;
      
      pInt = &myInt2;     // X 不可更改「 常數指標」記錄的位址
      
  • 「指向常數的常數指標」(A Constant Pointer to a Constant)
    • 「指向常數的常數指標」記錄的位址和指向的值皆不可更改
    • const 型態* const 變數名稱1 = &變數名稱2;
    • const myInt1 = 0;
      const myInt2 = 0;
      const int* const pInt = &myInt1;
      cout << *pInt << endl;
      
      *pInt = 5;          // X 不可更改「 指向常數的常數指標」指向的值
      pInt = &myInt2;     // X 不可更改「 指向常數的常數指標」記錄的位址
      
Δ 回到最上方

指標與陣列

  • 陣列名稱記錄指向陣列元素開頭的位址
  • 陣列名稱可視為指標
    • int intArray[3]={1,2,3};
      int* pInt = intArray;
      cout << intArray << " " << &intArray << endl;       //印出0012FF58 0012FF58
      cout << pInt << " " << *pInt << endl;               //印出0012FF58 1
      cout << *pInt << *(pInt + 1) << *(pInt + 2) << endl;              //印出123
      cout << *intArray << *(intArray + 1) << *(intArray + 2) << endl;  //印出123
      
  • 指標記錄的位址可以修改,但陣列名稱所指的位指是固定的
    • int intArray[3]={1,2,3};
      int* pInt = intArray;
      pInt +=1;               //合法,指標指向陣列的第2個元素
      pInt = &inArray[1];     //合法,指標指向陣列的第2個元素
      intArray += 1; x       //編譯時產生錯誤
      
  • 指標運算
    • 指標只能使用加減法運算及求位址差值
    • 指標與加減法
      • 可以使用+、++、+=、-、--或-=
      • 指標+1表示指向陣列的下一個元素,即指標記錄的位址加上型態的位元組數
        • 以整數為例,因為有4byte,所以指標加1會對指標記錄的位址加4
      • 指標-1表示指向陣列的前一個元素,即指標記錄的位址減掉型態的位元組數
      • 指標的取值運算子「*」的優先權高於加減法運算
        • *(pInt + 1) ⇒ 取出第2個元素的值
          *pInt + 1   ⇒ 取出第1個元素的值,並將值+1
          
        • int intArray[3]={1,2,3};
          int* pInt = intArray;
          cout << pInt << " " << *pInt << endl;        //印出00120058 1
          pInt++;                //指標記錄的位址由00120058變為印出0012005C
          cout << pInt << " " << *pInt << endl;        //印出0012005C 2
          
      • 在求位址差值時,以元素大小為單位
        • int intArray[5] = { 1, 3, 5, 7, 9 };
          int* pInt1 = &intArray[1];
          int* pInt2 = &intArray[3];
          cout << (pInt1 - pInt2) << endl;    //印出2
          
  • 多維陣列
    • 指標需可以存取到多維陣列的第一列的每個元素
    • C++中,陣列變數會有陣列型別和大小的資訊,所以要宣告和多維陣列相同型別的指標時,也需要型別和大小的資料
      • 型態 (*變數名稱)[大小] = 陣列名稱;
      • 如果變數名稱外沒有小括號,編譯器會將型別和*合在一起解讀成記錄指標的陣列
        • 型別* 變數名稱 [大小] = 陣列名稱;
    • 多維陣列的陣列名稱相當於一維陣列,每個元素記錄每列開頭的位址
      • 陣列名稱+1相當於指向第二列開頭
      • 可以使用*( 陣列名稱[ i ] + j)來存取第i列第j行的元素
    • n×m的二維陣列相當於有m個元素的指標型態的一維陣列
    • 陣列第一維度的大小不屬於型別的一部份
      • int intMulAr[2][4];             // intMulAr的型態為int[2][4]
        int* pInt1 = &intMulAr[0][0];   // 第一列第一個元素的位址
        int* pInt2 = intMulAr[0];       // 第一列的位址
        
        int* pInt3 = intMulAr;          // X 型別不同,適合存放intMulAr的型別為int*[4]
        int (*pInt3)[4] =intMulAr;      // O 小括號將指標初始化指向陣列
        //intMulAr的型別為int*[4],所以(intMulAr+1)時才會移到下一列
        
    • 以指標型態的一維陣列參考到二維陣列時
      • int (*pInt)[4]; ⇒ int intMulAr[2][4];
        
      • 指標名稱同陣列名稱
        • cout << pInt << " = " << intMulAr << endl;
          
      • 指標型態的陣列的每個元素,相當於二維陣列每列的開頭,但可能會參考到超出陣列範圍的位址,需小心使用
      • 當二維陣列的行數小於列數時,可以使用超過指標型態陣列範圍的元素
        • int (*pInt)[2]; ⇒ int intMulAr[4][2];
          cout << pInt[3] << " = " << &intMulAr[3][0] << endl;
          
      • 指標+1相當於指到下一列開頭的位址
    • 元素存取法:假設要存取第i列第j行的元素,有下列幾種方法:
      • 陣列名稱[i][j]
      • *( *(陣列名稱 + i ) + j)
      • *( 陣列名稱[ i ] + j)
      • ( *(陣列名稱 + i) ) [ j ]
      用指標存取陣列元素
Δ 回到最上方

動態記憶體配置(Dynamic Memory Allocation)

  • 動態記憶體配置(Dynamic Memory Allocation)
    • 程式執行到需要儲存處理的資料時才配置記憶體
    • 變數不可在編譯時期宣告
    • 堆積(Heap):堆積是一種資料結構,用來存放電腦中沒有使用的記憶體,可自由配置(Free Store)
    • 使用new運算子配置空間,配置後會一直佔用空間直到使用delete運算子釋放空間為止
    • new運算子和delete運算子通常是成對出現
    • 配置空間:
      • 先配置指定型別的指標,並初始化,再用new運算子配置記憶體空間
        • 型態* 變數 = 0;    變數 = new 型態;    *變數 = 初始值;
        • 型態* 變數 = 0;    變數 = new 型態(初始值);
      • 配置失敗的原因:
        • Heap中沒有足夠的空間
        • 太多記憶體碎片,沒有足夠的連續空間
    • 釋放空間:
      • 空間釋放後,指標變數仍會記錄其位址,為避免誤用,釋放後應指向NULL
      • delete 變數;    變數 = 0;
      • int*  pInt = 0;     //宣告指標不指向任何地方
        pInt = new int(27);
        cout << *pInt;      //顯示:27
        delete pInt;
        pInt = 0;
        
    • 若指標所指向的記憶體位址遺失,則再無法釋放該空間或使用該空間
      • int*  pInt = 0;
        pInt = new int(27);
        pInt = new int(15);  //已沒有指向記錄整數值27的指標,無法釋放該空間或存取其值	
        delete  pInt;
        pInt = 0;
        
    • 陣列的動態記憶體配置:
      • 陣列釋放時一定要加中括號,否則會造成不可預知的後果
      • char*  pString = 0;
        pString = new Char[10];
        delete  [ ]  pString;       //釋放陣列空間
        pString = 0;
        
  • 記憶體遺漏(Memory Leak):
    • 遺失尚未釋放記憶體的位址,使該記憶體無法使用或釋放
    • 大量Memory Leak會造成記憶體空間不足
    • 一旦無法存取指標,記憶體就無法釋放,所以一定要在指標的生存空間內將記憶體釋放掉
Δ 回到最上方

指標型態轉換

  • 指標型態轉換
    • reinterpret_cast<型別>(變數)
    • 不可將const指標型別解釋為非const指標型別
    • 可將非指標型別解釋為指標型別,但此法相當危險
    • 雖然解釋為新型別,但原變數的型別並沒有變化
    • float  myFloat = 3.14;
      float*  pFloat = &myFloat;
      long* pLong = reinterpret_cast<long*>(pFloat);
      cout << myFloat << "  " << *pLong << endl;   //印出3.14   1078523331
      
Δ 回到最上方

多重指標(Multiple Indirection, Pointers to Pointers)

  • 型別** 變數名稱;
    多重指標
  • 雙指標與二維陣列
    • n×m的二維陣列相當於有m個元素的指標型態的一維陣列
    • 陣列第一維度的大小不屬於型別的一部份
      • intMulAr[2][4];  ⇒ int (*pInt)[4];
        
    • 一個雙指標變數可視為二維陣列,即相當於指標型態的一維陣列
      • 動態配置一個2×3的陣列
        int** ppInt;
        ppInt = new int*[2];
        for(int i = 0; i < 2; i++)
            ppInt[i] = new int[3];
        
        for(int i = 0; i < 2; i++)
            delete [ ] ppInt[i];
        delete [ ] ppInt;
        
Δ 回到最上方

指涉器(Reference)

  • 指涉器指涉器相當於變數的別名,對指涉器指派新值,則原變數的值也會變成新值
    • 可以直接存取變數的值,不需加上取值運算子「*」
  • 型別& 變數名稱;
  • int  myInt = 0;
    int& rMyInt = myInt;
    rMyInt = 5;
    cout << myInt << endl;	//顯示:5
    
  • 指涉器是指定變數的別名,所以一定要用變數來初始化
  • 指涉器宣告後就不能改變,即只能做為一個變數的別名
  • 函式每次被呼叫時都會產生新的指涉器參數,所以傳入不同引數時,即相當於引數的別名
Δ 回到最上方