C 速查手冊

指標

C 語言的指標 (pointer) 是用來指向儲存指向某個記憶體位址的資料型態 (data type) ,實際上我們須認識電腦管理記憶體好比一個長列,每一列都有以位元編碼的位址,每一位址都可儲存位元編碼的資料,示意圖如下

0000 ▭▭▭▭
0001 ▭▭▭▭
0010 ▭▭▭▭
0011 ▭▭▭▭
0100 ▭▭▭▭
0101 ▭▭▭▭
0110 ▭▭▭▭
0111 ▭▭▭▭
1000 ▭▭▭▭
 ↓   ↓
位址  內容

例如,我們宣告並指派初值 22 給整數變數 (variable) a ,編譯器 (compiler) 將變數 a 放在 0011 的記憶體位址裡,稍後我們再宣告另一個指向 a 的指標變數 aPtr ,假設編譯器 aPtr 放在 0111 的記憶體位址裡,如下圖所示

0000 ▭▭▭▭   45
0001 ▭▭▭▭   98
0010 ▭▭▭▭  509
0011 ▭▭▭▭   22    a
0100 ▭▭▭▭ 1234
0101 ▭▭▭▭    0
0110 ▭▭▭▭  382
0111 ▭▭▭▭ 0011 aPtr
1000 ▭▭▭▭ 9000
 ↓   ↓     ↓   ↓
位址  內容   數值  變數
其他記憶體位址的數值可能為殘值,意思是如果沒有被程式利用,就對程式沒有用處。

因此,指標變數 aPtr 的內容為變數 a 的記憶體位址,如下

0000 ▭▭▭▭   45
0001 ▭▭▭▭   98
0010 ▭▭▭▭  509
0011 ▭▭▭▭   22    a
0100 ▭▭▭▭ 1234      ↖
0101 ▭▭▭▭    0       ↑
0110 ▭▭▭▭  382       ↑
0111 ▭▭▭▭ 0011 aPtr ↗
1000 ▭▭▭▭ 9000
 ↓   ↓     ↓   ↓
位址  內容   數值  變數

宣告 C 語言的指標變數,格式如下

datatype *name;

* 為宣告指標所用的運算子 (operator) ,注意這跟乘法運算子一樣,編譯器會依上下文判斷星號用為宣告指標,還是用為兩數相乘。

很多情況下我們都需要直接操作記憶體位址,例如提升程式執行效率與建立資料結構。 C 語言中使用指標需要先經過宣告,如下列程式建立一個指標變數 aPtr 指向變數 a

#include <stdio.h>

int main(void)
{
    int a = 9;
    int *aPtr;
    aPtr = &a;
    
    printf("利用指標指向變數 *aPtr = %d\n", *aPtr);
    
    return 0;
}

/* 《程式語言教學誌》的範例程式
    http://kaiching.org/
    檔名:aPtr.c
    功能:示範指標的宣告及使用
    作者:張凱慶 */

編譯後執行,結果如下

$ gcc aPtr.c
$ a.out
利用指標指向變數 *aPtr = 9
$

以下為宣告指標變數

int *aPtr;

下一行替指標變數指派初值

aPtr = &a;

注意, & 為取址運算子,可以取得該變數的記憶體位址,這跟位元運算的且運算子是相同的,所以編譯器會依前後文自行判斷用途。

這兩行可以合併寫成

int *aPtr = &a;

印出指標變數的一行

printf("利用指標指向變數 *aPtr = %d\n", *aPtr);

指標變數使用時另加上的星號 * ,稱為間接運算子或反參考運算子,這是用來取得該指標所指向的數值,而非記憶體位址。

下列程式依次印出變數 a 的值與記憶體位址、 aPtr 的值與所參考的數值

#include <stdio.h>

int main(void)
{
    int a = 9;
    int *aPtr = &a;
    
    printf("a = %d\n", a);
    printf("&a = %p\n", &a);
    printf("aPtr = %p\n", aPtr);
    printf("*aPtr = %d\n", *aPtr);
    
    return 0;
}

/* 《程式語言教學誌》的範例程式
    http://kaiching.org/
    檔名:aPtr2.c
    功能:印出指標變數的記憶體位址
    作者:張凱慶 */

編譯後執行,結果如下

$ gcc aPtr2.c
$ a.out
a = 9
&a = 0x7ffee64bd848
aPtr = 0x7ffee64bd848
*aPtr = 9
$

由於 C 語言的函數只能回傳一個值,所以當程式需要呼叫函數修改超過兩個值的時候,可以傳遞指標當作參數,讓呼叫的函數直接進行修改,如下例的函數 swap()ab 的值進行對調

#include <stdio.h>

void swap(int *, int *);

int main(void)
{
    int a = 22;
    int b = 11;
    
    printf("兩數交換以前.. a = %d, b = %d\n", a, b);
    
    swap(&a, &b);
    
    printf("兩數交換以後.. a = %d, b = %d\n", a, b);
    
    return 0;
}

void swap(int *aPtr, int *bPtr)
{
    int temp;
    
    temp = *aPtr;
    *aPtr = *bPtr;
    *bPtr = temp;
}

/* 《程式語言教學誌》的範例程式
    http://kaiching.org/
    檔名:swap.c
    功能:示範利用指標修改兩個參數
    作者:張凱慶 */

編譯後執行,結果如下

$ gcc swap.c
$ a.out
兩數交換以前.. a = 22, b = 11
兩數交換以後.. a = 11, b = 22
$

呼叫 swap() 的地方

swap(&a, &b);

將兩個變數取址後就是指標變數,然後傳遞給函數 swap()

void swap(int *aPtr, int *bPtr)
{
    int temp;
    
    temp = *aPtr;
    *aPtr = *bPtr;
    *bPtr = temp;
}

函數 swap() 接受兩個指標的參數後,另以一個區域變數 temp 暫存 aPtr 所指向的值,然後把 bPtr 的值給 aPtr ,注意此時兩個變數 aPtrbPtr 的值一樣。最後把暫存變數 temp 的值,也就是 aPtr 原先所儲存的值給 bPtr ,兩數的值便做了對調。

由於陣列 (array) 名稱就是指標,字串 (string) 也就是字元 (character) 陣列常常直接利用指標來操作,如下例

#include <stdio.h>

int main(void)
{
    char *aPtr = "Seeing is believing.";
    
    while (*aPtr != '.') {
        printf("%c\n", *aPtr);
        aPtr++;
    }        
    
    return 0;
}

/* 《程式語言教學誌》的範例程式
    http://kaiching.org/
    檔名:charPtr.c
    功能:示範利用指標操作字元陣列
    作者:張凱慶 */

編譯後執行,結果如下

$ gcc charPtr.c
$ a.out
S
e
e
i
n
g
i
s
b
e
l
i
e
v
i
n
g
$

宣告、建立指標變數的地方

char *aPtr = "Seeing is believing.";

指標變數 aPtr 會指向此字串第一個字元,也就是大寫 S 的記憶體位址。

下面的 while 迴圈 (loop)

while (*aPtr != '.') {
    printf("%c\n", *aPtr);
    aPtr++;
}

這裡利用迴圈逐行印出每個字元。

此外注意指標也是可以進行計算的,但有其限制,合法的指標運算如下表

相同資料型態的指標才可互相指派
指標可相加或相減某一範圍的整數
指向相同陣列的指標可以相減或比較
指標可以指派 0 或是與 0 做比較
關於在結構中使用指標,請參考 C 語言的結構,另有指向函數的指標,請參考函數指標

回 C 速查手冊首頁