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

C語言指針進階:二級指針、void* 與函數指針實戰

     編輯:關於C語言

C語言指針進階:二級指針、void* 與函數指針實戰

指針進階的核心在於「指針的指針」與「函數指針」——它們是構建靈活框架、實現回調與泛型編程的基石。本文通過五個精煉案例,帶你攻克二級指針操作字符串數組、字符串轉整數解析器、通用指針 void* 的類型擦除與還原、函數指針實現策略模式與自定義排序規則。

一、指針基礎回顧與防禦性編程

1.1 核心操作速覽

#include 

void swap(int *a, int *b) {
    int tmp = *a; *a = *b; *b = tmp;
}

int main() {
    int a = 10, *p = &a;
    printf("a=%d, p=%p\n", *p, p);
    p++; printf("p+1=%p (跨過 %zu 字節)\n", p, sizeof(int));
    p--; printf("p-1=%p\n", p);

    int b = 20;
    swap(&a, &b);  // 地址傳遞實現交換
    printf("a=%d, b=%d\n", a, b);

    /* const 修飾位置決定鎖定對象 */
    const int *p1 = &a;  // *p1 不可改
    // *p1 = 30;        // 錯誤
    p1 = &b;             // 合法

    int *const p2 = &a;  // p2 不可改
    *p2 = 40;            // 合法
    // p2 = &b;           // 錯誤

    /* 野指針防範:用完置 NULL,用前判空 */
    p = NULL;
    if (p) printf("%p\n", p); // 不會執行
    return 0;
}

1.2 關鍵原則

  • 指針算術:步長 = sizeof(所指類型)。
  • const 口訣:const 在 * 左 → 鎖定值;在 * 右 → 鎖定指針。
  • 空指針 ≠ 野指針:NULL 是明確的「無效地址」;野指針是未初始化或已釋放的隨機地址,極其危險。

二、二級指針操作字符串數組

2.1 指針數組的內存圖

char *names[] = {"ZhangSan", "LiSi", "WangWu", "ZhaoLiu"};
/* 內存佈局:
   names (棧) → [ptr0] → "ZhangSan" (常量區)
                [ptr1] → "LiSi"     (常量區)
                [ptr2] → "WangWu"   (常量區)
                [ptr3] → "ZhaoLiu"  (常量區)
*/

2.2 二級指針遍歷

char **p = names;  // p 指向首個 char*
for (int i = 0; i < 4; i++)
    printf("%s\n", p[i]);  // p[i] 等價 names[i] 等價 *(p+i)
/* 輸出四行名字 */

本質:char **p 指向 char* 類型的數組,解引用一次得到 char*,再解引用得到字符。

三、二級指針實戰:字符串轉整數解析器

需求:逐字符掃描,遇非數字停止,返回已解析整數,並將輸入指針推進到首個非數字字符——這正是 strtol 的核心邏輯。

3.1 實現代碼

#include 

int convert(char **str) {  // 輸入輸出參數:雙重指針
    int num = 0;
    while (**str) {        // **str 當前字符
        if (**str >= '0' && **str <= '9')
            num = num * 10 + (**str - '0');
        else
            break;
        (*str)++;          // 移動原始指針(需括號:先解引用再++)
    }
    return num;
}

int main() {
    char buf[] = "1234abc";
    char *p = buf;
    printf("數值: %d\n", convert(&p)); // 輸出 1234
    printf("剩餘: %s\n", p);           // 輸出 abc
    return 0;
}

3.2 關鍵點解析

  1. char **str:調用者傳 &p,函數內通過 *str 修改主調的 p。
  2. (*str)++:括號至關重要——*str++ 會被解析為 *(str++)(移動二級指針本身),導致邏輯錯誤。
  3. 返回值攜帶解析結果,指針攜帶新位置,雙通道輸出。

四、void*:類型擦除與泛型編程基石

4.1 核心規則

  • void* 可接收任意類型指針賦值(隱式轉換)。
  • 不可直接解引用:*pv 編譯錯誤,編譯器不知道目標大小。
  • 使用前必須強轉:*(int*)pv = 20;。

4.2 最小演示

#include 

int main() {
    int a = 10;
    int *pi = &a;
    void *pv = pi;           // 隱式轉換

    // *pv = 20;             // 錯誤:void* 無法解引用
    *(int*)pv = 20;          // 正確:先轉回 int*
    printf("a = %d\n", a);   // 輸出 20
    return 0;
}

