這次要來講的是許多程式新手踢到的第一塊鐵板 — 指標,這篇教學會盡量以淺顯易懂的方式教導各位什麼是指標。
- 先別急,讓我們先認識些觀念
一般來說我們寫程式時都會用到一些變數,例如:int 整數、float / double 浮點數、char 字元..等,這些基礎的變數型態各位應該都了解,而我們「宣告變數」這個動作,代表著我們向電腦請求一塊記憶體來存儲這個變數。這意味著這個變數的背後有一個記憶體位址。
#include <iostream> using namespace std; int main(){ int a = 10; int b = 10; return 0; } |
上面這段程式就是指,我向電腦分別請求一段記憶體,用來存 a 和 b 這兩個變數,所以說儘管 a 的值等於 b 的值,這兩個依然是不同的東西,因為他們的位址是不一樣的。
到這邊我希望各位,可以記住這幾個分別的概念,variable 變數,value 數值,address 位址。
- 取址符
取址符,顧名思義,就是取出變數的地址,在c++中我們以「&」為取址符號,所以我們如果在變數之前加上 &,代表的意義就是「提取這個變數的位址」。
下面是程式碼示範:
#include <iostream> using namespace std; int main(){ int a = 10; int b = 5; cout << &a; // 輸出: 0x61febc cout << &b; // 輸出: 0x61feb8 return 0; } |
- 取值符
可以,這時候就要使用到 取值符,在c++中的符號為「*」。是的,和乘法符號一模一樣,請各位注意不要搞混 。只要在位址前面加上「*」,其所代表的涵義就是取得這個位址的變數。
下面為程式範例:
#include <iostream> using namespace std; int main(){ int a = 10; int b = 5; cout << &a; // 輸出: 0x61febc cout << &b; // 輸出: 0x61feb8 cout << *(&a) // 輸出: 10; cout << *(&b) // 輸出: 5; return 0; } |
上面這段程式碼的意思就是說,對 a 和 b 的地址做取值,也就是對 0x61febc 和 0x61feb8 取值,並且輸出。可能有邏輯好的人已經發現到:「*(&a) 跟 a 不是一樣的東西嗎?」,沒錯,這兩個就是一樣的意思。
請特別留意,取值符的對象是一段位址,因此你如果對變數 a 使用取值符的話,編譯器不會讓你過的。相對的,取址符的對象是變數,因此如果你對一段地址做取址,編譯器也會不讓你過的喔!
- 儲存位址的變數 — 指標
有的,這個東西就是這篇的重點 — 指標,不過我覺得指標這個名字在初學時很不直觀,什麼誰指向誰的,我覺得對於初學者來說太混亂了,所以我比較喜歡叫它「儲存位址的變數」。你可以把它想成像是 int, char, double 這樣獨立的一種變數。我們在宣告一個儲存位址的變數時,其宣告方式大概有以下三種:
型態* 變數名稱 = 位址; (當然你也可以先不賦值,就跟 int e; 的道理是一樣的)
型態 * 變數名稱 = 位址;
型態 *變數名稱 = 位址;
「恩? * 不是取值符嗎?」這可能是很多新手有點混亂的地方。在宣告的時候加上 * 就不是代表取值符了,而是指「宣告 * 前面型態的儲存位址的變數」,是不是有點亂了? 趕緊來一個範例:
#include <iostream> using namespace std; int main(){ int a = 10; // 位址: 0x61febc char b = 'k'; // 位址: 0x61feb8 int* c; char* d; return 0; } |
在上面的程式碼中,c 這個變數的功能就是「儲存 int 型態變數的位址」,d 這個變數的功能就是「儲存 char 型態變數的位址」。也就是說 c 這個變數可以儲存 0x61febc 這個位址,因為 0x61febc 這個位址的變數是 int 型態的,那麼如果我讓 c 儲存 0x61feb8 這個位址的變數呢?答案是不行的,因為 0x61feb8 這個位址的變數在上面程式碼是 char 型態,編譯器不會讓你過的。
- 如何儲存位址 \ 取值?
#include <iostream> using namespace std; int main(){ int a = 10; // 位址: 0x61febc char b = 'k'; // 位址: 0x61feb8 int* c = &a; // 此時 c 的值是: 0x61febc char* d = &b; // 此時 d 的值是:0x61feb8 return 0; } |
利用取址符取得 a 與 b 的位址,並且分別放入 c 和 d 就可以了。
那麼如何取值呢?很簡單,就是使用前面提到的取值符 *
#include <iostream> using namespace std; int main(){ int a = 10; // 位址: 0x61febc char b = 'k'; // 位址: 0x61feb8 int* c = &a; // 此時 c 的值是: 0x61febc char* d = &b; // 此時 d 的值是: 0x61feb8 cout << *c; // 輸出: 10 cout << *d; // 輸出: k return 0; } |
上面的程式簡單來說就是「對 c 取值的結果就是變數 a,對 d 取值的結果就是變數 b,然後輸出變數 a 和變數 b」,為什麼對 c 取值就是 a?因為 c 這個變數的值是 0x61febc,對 c 取值相當於對這個地址取值,就跟你寫 a + 10輸出的結果是20的概念是一樣的,a就是代表 10, 10 + 10 當然就是 20 囉。如果你到這邊都能看懂,那麼恭喜,你幾乎已經會指標的概念了。
- 進階概念
#include <iostream> using namespace std; int main(){ int a = 10; // 位址: 0x61febc char b = 'k'; // 位址: 0x61feb8 int* c = &a; // 此時 c 的值是: 0x61febc char* d = &b; // 此時 d 的值是: 0x61feb8 int** e = &c; char** f = &d; return 0; } |
如果你能夠理解我前面所說的概念,相信這個對你來說也能舉一反三。 e 這個變數就是儲存 c 的位址而已,所以如果我對 e 取值就是 c,c 再取值就是 a,這樣理解了吧!下面是程式的樣子。
#include <iostream> using namespace std; int main(){ int a = 10; // 位址: 0x61febc char b = 'k'; // 位址: 0x61feb8 int* c = &a; // 此時 c 的值是: 0x61febc char* d = &b; // 此時 d 的值是: 0x61feb8 int** e = &c; char** f = &d; cout << **e; // 輸出: 10 cout << **f; // 輸出: k return 0; } |
至於「誰是誰的指標、誰指向誰」那種說法,如果你不是很熟悉我上面的概念的人,我不是很建議以這種說法來理解,可以再往上重看一次,那麼如果你很清楚我上面概念的,或許可以稍微了解一下這種說法,畢竟這個還是大家習慣的講法,我在這邊簡短的敘述,以上面的程式碼來說,「c 是一個指向 a 變數的指標, e 是一個指向 c 變數的指標。」,大概就是這樣的概念,通常這種說法的好處要到你寫「翻轉鏈結串列 reverse linked list」才能體會到,初學的我覺得還是以我這種說法來理解就好。
- 總結