前往
大廳
主題

帶你攻略程式新手村大魔王 — 「指標」

魔化鬼鬼 | 2020-11-28 13:20:32 | 巴幣 1020 | 人氣 631

        這次要來講的是許多程式新手踢到的第一塊鐵板 — 指標,這篇教學會盡量以淺顯易懂的方式教導各位什麼是指標。


  • 先別急,讓我們先認識些觀念
        在了解什麼是指標之前,我們得先認識一些程式的基本概念,這篇我就以C++語言為主(C語言也是通的喔)。

        一般來說我們寫程式時都會用到一些變數,例如: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 使用取值符的話,編譯器不會讓你過的。相對的,取址符的對象是變數,因此如果你對一段地址做取址,編譯器也會不讓你過的喔!


  • 儲存位址的變數 — 指標
        我們知道上面的例子,「變數a」 的 「值是10」,也就是說a這個變數存的值是整數型態的10,那麼有沒有「變數c」 的 「值是0x61febc」呢?

        有的,這個東西就是這篇的重點 — 指標,不過我覺得指標這個名字在初學時很不直觀,什麼誰指向誰的,我覺得對於初學者來說太混亂了,所以我比較喜歡叫它「儲存位址的變數」。你可以把它想成像是 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 型態,編譯器不會讓你過的。


  • 如何儲存位址 \ 取值?
        接下來就是要讓 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

    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」才能體會到,初學的我覺得還是以我這種說法來理解就好。


  • 總結
        如同我上面所說,指標就是另一種的變數型態罷了,只是多了取址和取值的概念,希望這篇能夠多少讓一些新手跨過指標這個難關。

創作回應

更多創作