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

C 語言核心難點深度解析:數組與指針的本質區別與陷阱大全

     編輯:C 語言入門

核心結論:數組 ≠ 指針

雖然均可用 a[i] / *(p+i) 訪問,但本質完全不同:

  • 數組:一塊連續內存區域,名字代表整體,編譯期固定地址
  • 指針:存儲地址的變量,可指向任意合法地址,運行期可修改

第一部分:數組深度解析

定義與初始化陷阱

int n = 4;
int arr[n] = {1,2,3,4};  // 錯誤:n 非常量(C99 VLA 除外)

// 正確方式
#define N 10
int arr[N] = {0};  // 預處理階段 N→10

const int m = 5;  // C++ 中可作數組長度,C 中為只讀變量
// int arr2[m];  // C 標準下非法

sizeof 與數組

int a[5];
sizeof(a)        // 20 (32位系統,5*4)
sizeof(a[0])     // 4
sizeof(a[5])     // 4 - 不會越界!sizeof 編譯期求值,不訪問內存

數組名不降級的兩種情況

  1. sizeof(arr):得到整個數組大小
  2. &arr:得到數組指針(類型 int(*)[5]),而非首元素地址

二維數組本質

int b[2][3];  // 2 行 3 列
// 內存佈局:連續 6 個 int
// b[0][0], b[0][1], b[0][2], b[1][0], b[1][1], b[1][2]

二維數組內存佈局

數組名作左值/右值

  • 不能作左值:a++、--a、a = ... 皆非法
  • 可作右值:隱式轉換為 &a[0](首元素地址)

a vs &a 經典面試題

int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a + 1);
printf("%d %d", *(a+1), *(ptr-1));  // 輸出:2 5

解析:

  • &a 類型 int(*)[5],&a+1 跳過整個數組(+20 字節)
  • 強轉 int* 後,ptr-1 回退 4 字節 → 指向 a[4] = 5
  • 若不強轉,ptr-1 回退 20 字節 → 指向 a[0] = 1

第二部分:指針深度解析

指針常量

*(int*)0x0018FF44 = 20;  // 必須強轉,否則視為整數無法解引用

指針自指現象

int *p = (int*)0x0018FF44;  // 假設 p 自己的地址就是 0x0018FF44
*p = NULL;  // 修改 p 指向的內存,即修改 p 自身 → p 變為 NULL

左值/右值完整表

表達式左值含義右值含義
&ch非法ch 的地址
cpcp 所代表的內存cp 的值(地址)
&cp非法指針變量 cp 的地址
*cpcp 指向的內存cp 指向內存的內容
*cp+1非法內容+1
*(cp+1)cp+1 指向的內存cp+1 指向內存的內容
++cp非法cp+1 指向的內容
cp++非法cp 的原值
*++cpcp+1 指向的內存cp+1 指向內存的內容
*cp++cp 指向的內存cp 指向內存的內容
++*cp非法內容+1
(*cp)++非法內容(後++)

優先級:++ > * > +

指針運算

int arr[10];
int n = &arr[10] - &arr[0];  // 10 - 元素個數
// &arr[10] 合法(只取地址不訪問),&arr[-1] 非法(棧無預留空間)

第三部分:指針數組 vs 數組指針

指針數組

int *arr[5];  // 5 個 int* 元素的數組
// 類型:int*[5]
// main 參數:char *argv[] 即指針數組

指針數組內存結構

數組指針

int (*p)[5];  // 指向含 5 個 int 的數組的指針
// 類型:int(*)[5]
// 二維數組退化:int a[2][3] → int(*)[3]

數組指針內存結構

二維數組傳參鐵律

void fun(int arr[2][3]);        // OK
void fun(int arr[][3]);         // OK - 第二維必須給定
void fun(int (*arr)[3]);        // OK - 等價寫法
// void fun(int arr[][]);       // 錯誤
// void fun(int **arr);         // 錯誤!二維數組 ≠ 二級指針

第四部分:聲明與定義匹配原則

定義聲明結果
int arr[] = "abc";extern char *arr;崩潰:指針讀取字符串內容作地址
char *p = "abc";extern char str[];亂碼:數組名讀取指針值作字符

錯誤修正技巧

// 定義指針,聲明數組時:
printf("%s", *(char**)str);  // 或 (char*)*(int*)str

第五部分:經典面試題精講

題 1:數組指針運算

int a[5][5];
int (*p)[4] = (int(*)[4])a;
printf("%d %p", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);

解析:

  • %d:指針相減得元素個數 = 4
  • %p:-4 的無符號十六進制 = FFFFFFFC

題 2:指針運算與強轉

int a[4] = {1,2,3,4};
int *ptr1 = (int*)(&a + 1);  // 跳過整個數組
int *ptr2 = (int*)((int)a + 1);  // 地址+1 字節
printf("%x %x", ptr1[-1], *ptr2);  // 輸出:4 20000000 (小端模式)

指針運算內存圖解

總結:避坑清單

  1. 數組名不修改:不作左值、不遞增遞減
  2. sizeof 區分:sizeof(arr) vs sizeof(ptr)
  3. 參數退化:函數參數 int a[] 實為 int *a
  4. 聲明定義一致:數組配數組,指針配指針
  5. 二維數組 ≠ 二級指針:傳參必須保留第二維
  6. 指針運算單位:按類型大小,非字節
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved