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

C/C++ • 程式

轉載的原作者:WRITTEN BY: LYNN 轉載的原網頁

2017-05-15

指標(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 語言中的指標是長什麼樣子?

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

1234567void 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 語言裡面,有個運算符號是用來「取址」,就是「&」。

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

12345678910#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 語言中的另一個運算元「*」來做這件事。

12345678910#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 的指標變數」。

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

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

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

1int* pointer

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

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

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

直接看下圖吧!

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

1234567891011int b; //跟記憶體要一塊區域稱為b,這塊區域專門放int型變數值 b = 2; //把2這個值給變數b int* pointer; //跟記憶體要一塊區域稱為pointer,這塊區域專門放指向int型變數的指標(地址) pointer = &b; //把變數b的地址值給pointer,注意不能寫成 pointer = b;

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

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

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

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

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

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

會發生這件事:

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

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

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

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

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

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

直接看程式碼吧:

12345678910111213141516171819#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 的地址果然不一樣。

試著寫個小程式玩玩看:

123456789101112131415161718192021#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編崩潰中

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

在 WordPress.com 建立自己的網站
立即開始使用
%d 位部落客按了讚: