【從初學者到中階者】C語言指標變數完全攻略!圖解+程式範例,輕鬆說明

目次

1. 前言

在學習 C 語言時,無法迴避的就是對「指標變數」的理解。對於初學者而言,「位址」與「間接參照」等概念可能會感到難以理解,但指標是 C 語言的根本重要元素,掌握它後即可進行更高階的程式設計。 本篇文章將從「什麼是指標變數?」的基礎開始,逐步且細緻地說明實作範例、以及與陣列、函式的關係、應用方式等。為了讓讀者更易掌握專業術語的意義與運作概念,會搭配圖解與範例程式碼,即使對 C 語言尚未熟悉的讀者也能安心閱讀。 指標一旦熟悉,就會成為非常強大且便利的功能。它能進行動態記憶體操作、函式參數的傳遞等,極大擴展程式的可能性。希望透過本篇文章,能加深對指標變數的理解,讓您在 C 語言開發技能上提升一個層次。

2. 指標變數是什麼?

指標變數的基本概念

在 C 語言中,指標變數是「儲存記憶體位址的變數」的意思。一般變數儲存資料本身,而指標則記憶另一個變數所儲存的位址(地址)。 例如,考慮以下程式碼。
int a = 10;
int *p = &a;
在此情況下,變數a儲存值 10,p儲存 a 的位址。寫成 *p 時,可間接取得 p 所指向位址中的值(此例為 10)。

位址與記憶體關係

所有資料皆儲存在電腦的記憶體中。而記憶體每一個位元組都有對應的位址。指標利用這些位址資訊,作為指定「要操作哪個記憶體位置」的手段。 使用指標可以做到以下幾件事。
  • 在函式之間直接修改變數的內容
  • 彈性操作陣列的元素
  • 使用 heap 記憶體進行動態記憶體管理
也就是說,指標是支撐 C 語言彈性與低階控制能力的重要機制

指標變數的宣告與初始化

指標變數以在目標資料型別後加上星號(*)的方式宣告。
int *p;   // 指向 int 型變數的指標
char *c;  // 指向 char 型變數的指標
然後,通常使用 & 運算子,將其他變數的位址賦值給它。
int a = 5;
int *p = &a;  // 將 a 的位址存入 p
此處重要的是,「指標的型別」與「指標所指向的值的型別」必須相同。若將指向 int 型的指標賦予 char 型的位址,可能無法保證其運作。

3. 指標的基本操作

了解指標變數的基本後,接下來具體看看「要怎麼使用?」。在此將介紹指標操作不可或缺的運算子、值的讀寫、指標之間的運算等基本操作方法。

位址運算子(&)與間接運算子(*)

位址運算子(&)

& 被稱為「位址運算子」,用於取得變數的記憶體位址
int a = 10;
int *p = &a;
在此範例中,將變數a的位址存入pp會保存「a 所在的記憶體位置」。

間接運算子(*)

* 被稱為「間接運算子」或「參照運算子」,在指標參照或修改其指向位址的內容時使用。
int a = 10;
int *p = &a;

