C語言: 超好懂的指標,初學者請進~

指標(pointer)這個功能在 C 語言中有著非常重要的地位。

C 語言中特有的指標,可以透過記憶體映射的方式直接控制硬體,這也是為什麼 C 語言在硬體系統特別強大的原因,包括資料結構(陣列/字串/鏈結串列)、系統程式(編譯器/作業系統)、演算法,都會進一步使用到。

但對於初學者來說,一開始無法釐清指標、導致後續指來指去,指到最後往往變成噩夢。

今天我們的目標,就是從頭開始把指標介紹的非常簡單。

 

什麼是指標(Pointer)

這張照片叫「Milky Way and Starry Night Sky」,是一張看著便賞心悅目的美麗星空圖,當桌布做美工都適合。

如果你想要這張照片的話,最直接的辦法就是我給你這張照片的網址「https://www.goodfreephotos.com/astrophotography/milky-way-and-starry-night-sky.jpg.php」,讓你在網址上拿到這張照片。

 

 

這和指標有什麼關係呢?概念很類似噢!只要我給你這個網址,你就可以藉由這個網址拿到這張照片,也就是說網址是指向這個資料的「指標」。

哦~原來資料的地址就是指標嘛,是不是非常簡單的概念呢?

等等,那 C 語言中的指標是長什麼樣子?

讓我們來看看這段程式碼:

void main(){
  int a = 15;
  int b = 2;
  int c = 39;
  int d = 180;
  int e = 67;
}

在這個程式碼中,我們宣告了五個變數。

以 int b = 2 為例,在宣告好變數、程式開始執行後,會去記憶體中要一塊儲存空間,然後把 2 這個資料放進去。

還記得我們說過記憶體就像一個大櫃子、每個格子都有相對應的地址嗎?這個 2 的地址就是在記憶體中的某一個地方。

但我們說過,記憶體中一個格子的大小是 1 個 byte,而一個 int (整數型)的大小就占了 4 個 byte,所以這邊寫的地址,是 2 這個整數所占的這一塊記憶體空間的起始地址。

從起始地址開始起算、共佔了 4 個格子,也就是 4 byte。

也就是說,當我們宣告一個變數時,總共會有三個要素:

 

程式會向記憶體要一塊空間來儲存變數值,所以這個儲存空間有一個起始位址。再加上這個變數的名稱(b)與變數值(2)。

通常我們會把變數的位址,稱為「指向該變數的指標」。

在這邊需要特別注意的是,我講的是「指標」,而不是「指標變數」;這兩個是不同的東西。

拿一開始的照片作為對照例子,是不是很清楚呢?

但我們在宣告一個變數,比如宣告 b 的時候,要怎麼知道到底是跟記憶體要了哪一塊地址放這個 b 呢?怎麼樣才能看到這個變數的位址?

這邊就要介紹一下在 C 語言裡面,有個運算符號是用來「取址」,就是「&」。

怎麼做呢?請參考以下程式:

#include <stdio.h>

int main(void) {
    int b = 2;

    printf("變數 b 的值:%d\n", b);
    printf("變數 b 的記憶體位址:%p\n", &b); //%p為印出地址的16進位表示法

    return 0;
}

看一下在 Terminal 中編譯完的結果:

這個程式中,宣告了一個 int 整數變數 b,並藉由印出「&b」的值,知道 b 所在的記憶體位址是 0x7fff54a109c8(16進位表示法)。

從 0x7fff54a109c8 開始的 4 個 byte 都是 b 所配置到的記憶體空間,儲存了 2 這個值。

這時你一定想問:有地址要做什麼?我們宣告完變數後也用得好好的,沒事要它的地址幹嘛?

事實上我們拿到一個地址,都是為了要去到這個地址上、以抓取上面的變數。(知道了朋友的地址,目的不就是要去拜訪他嗎?或就像我們利用網址去抓照片一樣)

但問題來了,怎麼樣能利用一個變數的地址、去拿到這個變數呢?直接把地址寫出來然後執行嗎?

答案是不行的。我們要利用 C 語言中的另一個運算元「*」來做這件事。

#include <stdio.h>

int main(void) {
    int b = 2;

    printf("變數 b 的值:%d\n", b);
    printf("變數 b 的值:%d\n", *&b);//從這個地址中取出變數b的值

    return 0;
}

來看看 Output 的結果:

奇怪,明明印出 b 變數的值,和「從 b 這個位址中取值」印出 *&b,還不是一樣的答案?這樣的話為什麼要寫那麼多?

你說的沒錯。事實上,「*&b」和「b 」的意義是等價的。

 

指標變數(Pointer variable)

講完了什麼叫指標後,接下來讓我們看看「指標變數」。

還記得我們說過,指標 (Pointer) 就是某變數的位址。而這邊的指標變數 (Pointer Variable),則是用來存放指標的變數。

 

案例中的 pointer 就是一個指標變數。變數都是用來存放「值」的,而整數型變數 int 就是存整數、字元型變數 char 就是存字元。所以這個指標變數就是用來存「地址」的變數。

