首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> C 語言入門

C 語言指標入門精要:變數作用域、指標運算與 const 修飾符

     編輯:C 語言入門

前言

指標是 C 語言區別於其他高階語言的核心特性,直接操作記憶體位址賦予了程式極高的效率與控制力。本文從變數作用域與生命週期切入,層層遞進講解指標的基本語法、傳址呼叫機制、指標算術運算規則、const 修飾語意,以及指標與陣列的深層關係,為初學者建立堅實的指標思維模型。

1. 變數作用域與生命週期:區域、全域與靜態

理解指標指向記憶體的有效性,首先要搞清變數「活」在哪裡、「死」在哪裡。

1.1 儲存類別對比

類別關鍵字作用域生命週期儲存區預設初值
區域變數auto (省略)區塊內 { }進入區塊建立,離開銷毀堆疊不定值 (垃圾值)
全域變數無 (檔案頂層)全檔案 (需 extern 跨檔)程式啟動~結束資料區 (BSS/Data)0 / NULL
靜態區域變數static區塊內 { }程式啟動~結束資料區0 / NULL
靜態全域變數static (檔案頂層)當前檔案 (內部連結)程式啟動~結束資料區0 / NULL

1.2 遮蔽效應與 static 保持狀態

區域變數同名會遮蔽全域變數。static 區域變數僅初始化一次,函數多次呼叫間共享同一記憶體,可用於計數器、單例模式等。

#include <stdio.h>

int x = 1000; // 全域變數

void f1(int a) {
    a = 10;                // 參數為區域副本
    auto int x = 20;       // 區域變數,遮蔽全域 x
    static int y = 30;     // 靜態區域變數,僅初始化一次
    y++; x++;
    printf("in f1: a=%d, x=%d, y=%d\n", a, x, y); // y 會遞增
}

void f2() { printf("in f2: x=%d\n", x); x = 2000; } // 存取全域 x

int main() {
    int x = 30;            // main 的區域 x,遮蔽全域 x
    f1(100); f1(100);      // y 輸出 31, 32
    printf("in main: x=%d\n", x); // 輸出 30 (區域 x)
    f2(); f3();            // 全域 x 被 f2 改為 2000
    return 0;
}

2. 指標基礎:位址與間接存取

  • & (取址運算子): 取得變數在記憶體中的位置 (編譯期/執行期確定)。
  • * (間接運算子/解引用): 透過位址存取該位置的資料。
  • 指標變數宣告: int *p; —— p 儲存 int 型別資料的位址。
int a = 10;
int *p = &a;    // p 指向 a
printf("p = %p, *p = %d\n", (void*)p, *p); // 輸出位址與值 10
*p = 20;        // 透過指標修改 a,a 變為 20

3. 指標的威力:模擬傳址呼叫

C 語言參數傳遞永遠是值傳遞。若想在函數內修改主調變數,必須傳遞該變數的位址 (指標)。

3.1 失敗案例:值傳遞交換

void swap_val(int a, int b) { // 形參為副本
    int t=a; a=b; b=t; // 僅影響副本
}

3.2 成功案例:指標傳遞交換

void swap_ptr(int *a, int *b) { // 形參為位址
    int t = *a; *a = *b; *b = t; // 解引用操作原變數
}

int main() {
    int x=10, y=20;
    swap_ptr(&x, &y); // 傳遞位址
    // x=20, y=10
}

4. 進階應用:透過指標回傳多個結果

函數僅能 return 一個值。若需回傳最大值、最小值等多項資料,採用「輸出型參數」模式:

void maxAndMin(int data[], int size, int *max, int *min) {
    *max = *min = data[0];
    for (int i=1; i<size; i++) {
        if (data[i] > *max) *max = data[i];
        if (data[i] < *min) *min = data[i];
    }
}

int main() {
    int arr[10] = {5, 2, 9, 1, 7, 3, 8, 4, 6, 0};
    int max, min;
    maxAndMin(arr, 10, &max, &min); // 傳遞變數位址
    printf("Max: %d, Min: %d\n", max, min);
}

5. 指標算術運算:型別決定步長

指標 +1 不代表位址 +1 位元組,而是 + sizeof(指向型別)。這使得指標能自然遍歷陣列。

int a; int *pa = &a;     // pa+1 增加 4 bytes (假設 int 4B)
short s; short *ps = &s; // ps+1 增加 2 bytes
char c; char *pc = &c;   // pc+1 增加 1 byte
double d; double *pd = &d; // pd+1 增加 8 bytes

// 陣列遍歷等價性
int arr[5] = {10,20,30,40,50};
int *p = arr;
for(int i=0; i<5; i++) {
    printf("%d ", *(arr+i)); // arr[i]
    printf("%d ", *(p+i));   // p[i]
    printf("%d ", i[arr]);   // 同樣合法但不推薦
}

指標減法: 同型別指標相減得元素個數 (ptrdiff_t)。

6. const 修飾指標:三種鎖定模式

const 位置決定「誰被鎖死」:

宣告口訣指標值 (位址)指向值 (*p)
const int *p
int const *p
const 在 * 左邊:指向常數的指標可修改 (p = &b)不可修改 (*p=20 錯誤)
int *const pconst 在 * 右邊:常數指標不可修改 (p=&b 錯誤)可修改 (*p=20 正確)
const int *const p雙重 const不可修改不可修改
int i1=5, i2=10;
const int *p1 = &i2; // *p1=20 錯誤; p1=&i1 正確
int *const p2 = &i2; // p2=&i1 錯誤; *p2=20 正確

// 常見用途:函數參數唯讀保護
void print_str(const char *s) { // 承諾不修改 s 指向的內容
    while(*s) putchar(*s++);
}

7. 指標與陣列:等價與差異

7.1 核心等價性

  • arr[i] 編譯器自動轉為 *(arr + i)。
  • 陣列名 arr 在大多數表達式中退化為指向首元素的指標 &arr[0]。
  • 因此 *(arr+i)、*(p+i)、p[i]、arr[i] 完全等價。

7.2 關鍵差異:陣列名是「常量指標」

int arr[5] = {1,2,3,4,5};
int *p = arr;

p++;      // 合法:指標變數可修改
// arr++;  // 錯誤:陣列名是常量,不可修改指向

sizeof(arr); // 20 (整個陣列大小,單位 byte)
sizeof(p);   // 4/8 (指標變數大小)

7.3 指標運算細節:後置遞增優先級

  • *p++ 等價 *(p++):先取值 *p,再 p++ 移至下一元素。
  • (*p)++:先取值 *p,再將指向的值加 1。
  • *++p:先 p++,再取新位置的值。
  • ++*p:先取值,再將值加 1 (前置遞增)。

總結

  1. 作用域決定生命週期: 指標指向的記憶體必須在有效期內 (堆疊變數離開作用域即失效,malloc 需手動 free)。
  2. 指標實現傳址呼叫: 透過傳遞位址 (&var) 並在函數內解引用 (*ptr) 修改原值。
  3. 算術運算看型別: p+N 位移 N * sizeof(*p) 位元組。
  4. const 看位置: 左鎖值、右鎖址、雙鎖死。
  5. 陣列名非指標變數: 不可修改、sizeof 計算整體大小、退化為指標時失去長度資訊。

掌握這五大核心概念,即可駕馭 C 語言指標的精髓,編寫出高效、安全的系統級程式碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved