[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),否則會產生編譯錯誤
- 指標的功能
- 快速存取陣列
- 存取函式外的資料
- 動態分配使用的記憶體
取址運算子(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陣列需要一個字元一個字元的搬動
- const char* 變數名稱[ ] = { "字串1", "字串2", ...,"字串n"};
常數與指標
- 「指向常數的指標」(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中沒有足夠的空間
- 太多記憶體碎片,沒有足夠的連續空間
- 先配置指定型別的指標,並初始化,再用new運算子配置記憶體空間
- 釋放空間:
- 空間釋放後,指標變數仍會記錄其位址,為避免誤用,釋放後應指向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;
- 動態配置一個2×3的陣列
指涉器(Reference)
- 指涉器:指涉器相當於變數的別名,對指涉器指派新值,則原變數的值也會變成新值
- 可以直接存取變數的值,不需加上取值運算子「*」
- 型別& 變數名稱;
-
int myInt = 0; int& rMyInt = myInt; rMyInt = 5; cout << myInt << endl; //顯示:5
- 指涉器是指定變數的別名,所以一定要用變數來初始化
- 指涉器宣告後就不能改變,即只能做為一個變數的別名
- 函式每次被呼叫時都會產生新的指涉器參數,所以傳入不同引數時,即相當於引數的別名