也就是說,宣告一個指標變數,和一般宣告變數一樣, 是跟記憶體要一個區域、存放這個變數的值。只是這個變數的型別是指標。

另外,由於 pointer 中存的地址是變數 b 的值,因此我們又把 pointer 稱為「變數 b 的指標變數」。

這些概念非常的簡單,只是一定要弄清楚。

接下來你可能會問:要怎麼去宣告一個指標變數呢?

我們可以採用「*」這個運算符號。

int* pointer

pointer 表示這個變數的名稱,而 * 表示 pointer 這個變數是個指標。

等等,那 int 代表什麼意思?星星符號運算元 * 不就說是個指標了嗎?

int 代表這個指標變數指向的變數的類型。… 你在說什麼在繞口令嗎?

直接看下圖吧!

具體來說的程式碼長這樣:

int b; 
//跟記憶體要一塊區域稱為b,這塊區域專門放int型變數值

b = 2; 
//把2這個值給變數b

int* pointer; 
//跟記憶體要一塊區域稱為pointer,這塊區域專門放指向int型變數的指標(地址)

pointer = &b; 
//把變數b的地址值給pointer,注意不能寫成 pointer = b;

還記得「&」代表「把這個變數的地址取出來」的意思嗎?要注意,這邊絕對不能寫成 pointer = b; ,因為 pointer 是專門存放地址的變數。

如此一來,我們就稱「指標變數 pointer 指向了變數 b」,是不是很好懂呢!

很多書上寫的有些模糊,初學者又還搞不清楚時,就會導致指標、指標變數、指標變數宣告等產生混亂。

接下來還想問個問題:能不能利用 pointer 去拿到它指向的 b 這個變數呢?當然可以。這邊同樣要利用到 * 這個運算符號。

當我們跑完這個程式碼之後:

int b = 2; 
int* pointer = &b;

會發生這件事:

也就是說變數 b 在記憶體中對應了一塊儲存空間,而這塊儲存空間總有一個起始的地址。所以 pointer 對應到的就是這個起始地址。

在這種狀況下,就可以用「*pointer」來拿到這個變數。

這裡的「*」,和宣告指標變數的 int* pointer 的意義不太一樣。反而是和「&」相對應——「&」代表「取出地址」、「*」代表「取出內容」。

等等,那所謂的「*pointer 取出的內容」指的到底是變數 b、還是變數 b 的值 2?

這兩個是不同的東西喔!變數 b 是這塊區域,2 是值。

答案是:*pointer 代表的就是變數 b。所以我們可以把 *pointer 當作變數 b 來使用。

直接看程式碼吧:

#include <stdio.h>

int main(void) {
    int b = 2;
    int* pointer = &b;

    printf("變數 b 的值:%d\n", b);
    printf("變數 b 的地址:%p\n", &b);
    printf("pointer 的值:%p\n", pointer);
    printf("\n"); //換行
    
    *pointer = 100;

    printf("*pointer 的值:%d\n", *pointer);
    printf("變數 b 的值:%d\n", b);
    printf("變數 pointer 的地址:%p\n", &pointer);

    return 0;
}

 

執行這段程式碼的結果:

我們可以看到一開始的變數 b 的值被設定為 2,所以印出來也會是 2。然後用「&b」取出變數 b 的地址為「0x7fff551b49c8」。

由於 &b 的值被賦予給 pointer,所以把 pointer 印出來後同樣也是「0x7fff551b49c8」。

由於我們說過 *pointer 就是其指向的變數 b,所以在這邊我們試著把 *pointer 中的值改成 100,然後印看看原有的變數 b 會不會跟著改變。

發現會欸!2 被改成 100 了!

最後,由於存放 pointer 這個變數的地址,和變數 b 的地址不一樣,所以利用「&pointer」後,可發現地址「0x7fff551b49c0」和變數 b 的地址果然不一樣。

試著寫個小程式玩玩看:

#include <stdio.h>

int main(void) {
    int a, b, temp;
    int *p1, *p2;
    printf("請輸入 a 的值:"); 
    scanf("%d", &a);
    printf("請輸入 b 的值:");
    scanf("%d", &b);

    p1 = &a;
    p2 = &b;

    if(*p1 < *p2){
      temp = *p1;
      *p1 = *p2;
      *p2 = temp;
    }
    printf("*p1的值:%d\n", *p1);
    printf("*p2的值:%d\n", *p2);
}

 

讓使用者能自行輸入 a 和 b 變數的值,然後再把該變數賦值給 *p1 和 *p2 兩個 pointer。

最後比較一下,如果 *p1 值小於 *p2 值,就把兩數交換。

 

今天的指標與指標變數教學就到這邊,下一篇會來介紹指標與陣列的相互應用。感謝收看~

 

(來源:xkcd.com)

念金融的一聽到「指標」,就會反射性想到「市場指標」之類的折線圖或指數;結果現在看指標太久,下意識就想到一堆記憶體地址…

#兩種領域間的腦內打架 #Lynn編崩潰中