printf("%d
", *p);  // 結果: 10
這樣寫 *p,即可間接取得a的值(內容)。相反地,若這樣寫則可以修改值。
*p = 20;
printf("%d
", a);  // 結果: 20

取得與寫入值:指標的活用範例

使用指標,可以直接從其他函式修改變數的值。以下是基本範例。
void updateValue(int *p) {
    *p = 100;
}

int main() {
    int num = 50;
    updateValue(&num);
    printf("%d
", num);  // 結果: 100
    return 0;
}
如此一來,指標在函式內更新值時也能發揮作用。在 C 語言中,傳遞參數時基本上是以複製(值傳遞)方式,但使用指標則可以直接操作原始值本身

指標的加法與減法

指標可以進行加法與減法。這在處理陣列或連續記憶體區域時非常便利。
int arr[3] = {10, 20, 30};
int *p = arr;

printf("%d
", *p);     // 10
p++;
printf("%d
", *p);     // 20
此處重要的是,使用「p++」時,會移動到下一個 int 型變數的位址。若 int 為 4 位元組,則 p++ 在位址上相當於「+4」。 如此了解指標的基本操作,就是在 C 語言中建立記憶體操作基礎的第一步。下一章將更深入探討指標與陣列的關係。

4. 陣列與指標的關係

在 C 語言中,陣列與指標有非常密切的關係。對於初學者來說,這也是容易混淆的重點,但透過了解此關係,能夠實現更彈性且高效的陣列操作。

陣列名稱可視為指標使用

在 C 語言中,陣列名稱被視為指向首個元素位址的指標。例如,請看以下程式碼。
int arr[3] = {10, 20, 30};
printf("%d
", *arr);     // 結果: 10
此時,arr指向與 &arr[0] 相同的位址,*arr 代表陣列的第一個元素(arr[0])。

使用指標存取陣列

陣列可以透過索引存取,但也可以使用指標進行相同的操作。
int arr[3] = {10, 20, 30};
int *p = arr;

printf("%d
", p[1]);     // 結果: 20
此處,p[1]等同於 *(p + 1)。也就是說,使用指標也可以寫成以下方式。
printf("%d
", *(arr + 2));   // 結果: 30
如此一來,索引表示法與指標運算本質上是相同的

指標運算與陣列索引的差異

陣列與指標雖相似,但仍需注意並非完全相同

1. 取得大小

使用陣列名稱時,可透過 sizeof 取得其大小,但在指標被賦值時,大小資訊即遺失。
int arr[5];
int *p = arr;

printf("%zu
", sizeof(arr)); // 結果: 20(5×4 位元組)
printf("%zu
", sizeof(p));   // 結果: 8(在 64 位元環境中指標為 8 位元組)

2. 是否可重新賦值

陣列名稱類似於常量指標,無法透過賦值改變
int arr[3];
int *p = arr;
p = p + 1;     // OK
arr = arr + 1; // 錯誤(陣列名稱不可重新賦值)

熟練指標與陣列的好處

  • 使用指標即可、彈性操作記憶體空間
  • 陣列處理會變得更快速且高效(有時指標運算比索引稍快)。
  • 在將陣列傳遞給函式時,只會傳遞首位址而非實體,因此指標相關知識是必須的。

5. 函式與指標

在 C 語言中,將變數傳遞給函式時,值傳遞(call by value)是基本方式。因此,即使在函式內更改參數,原始變數也不會受到影響。然而,透過使用指標,函式可以直接操作原始變數的值。 本章將說明函式與指標的關係、值的改寫方法、函式指標的基礎等,解釋在函式中使用指標的方式。

使用指標改寫值

首先,若要在函式內更改變數的值,需要使用指標。

範例:未使用指標的情況

void update(int x) {
    x = 100;
}

int main() {
    int a = 10;
    update(a);
    printf("%d
", a); // 結果: 10(未變更)
    return 0;
}
在此情況下,函式收到的是 a 的副本,因此 a 本身不會被更改。

範例:使用指標的情況

void update(int *x) {
    *x = 100;
}

int main() {
    int a = 10;
    update(&a);
    printf("%d
", a); // 結果: 100(已變更)
    return 0;
}
如此一來,將變數的位址傳遞給函式,即可更改原始值。這是作為「參照傳遞(call by reference)」技巧被廣泛使用。

陣列與函式的關係

在 C 語言中,將陣列傳遞給函式時會自動被視為指標。也就是說,列首元素的位址會傳遞給函式,因而在函式內可以修改其內容。
void setValues(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        arr[i] = i * 10;
    }
}

int main() {
    int nums[3];
    setValues(nums, 3);
    printf("%d
", nums[1]); // 結果: 10
    return 0;
}
如同此例,只要直接傳遞陣列名稱,即可在函式內修改其內容。

函式指標的基礎

在 C 語言中,也可以將函式的位址存入變數並呼叫。這就是函式指標。

宣告與使用範例:

int add(int a, int b) {
    return a + b;
}

int main() {
    int (*funcPtr)(int, int); // 回傳 int 型,接受兩個 int 型參數的函式指標
    funcPtr = add;

    int result = funcPtr(3, 4);
    printf("%d
", result); // 結果: 7
    return 0;
}
函式指標可用於想要動態選擇並執行函式,或實作回呼函式等情況。僅使用函式名稱即可取得函式的位址,從而實現彈性的程式設計。

實務上的使用情境

  • 陣列排序(Sort)時,將比較函式以指標傳遞
  • 選單選擇型程式中,使用函式指標管理每個選項對應的執行函式
  • 事件驅動處理與回呼函式的實作(GUI、嵌入式、遊戲開發等)

6. 指標的應用範例

了解指標的基本用法後,接下來學習應用的活用方法吧。C 語言的指標在 動態記憶體操作與高階資料結構的實作上是不可或缺的。本章將介紹在實務中也常被使用的三種應用技巧。

動態記憶體配置(mallocfree

在 C 語言中,可以 在執行時動態配置所需的記憶體。這由標準函式庫的 malloc 函式實現。配置的記憶體以指標參照,使用完畢後必須使用 free 釋放。

範例:動態配置整數

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = (int *)malloc(sizeof(int));  // 配置一個 int 型的記憶體
    if (p == NULL) {
        printf("記憶體配置失敗\n");
        return 1;
    }

    *p = 123;
    printf("%d\n", *p);  // 結果: 123

    free(p);  // 釋放記憶體
    return 0;
}

注意事項:

  • malloc 會返回配置記憶體的起始位址返回值必須檢查是否為 NULL
  • 記憶體使用完畢後,必須使用 free 釋放
如此一來,能在需要的時機配置所需的記憶體正是使用指標進行動態記憶體管理的魅力所在。

指標的指標(雙重指標)

保存指標位址的指標,也就是「指標的指標」在 C 語言中也常被使用。特別是在函式內想要修改指標,或是操作二維陣列時會發揮作用。

範例:在函式內初始化指標

void allocate(int **pp) {
    *pp = (int *)malloc(sizeof(int));
    if (*pp != NULL) {
        **pp = 42;
    }
}

int main() {
    int *p = NULL;
    allocate(&p);
    printf("%d\n", *p);  // 結果: 42
    free(p);
    return 0;
}

常見的使用情境:

  • 陣列的陣列(2 維陣列)
  • 在結構體中持有可變長度的陣列時
  • 處理多個字串(char *argv 等)

函式指標與陣列的結合

將函式指標以陣列方式保存,依情況動態切換並呼叫函式等高階技巧在 C 語言中也很常見。

範例:簡易選單

#include <stdio.h>

void hello() { printf("Hello\n"); }
void bye()   { printf("Goodbye\n"); }

int main() {
    void (*funcs[2])() = {hello, bye};

    int choice = 0;
    printf("0: hello, 1: bye > ");
    scanf("%d", &choice);

    if (choice >= 0 && choice < 2) {
        funcs[choice]();  // 呼叫函式
    }
    return 0;
}
這樣的設計在 狀態管理與事件驅動型程式中也很有用。 使用指標的應用技巧,不僅僅是寫程式的能力,更需要 對記憶體與執行控制有意識的設計能力。然而,只要能熟練運用,就能將 C 語言的威力發揮到極致。

7. 常見錯誤與其對策

指標是非常強大的功能,但如果使用不當會成為錯誤和安全漏洞的原因。本章將說明在 C 語言中使用指標時常發生的錯誤,以及防止這些錯誤的對策。

未初始化指標的使用

最基本且危險的是使用未初始化的指標的情況。指標僅僅宣告後並不指向有效的位址。

錯誤範例:

int *p;
*p = 10;  // 未定義行為!p 未指向任何位址

對策:

  • 指標必須先初始化後再使用
  • 使用前執行 NULL 檢查
int *p = NULL;
// 使用前先分配記憶體或指派有效的位址

NULL 指標的解參考

當指標指向 NULL 時執行 *p,程式會崩潰。這是非常常見的錯誤。

範例:

<codeint ",="" *p="NULL;" *p);="" code="" printf("%d="" 執行時錯誤(如分割錯誤等)<="">

對策:

  • 在使用前確認指標是否不為 <codenull< code=””></codenull<>
if (p != NULL) {
    printf("%d
", *p);
}

記憶體洩漏

如果忘記對動態分配的記憶體呼叫 free,會產生記憶體未被釋放而累積的「記憶體洩漏」。對於長時間執行的程式或嵌入式系統而言這是致命的。

範例:

int *p = (int *)malloc(sizeof(int));
 // 處理結束後仍未 free → 記憶體洩漏

對策:

  • 使用完畢後務必呼叫 free
  • 注意 mallocfree 的對應
  • 開發時使用記憶體洩漏偵測工具(例如:Valgrind)

懸空指標

指向已 free 後記憶體的指標稱為「懸空指標」,若再次使用會導致未定義行為。

範例:

int *p = (int *)malloc(sizeof(int));
free(p);
*p = 123;  // 錯誤!存取已釋放的記憶體

對策:

  • free務必指派 NULL 以使其失效
free(p);
p = NULL;

陣列外存取

使用指標進行索引運算時,可能會不小心超陣列的界限。這同樣非常危險,會成為錯誤和漏洞的原因。

範例:

int arr[3] = {1, 2, 3};
printf("%d
", *(arr + 3));  // 未定義行為(arr[3] 不存在)

對策:

  • 始終確認是否在有效範圍內存取
  • 在迴圈處理時徹底執行「邊界檢查」

對同一指標進行雙重釋放

對相同的記憶體位址呼叫 free 兩次,程式有可能崩潰。 4>對策:
  • free 後的指標指派為 NULL,以防止雙重釋放
free(p);
p = NULL;
只要遵守基本原則,仔細編寫程式碼,就能防止些錯誤。尤其對於初學者而言,將「初始化」「NULL 檢查」「徹底使用 free」作為規則遵守,才能寫出沒有錯誤的程式碼。

8. 總結

在 C 語言中,指標變數是最基本且深奧的重要元素。本文從「指標是什麼?」的基礎,逐步說明了陣列、函式、記憶體管理、函式指標等應用範例。

學習要點回顧

  • 指標變數是儲存資料位址的變數,透過 *(間接運算子)和 &(位址運算子)來操作。
  • 陣列與指標密切相關,陣列名可視為指向首位址的指標來使用。
  • 結合 函式與指標後,可在函式內直接操作變數的「參照傳遞」成為可能,使用函式指標亦能實現彈性的函式呼叫。
  • 動態記憶體管理(malloc/free)以及 雙重指標等技巧,支援更實務且彈性的程式設計。
  • 另一方面,未初始化指標、NULL 參照、記憶體洩漏、懸掛指標等指標特有的錯誤也很多,需要謹慎處理。

給予初學者的建議

指標常給人「難」或「可怕」的印象,這是因為它被當作黑盒子使用。只要徹底了解位址的意義與記憶體的運作機制,焦慮就會轉化為自信。 以下的步驟有助於鞏固學習:
  • 將範例程式碼用紙筆與圖示手寫追蹤
  • 使用 printf 將位址與值可視化並確認
  • 活用 Valgrind記憶體檢查工具
  • 撰寫多個小型指標操作練習程式

邁向下一步

本文介紹的內容涵蓋了 C 語言指標的基礎到中級層面。若要進一步深化理解,建議可進入以下主題。
  • 結構體與指標
  • 使用指標的字串操作
  • 檔案輸入輸出與指標
  • 多維陣列的操作
  • 使用函式指標的回呼設計
透過了解指標,您將能體會 C 語言真正的趣味與強大。雖然起初可能會感到困惑,但請逐步且確實地累積理解。希望本文能成為您的助力。