C语言指针和数组有什么区别?sizeof、退化和 &arr 一次讲清
"数组名就是指针"——这句话对了一半,但在关键地方是错的。面试官最爱问的就是那一半:sizeof(arr) 和 sizeof(ptr) 差多少?&arr 和 arr 类型一样吗?数组传进函数后还能 sizeof 吗?搞不清这些,写代码时遇到诡异的 bug 也排查不了。
本质区别:存储的是什么
数组是一块连续内存,里面存的是数据本身。指针是一个变量,里面存的是地址。
cint arr[5] = {1, 2, 3, 4, 5}; // 20 字节连续内存,存了 5 个 int int *ptr = arr; // 8 字节(64 位系统),存的是 arr[0] 的地址
arr 和 ptr 都能通过下标访问元素,arr[2] 和 ptr[2] 效果一样——这是"对了一半"的来源。但底层机制不同:数组直接通过基地址 + 偏移计算,指针先从指针变量里读出地址,再算偏移。
sizeof:最直观的区别
cint arr[10]; int *ptr = arr; sizeof(arr); // 40 = 10 * 4,整个数组的大小 sizeof(ptr); // 8,指针变量本身的大小(64 位系统)
sizeof 对数组名返回整个数组占用的字节数,对指针返回指针变量的大小。这是两者最可靠的区分方式——如果你在函数里对一个参数用 sizeof 试图获取数组大小,拿到的是指针大小而不是数组大小,因为数组已经退化了。
赋值:数组名不能被赋值
cint arr[10]; int *ptr; ptr = arr; // 合法:指针指向数组首元素 arr = ptr; // 非法:数组名是常量地址,不能赋值 arr++; // 非法:同上 ptr++; // 合法:指针可以移动
数组名在大多数表达式中代表数组首元素的地址,但它本身不是指针变量——没有独立的存储空间存放这个地址,它只是一个编译期常量。你不能修改一个常量。
数组退化:传参时数组变成指针
数组作为函数参数传递时,自动退化为指向首元素的指针:
cvoid func(int arr[]) { // 这里的 arr 是指针,不是数组 sizeof(arr); // 8,不是原数组的大小 } int main(void) { int data[100]; func(data); // data 退化为 int* }
函数签名里的 int arr[] 和 int *arr 完全等价,编译器看到的都是 int*。所以函数内部无法通过 sizeof 获取数组长度——必须额外传一个长度参数。这就是为什么 C 标准库的 qsort、memset、memcpy 都需要你传大小。
&arr 和 arr:值相同,类型不同
这是一个经典面试题:
cint arr[10]; arr; // 类型:int*,指向首元素 &arr; // 类型:int (*)[10],指向整个数组的指针
两者的数值相同(都是数组起始地址),但类型不同,指针运算的步长不同:
carr + 1; // 偏移 sizeof(int) = 4 字节,指向 arr[1] &arr + 1; // 偏移 sizeof(int[10]) = 40 字节,指向整个数组之后
&arr 是"数组指针",指向的对象是整个数组;arr 退化为"元素指针",指向的对象是单个 int。类型不同导致指针算术的行为完全不同。
指针数组 vs 数组指针
这两个名字容易搞混,拆开读就清晰了:
cint *arr[5]; // 指针数组:5 个元素的数组,每个元素是 int* int (*ptr)[5]; // 数组指针:一个指针,指向 int[5] 类型的数组
读法技巧:找核心名词——arr[5] 说明 arr 是数组(指针数组),(*ptr) 说明 ptr 是指针(数组指针)。
指针数组的典型用途:字符串数组、函数指针表、不规则多维数组(每行长度不同)。
数组指针的典型用途:二维数组传参 void func(int (*matrix)[5], int rows)。
下标运算的本质
arr[i] 和 *(arr + i) 完全等价——C 语言的下标运算就是指针算术的语法糖。甚至 i[arr] 和 arr[i] 也是等价的,因为 *(i + arr) 和 *(arr + i) 一样。当然,写 i[arr] 只是炫技,别在项目里这么写。
对于多维数组,arr[i][j] 等价于 *(*(arr + i) + j)——先偏移到第 i 行,再偏移到第 j 列。
什么时候用指针,什么时候用数组
- 大小固定、生命周期明确:用数组。栈上分配,无需手动管理
- 大小运行时确定、需要动态分配:用 malloc + 指针
- 函数参数传数组:不可避免退化,额外传长度
- 字符串字面量:
char str[] = "hello"是拷贝到栈上的数组,可修改;char *str = "hello"指向只读段,修改行为未定义