近期工作繁忙,項目任務接踵而至,導致學習進度嚴重滯後。為了不讓知識斷層擴大,我利用碎片時間系統梳理了C語言指針的核心知識點。指針作為C語言的靈魂,雖然學習曲線陡峭,但掌握它能解鎖動態內存管理、數據結構構建等高級能力,是進階開發者的必經之路。
簡單來說,指針是一個存儲「內存地址」的變量。每個變量在內存中都有唯一地址(如門牌號),通過 & 運算符可獲取該地址。指針變量聲明格式為 type *name;,其中 type 表示指向數據的類型,* 標識該變量為指針。無論指向 int、float 還是 char,指針本質上都是存儲十六進制地址值的長整數,區別僅在於解引用時的數據解讀方式。
int *ip; // 整型指針 double *dp; // 雙精度指針 char *ch; // 字符指針
int *p = &i;*p 訪問或修改目標值以下示例演示如何通過指針間接修改變量值,以及函數參數傳遞地址實現「引用傳遞」效果:
#include <stdio.h>
void change(int *p) {
*p = 300; // 修改指針指向的內存值
}
int main() {
int i = 90;
int *p = &i;
printf("i的初始值:%d\n", i);
change(p); // 傳遞地址
printf("i的修改後值:%d\n", i); // 輸出 300
return 0;
}
未初始化的指針稱為「野指針」,極其危險。良好的習慣是聲明時賦值 NULL(標準庫定義的 0 值常量),表示不指向任何有效內存。判斷空指針推薦使用 if (!ptr) 而非 if (ptr == NULL),因為 NULL 本質是宏,某些標準允許重定義,前者更安全通用。
指針變量本身佔用內存,因此也有地址,可被另一指針指向。int **p2 為二級指針,指向一級指針 p1。解引用層級與星號數量對應:**p2 經兩次取值得到原始數據。實際開發中,一二級指針最常見(如命令行參數 char **argv、動態二維數組)。
int a = 100; int *p1 = &a; int **p2 = &p1; int ***p3 = &p2; // 解引用鏈:***p3 → **p2 → *p1 → a
指針加減運算的步長等於 sizeof(基類型)。這使得指針成為遍歷數組的利器:數組名即首元素地址,p++ 精確移動到下一元素。但對離散變量做指針運算無意義,因為內存佈局不可預期。
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // 等價於 p[i]
}
這是經典易混淆點,關鍵在運算符優先級:
int *p[5]:[] 優先,指針數組(含5個 int* 元素)int (*p)[5]:() 優先,指向含5個 int 的一維數組的指針(即二維數組行指針)前者佔用 20 字節(32位環境),後者僅 4 字節。二維數組遍歷時,數組指針 p+1 跨越整行,配合解引用實現雙重下標訪問:
int a[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
int (*p)[4] = a; // 指向第0行
printf("%d ", *(*(p+1)+2)); // 訪問 a[1][2] → 6
函數在內存中佔據連續區域,函數名即入口地址。函數指針聲明:返回類型 (*指針名)(參數列表)。括號不可省略,否則變為返回指針的函數聲明。常用於回調函數、狀態機、策略模式等場景。
int (*func_ptr)(char*, char*) = &msg_func;
func_ptr("內容", "標題"); // 通過指針調用
int *p; // 指向int / 一維數組 int **p; // 二級指針 int *p[10]; // 指針數組 int (*p)[10]; // 數組指針(指向含10個int的一維數組) int (*p)(); // 函數指針 int *p(); // 返回int*的函數
指針雖難,但掌握「地址、類型、解引用」三要素,配合內存模型圖解,便能駕馭這把雙刃劍,寫出高效、靈活的C代碼。