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

C語言指針基礎全攻略:從變量作用域到指針算術運算

     編輯:關於C語言

C語言指針基礎全攻略:從變量作用域到指針算術運算

指針是C語言的靈魂,也是許多初學者的「拦路虎」。本文將系統梳理指針的核心概念,從變量的生命週期與作用域切入,層層遞進至指針算術運算與const修飾符的進階用法,並附帶完整可運行的代碼示例,助你建立扎實的指針思維。

一、局部變量與全局變量:生命週期與作用域的博弈

1.1 核心概念

理解指針的前提是搞清變量的「生存空間」:

  • 全局變量:定義在所有函數外部,存儲在全局數據區,程序啟動時分配,程序結束時釋放,全文件可見。
  • 局部變量:定義在函數內部(或代碼塊),存儲在棧區,函數調用時分配,返回時釋放,僅在所在作用域有效。
  • 靜態局部變量(static):存儲在全局數據區,生命週期贯穿整個程序,但作用域仍局限於函數內部。

1.2 經典案例:同名變量的「隱藏」規則

觀察下面的代碼,體會局部變量如何「遮蔽」全局變量:

#include <stdio.h>

/* 全局變量 x */
int x = 1000;

void f1(int a) {
    a = 10;                    // 參數 a 是局部變量
    auto int x = 20;           // 局部變量 x(auto 可省略)
    static int y = 30;         // 靜態局部變量 y
    y++; x++;
    printf("in f1(), a=%d, x=%d, y=%d\n", a, x, y);
}

void f2() {
    printf("in f2(), x=%d\n", x); // 訪問全局 x
    x = 2000;                     // 修改全局 x
}

void f3() {
    printf("in f3(), x=%d\n", x); // 訪問全局 x
}

int main() {
    int x = 30, b = 100;      // main 的局部 x 隱藏全局 x
    f1(b); f1(b);             // 連續調用兩次 f1
    printf("in main(), b=%d, x=%d\n", b, x);
    f2(); f3();               // 觀察全局 x 的變化
    return 0;
}

1.3 關鍵輸出解析

in f1(), a=10, x=21, y=31   // 首次調用:x=20→21, y=30→31
in f1(), a=10, x=21, y=32   // 二次調用:x 重新初始化為 20→21,y 保持 31→32
in main(), b=100, x=30      // main 的局部 x 未被 f1 影響
in f2(), x=1000             // 全局 x 初始值
in f3(), x=2000             // 被 f2 修改後的全局 x

核心結論:

  1. 局部變量同名時會隱藏全局變量(就近原則)。
  2. static 讓局部變量「不死」,跨函數調用保持值。
  3. 函數參數傳遞是值傳遞,形參修改不影響實參。

二、指針基本用法:地址與值的雙重視角

2.1 兩個核心運算符

  • &(取地址):獲取變量在內存中的編號。
  • *(解引用/間接訪問):根據地址找到內存單元並讀寫數據。

2.2 入門代碼

#include <stdio.h>

int main() {
    int a = 10;
    int *p = &a;             // p 存儲 a 的地址

    printf("p   = %p\n", p);      // 輸出地址
    printf("&a  = %p\n", &a);     // 驗證地址一致
    printf("a   = %d\n", a);       // 直接訪問值
    printf("*p  = %d\n", *p);      // 通過指針間接訪問值
    return 0;
}

記憶口訣:定義時 int *p 表示「*p 是 int」,使用時 *p 表示「取 p 指向的內容」。

三、指針的優勢:突破值傳遞的牢籠

3.1 交換兩個變量的對比實驗

#include <stdio.h>

/* 失敗版:值傳遞 */
void swap(int a, int b) {
    int tmp = a; a = b; b = tmp;  // 僅交換副本
}

/* 成功版:地址傳遞 */
void swap1(int *a, int *b) {
    int tmp = *a; *a = *b; *b = tmp; // 交換實參內存中的值
}

int main() {
    int x = 10, y = 20;
    printf("初始: x=%d, y=%d\n", x, y);
    swap(x, y);
    printf("swap 後: x=%d, y=%d\n", x, y);   // 無變化
    swap1(&x, &y);
    printf("swap1 後: x=%d, y=%d\n", x, y);  // 成功交換
    return 0;
}