4.3 工程應用場景

  • 通用容器(鏈表、哈希表)節點存 void* data。
  • 線程函數簽名 void* (*)(void*)。
  • 回調函數傳遞上下文 void* ctx。

五、函數指針:策略模式與回調機制

5.1 函數指針聲明語法

返回類型 (*指針名)(參數列表);
// 例:int (*cmp)(int, int);  // 指向「接收兩 int、返回 int」的函數

5.2 案例一:動態切換求最值策略

#include 

int minVal(int *buf, int n) {
    int m = buf[0];
    for (int i = 1; i < n; i++) if (buf[i] < m) m = buf[i];
    return m;
}
int maxVal(int *buf, int n) {
    int m = buf[0];
    for (int i = 1; i < n; i++) if (buf[i] > m) m = buf[i];
    return m;
}

int main() {
    int data[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*op)(int*, int);  // 函數指針變量

    op = minVal;
    printf("min = %d\n", op(data, 10)); // 0

    op = maxVal;
    printf("max = %d\n", op(data, 10)); // 9
    return 0;
}

5.3 案例二:冒泡排序 + 自定義比較規則(策略模式)

#include 

void printArr(int *a, int n) {
    for (int i = 0; i < n; i++) printf("%d ", a[i]);
    puts("");
}

/* 三種比較規則 */
int asc(int x, int y)   { return x - y; }      // 升序
int desc(int x, int y)  { return y - x; }      // 降序
int mod3(int x, int y)  { return x%3 - y%3; }  // 按模 3 排序

/* 通用排序:接收比較器函數指針 */
void bubbleSort(int *a, int n, int (*cmp)(int, int)) {
    for (int i = 0; i < n-1; i++)
        for (int j = 0; j < n-1-i; j++)
            if (cmp(a[j], a[j+1]) > 0) {  // cmp 返回 >0 表示需交換
                int t = a[j]; a[j] = a[j+1]; a[j+1] = t;
            }
}

int main() {
    int a[10] = {1,2,3,4,5,6,7,8,9,0};
    printArr(a, 10);

    bubbleSort(a, 10, asc);  printArr(a, 10);  // 0 1 2 3 4 5 6 7 8 9
    bubbleSort(a, 10, desc); printArr(a, 10);  // 9 8 7 6 5 4 3 2 1 0
    bubbleSort(a, 10, mod3); printArr(a, 10);  // 0 3 6 9 1 4 7 2 5 8
    return 0;
}

5.4 進階技巧:typedef 簡化聲明

typedef int (*CmpFunc)(int, int);
void sort(int *a, int n, CmpFunc cmp);  // 更清晰

/* 標準庫 qsort 簽名 */
void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

六、綜合實戰:通用數組處理框架雛形

結合 void*、函數指針、二級指針,可構建極簡的泛型框架:

/* 通用回調類型 */
typedef void (*VisitFunc)(void *elem, void *ctx);

/* 遍歷任意類型數組 */
void forEach(void *base, size_t n, size_t sz, VisitFunc visit, void *ctx) {
    char *p = (char*)base;
    for (size_t i = 0; i < n; i++, p += sz)
        visit(p, ctx);
}

/* 使用示例 */
void printInt(void *e, void *) { printf("%d ", *(int*)e); }
void printStr(void *e, void *) { printf("%s ", *(char**)e); }

int main() {
    int nums[] = {1,2,3};
    char *words[] = {"A","B","C"};
    forEach(nums, 3, sizeof(int), printInt, NULL);  // 1 2 3
    puts("");
    forEach(words, 3, sizeof(char*), printStr, NULL); // A B C
    return 0;
}

七、避坑指南總結

陷阱症狀修正
(*str)++ 寫成 *str++指針未正確推進,解析錯誤加括號:先解引用再自增
void* 直接解引用編譯錯誤「void value not ignored」強轉後再解引用
函數指針賦值類型不匹配警告或運行時崩潰嚴格匹配簽名,用 typedef 統一
二級指針未分配內存就解引用段錯誤確保 **p 指向有效 *p
回調函數修改 const 數據未定義行為回調參數加 const 約束

下一步進階:深入 qsort/bsearch 標準庫實現、函數指針表驅動狀態機、void* 類型安全封裝(_Generic 關鍵字)、C++ 模板與 C 泛型的對比思考。

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