指標 林錦財
大綱 何謂指標 參考(reference) 函數呼叫:傳值、傳址、傳參考 指標的指標 指標陣列 函數指標 複雜的指標宣告
何謂指標 很多程式員說:學C/C++而不會使用指標,相當於沒學過C/C++。 一般變數所存放的是「資料」 然而,指標變數則存放「記憶體位址」 依據指標變數所指的記憶體位址才能找到(dereference)「資料」
簡單的例子 如圖 p1指標變數所記載的值是變數a的記憶體 (memory) 位址,而p2則記載著b的記憶體位址, 稱p1是一個指向a的指標,p2是一個指向b的指標。 b 10 a 5 int *p1 = &a; int *p2 = &b; p1 p2
瞭解語法 int *p1 = &a; int *p2 = &b; 其中的&,稱為address of (取址)。即,p1 = address of a,p2 = address of b。 另一個符號*,代表的意義是指標。
瞭解語法 int *p1; 此宣告的意義是 p1是指向整數的指標
瞭解語法 int *p1 = &a; 這整行的意義:p1是一個指向整數的指標,指向整數變數a。
C++ 語法─參考(reference) 先暫停討論指標,而看看C++之「參考」 。 參考:可想像為一個變數或物件的別名 。 當函式的參數 (parameter) 在函式中會被修改,而且要將修改結果反映給呼叫函式,則會用參考來當函式的參數。
C++ 語法─參考(reference) 當其他程式呼叫此swap函式時,只要直接寫swap(x, y) 就能交換x與y的值。 void swap(int &a, int &b){ int tmp = a; a = b; b = tmp; } 當其他程式呼叫此swap函式時,只要直接寫swap(x, y) 就能交換x與y的值。 因為a就是x,b就是y。 a和b不是x和y的複製品,任何作用在a與b上的動作都會反映在x與y上面,反之亦然。
& & 既能用於「取址」又能用於「參考」,以及位元運算,容易造成初學者的混淆。 若&前面有資料型態 (例如 int &),則為參考,若&前面有等號 (例如 int* p = &a),則為取址。若&前後都是整數,則是位元運算 (例如 c = a & b)
* * 既能用於宣告指標變數(例如 int *p),也可用於找到(dereference)指標所指的資料 (例如 *p = q 或 r = *p),以及用於乘法運算 (例如 c = x * y) 將 int *p視為 int* p。 把int和*連在一起看,當作是一種型態叫做 “指向整數之指標”,要比int *p自然得多。 同樣的方式也可以套在char* p或void* p等。 但要注意的是下面的狀況: int* p, q;
int* p, q; 不要把這行誤解成p, q都是指向int之指標,事實上,q只是一個int變數。 上面這行相當於 int *p, q; 或 int *p; int q; 如果p, q都要宣告成指向int之指標,應寫成: int *p, *q 或者干脆分兩行寫: int* p; int* q;
call by value? call by address (或call by pointer)? call by reference? Call by value: swap(int a, int b) Call by address: swap(int* a, int* b) Call by reference: swap (int &a, int &b)
Call by value: swap(int a, int b) void swap(int a, int b){ int tmp = a; a = b; b = tmp; } 呼叫swap(x, y)後,x和y的值並不會有變化。
Call by address: swap(int* a, int* b) 利用指標來做參數傳遞,這種方法骨子裡仍是call by value,只不過call by value的value,其資料型態為指標罷了。 呼叫swap時,要寫成swap(&x, &y)。 void swap(int* a, int* b){ int tmp = *a; *a = *b; *b = tmp; }
Call by address: swap(int* a, int* b) 呼叫swap時,x的位址與y的位址會被複製一份到swap的指標變數a與b中。 x *a a &x 5 y *b b &y 10
Call by address: swap(int* a, int* b) swap結束後,&x (address of x) 和&y (address of y) 依然沒變,只是指標變數x所指的值與指標變數y所指的值交換了。 因為&x 和&y 其實是利用call by value在傳,因此,call by address其實骨子裡就是call by value。 x *a a &x 10 y *b b &y 5
Call by reference: swap (int &a, int &b) 這是C++才加進來的東西,C本身並沒有call by reference。 只要呼叫swap(x, y),就可以讓x和y的值交換。在這個例子中,a 就是 x, b 就是 y。 swap(int &a, int &b){ int tmp = a; a = b; b = tmp; }
測試一下您的理解 int g_int = 0; void changePtr(int* pInt){ pInt = &g_int; } void main(){ int localInt = 1; int* localPInt = &localInt; changePtr(localPInt); printf("%d\n", *localPInt); } 將印出?
int g_int = 0; void changePtr(int int g_int = 0; void changePtr(int* pInt){ pInt = &g_int; } void main(){ int localInt = 1; int* localPInt = &localInt; changePtr(localPInt); printf("%d\n", *localPInt); } 印出來的數字仍然會是localInt的1 因為changPtr中的pInt是由localPInt「複製」過去的,對pInt做改變並不會反映到localPInt身上。
指標的指標(pointer to pointer ) int g_int = 0; void changePtr(int** pInt){ *pInt = &g_int; } void main(){ int localInt = 1; int* localPInt = &localInt; changePtr(&localPInt); printf("%d\n", *localPInt); } 將印出?
pointer to pointer int g_int = 0; void changePtr(int** pInt){ *pInt = &g_int; } void main(){ int localInt = 1; int* localPInt = &localInt; changePtr(&localPInt); printf("%d\n", *localPInt); } g_int localInt localPInt 1
pointer to pointer int g_int = 0; void changePtr(int** pInt){ *pInt = &g_int; } void main(){ int localInt = 1; int* localPInt = &localInt; changePtr(&localPInt); printf("%d\n", *localPInt); } pInt g_int localInt localPInt 1
pointer to pointer int g_int = 0; void changePtr(int** pInt){ *pInt = &g_int; } void main(){ int localInt = 1; int* localPInt = &localInt; changePtr(&localPInt); printf("%d\n", *localPInt); } pInt g_int localInt localPInt *pInt 1
reference to pointer 同樣的功能也可以用「指標的參考」來做 int g_int = 0; void changePtr(int* &refPInt){ refPInt = &g_int; } void main(){ int localInt = 1; int* localPInt = &localInt; changePtr(localPInt); printf("%d\n", *localPInt); }
reference to pointer int g_int = 0; void changePtr(int* &refPInt){ refPInt = &g_int; } void main(){ int localInt = 1; int* localPInt = &localInt; changePtr(localPInt); printf("%d\n", *localPInt); } g_int localInt localPInt 1
reference to pointer int g_int = 0; void changePtr(int* &refPInt){ refPInt = &g_int; } void main(){ int localInt = 1; int* localPInt = &localInt; changePtr(localPInt); printf("%d\n", *localPInt); } g_int localInt refPInt即localPInt 1
reference to pointer int g_int = 0; void changePtr(int* &refPInt){ refPInt = &g_int; } void main(){ int localInt = 1; int* localPInt = &localInt; changePtr(localPInt); printf("%d\n", *localPInt); } g_int localInt refPInt即localPInt 1
指標陣列 一種常見的混淆是pointer array (指標陣列) 與pointer to pointers,因為兩種都可以寫成**的型式。 如,int**可能是pointer to pointer to integer,也可能是integer pointer array。 pointer array的常見例子:main(int argc, char** argv)其實應該是main(int argc, char* argv[])。 argv …
函式指標(function pointer) 指向函式的位址的指標。 假設有一個函式func1,如下: void func1(int int1, char char1); 想宣告一個能指向func1的指標,則寫成: void (*funcPtr1)(int, char); 理解成:funcPtr1是一個函數指標,它指向的函數接受int與char兩個參數並回傳void。
函式指標(function pointer) 指標指向函式的寫法: funcPtr1 = &func1; 取址符號省略亦可,效果相同: funcPtr1 = func1; 若欲在宣告時就直接給予初值,則寫成: void (*funcPtr1)(int, char) = &func1; 其中 & 亦可省略
函數指標的常見例子 stdlib.h中提供的qsort函式。此函式之原型如下: void qsort(void* base, size_t n, size_t size, int (*cmp)(const void*, const void*)); 其中的int (*cmp)(const void*, const void*) 就使用到函式指標。
函數指標的常見例子(續) 用於multithread時。函數指標負責把函數傳進建立執行緒的API中。 在event driven的環境中,使用的callback function。 所謂callback function即:發生某事件時,自動執行某些動作。
如何看懂複雜的指標宣告 常數與指標的讀法 const double *ptr; double *const ptr; double const* ptr; const double *const ptr; 以上幾個宣告,到底const修飾的對象是指標,還是指標所指向的變數呢? 關鍵在於:*與const的前後關係!
如何看懂複雜的指標宣告 當*在const之前,則是常數指標,反之則為常數變數。因此, const double *ptr; // ptr指向常數變數 double *const ptr; // ptr是常數指標 double const* ptr; // ptr指向常數變數 const double *const ptr; // 指向常數變數的常數指標 用中文講更是不清楚了@#$%!
如何看懂複雜的指標宣告 在The C++ Programming Language中有提到一個簡單的要訣:由右向左讀!!讓我們用這個要訣再來試一次。(用英文解說會比較清楚哦!) const double *ptr; // ptr is a pointer points to double, which is a constant double *const ptr; // ptr is a constant pointer points to double double const* ptr; // ptr is a pointer points to constant double const double *const ptr; // ptr is a constant pointer points to double, which is a constant
再來「英翻中」 const double *ptr; // ptr is a pointer points to double, which is a constant 中譯:ptr是一個指標,指向double的記憶體空間,此double值是常數(不可更改)
再來「英翻中」 double *const ptr; // ptr is a constant pointer points to double 中譯:ptr是一個常數指標(ptr值不可更改),指向儲存double的記憶體空間
再來「英翻中」 double const* ptr; // ptr is a pointer points to constant double 中譯:ptr是一個指標,指向固定double值的記憶體空間
再來「英翻中」 const double *const ptr; // ptr is a constant pointer points to double, which is a constant 中譯:ptr是一個常數指標,指向double的記憶體空間,此double值是常數(不可更改)
複雜宣告的讀法 void ** (*d) (int &, char **(*)(char *, char **)); 印度工程師Vikram的"The right-left rule": 從最內層的括號讀起,變數名稱,然後往右,遇到括號就往左。當括號內的東西都解讀完畢了,就跳出括號繼續未完成的部份,重覆上面的步驟直到解讀完畢。
複雜宣告的讀法 解讀 void ** (*d) (int &a, char*) : 1. 最內層括號的讀起,變數名稱: d 2. 往右直到碰到) : (空白) 3. 往左直到碰到( :是一個函數指標 4. 跳出括號,往右,碰到(int &, char*): 此函式接受兩個參數:第一個參數是reference to integer,第二個參數是character pointer。 5. 往左遇上void **: 此函式回傳的型態為pointer to pointer to void。
複雜宣告的讀法 解讀void ** (*d) (int &, char **(*)(char *, char **)) :