3.2 多返回值模式:通過指針參數「帶回」結果

C 函數只能返回一個值,但通過指針參數可實現多輸出:

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

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 data[10];
    srand((unsigned)time(NULL));
    for (int i = 0; i < 10; i++) data[i] = rand() % 100;

    int max, min;
    maxAndMin(data, 10, &max, &min);  // 傳遞地址接收結果
    printf("最大值: %d, 最小值: %d\n", max, min);
    return 0;
}

四、指針算術運算:步長由類型決定

指針加減運算不是簡單的字節增減,而是按所指類型大小跳躍:

#include <stdio.h>

int main() {
    int a;   int *pa = &a;   printf("int:   pa=%p, pa+1=%p (差 %ld 字節)\n", pa, pa+1, (char*)(pa+1)-(char*)pa);
    short s; short *ps = &s; printf("short: ps=%p, ps+1=%p (差 %ld 字節)\n", ps, ps+1, (char*)(ps+1)-(char*)ps);
    long l;  long *pl = &l;  printf("long:  pl=%p, pl+1=%p (差 %ld 字節)\n", pl, pl+1, (char*)(pl+1)-(char*)pl);
    char c;  char *pc = &c;  printf("char:  pc=%p, pc+1=%p (差 %ld 字節)\n", pc, pc+1, (char*)(pc+1)-(char*)pc);
    float f; float *pf = &f; printf("float: pf=%p, pf+1=%p (差 %ld 字節)\n", pf, pf+1, (char*)(pf+1)-(char*)pf);
    double d;double *pd = &d; printf("double:pd=%p, pd+1=%p (差 %ld 字節)\n", pd, pd+1, (char*)(pd+1)-(char*)pd);
    return 0;
}

規律總結:p + 1 等價於 (char*)p + sizeof(*p)。這是數組遍歷高效的根本原因——指針自動按元素大小步進。

五、const 與指針的三種綁定關係

聲明形式含義可修改指針?可修改所指值?
const int *p / int const *p指向常量的指針✅ 是❌ 否
int *const p常量指針(指針本身不可變)❌ 否✅ 是
const int *const p指向常量的常量指針❌ 否❌ 否

5.1 代碼驗證

#include <stdio.h>

int main() {
    int i1 = 5, i2 = 10;
    const int *p1 = &i2;  // *p1 不可改,p1 可改
    // *p1 = 200;         // 編譯錯誤
    p1 = &i1;             // 合法

    int *const p2 = &i2;  // p2 不可改,*p2 可改
    *p2 = 200;            // 合法,i2 變為 200
    // p2 = &i1;          // 編譯錯誤

    printf("i2 = %d\n", i2); // 輸出 200
    return 0;
}

六、指針與數組:殊途同歸的訪問方式

6.1 數組名的本質

  • 數組名 arr 在大多數上下文中轉換為首元素地址(&arr[0])。
  • 但 sizeof(arr) 返回整個數組大小,&arr 是數組指針(類型 int (*)[N])。
  • 數組名是常量指針,不能賦值(arr = p 非法)。

6.2 三種等價的訪問寫法

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;

for (int i = 0; i < 5; i++) {
    printf("%d %d %d\n", arr[i], *(arr+i), *(p+i)); // 完全等價
    // 甚至 i[arr] 也合法(編譯器轉為 *(i+arr)),但請勿在生產代碼使用
}

6.3 指針移動陷阱:後置 ++ 的優先級

printf("*p++ = %d\n", *p++);  // 等價於 printf("%d\n", *p); p++;
printf("(*p)++ = %d\n", (*p)++); // 先取值打印,再對所指值 +1

七、完整代碼匯總

所有示例均已整合為獨立可編譯的文件,建議逐個編譯運行,觀察內存地址變化與輸出結果,構建直觀的指針心智模型。


下一站預告:C語言指針進階——二級指針、void*、函數指針與回調機制,解鎖框架設計的核心鑰匙。

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