C语言数组与指针:为什么 a[i] 就是 *(a+i)?
数组和指针是 C 里最容易让人绕晕的一对概念。今天用一个实验把三件事讲透1.数组名什么时候退化成指针、2.arr和arr到底差在哪、以及 3.a[i]的本质到底是什么。一、先说结论数组名有两种含义含义一代表「整个数组」只在三种情形触发数组定义中——int arr[5];定义的就是整个数组取址符中——arr取出的是整个数组的地址见实验1sizeof运算表达式中——sizeof(arr)算的是整个数组的大小见实验2。含义二退化成「首元素地址」——除了上面三种情况其他所有地方都是这个含义arr1、*arr、作为函数参数传入等。arr、arr、arr[0]打印出来值一样但类型不同所以1的步长不同。a[i]在编译器眼里就是*(a i)——这就是下标运算符的全部秘密。二、实验1arr、arr、arr[0] 值相同步长不同intarr[]{10,11,12,13,14};// 5*4 20 字节printf(arr : %p\n,arr);// 首元素地址printf(arr : %p\n,arr);// 整个数组的地址值相同printf(arr[0] : %p\n,arr[0]);// 首元素地址三者打印出来的地址数字一模一样但含义不同arr类型是int *代表首元素地址。arr[0]对首元素取地址也是int *。arr对整个数组取地址类型是int (*)[5]指向含5个int的数组。所以1时偏移的单位不一样printf(arr1 : %p\n,arr1);// 偏 4 字节一个 intprintf(arr1 : %p\n,arr1);// 偏 20 字节一整个数组printf(arr[0]1:%p\n,arr[0]1);// 偏 4 字节记住一句话arr1直接跳到数组后面arr1只跳到第二个元素。三、实验2sizeof 的坑数组名没退化的情况有两个地方数组名不会退化成指针其中一个就是sizeofprintf(sizeof(arr) : %ld\n,sizeof(arr));// 20整个数组的大小printf(sizeof(arr0) : %ld\n,sizeof(arr0));// 8退化成指针64位下地址占8字节sizeof(arr)→ 求的是整个数组5×420。sizeof(arr 0)→arr先和0做加法退化成了指针对指针求 size 就是 8地址长度。小知识另一个不退化的情况是arr还有字符串字面量初始化数组时。四、实验3a[i] ≡ *(ai)下标可交换这是今天最重要的结论intx[3]{1,2,3};printf(x[0]%d, *(x0)%d, *(0x)%d, 0[x]%d\n,x[0],*(x0),*(0x),0[x]);// 四个全相等1 1 1 1printf(2[x]%d, x[2]%d\n,2[x],x[2]);// 2[x] x[2] 3为什么0[x]、2[x]都成立因为x[i]在编译期就被翻译成*(x i)而加法满足交换律*(xi)和*(ix)当然一样。所以i[x]也合法。结论[]不是什么神秘运算符它就是指针解引用的语法糖。五、实验4指针和数组是同一块内存普通变量是拷贝inta2[3]{3,4,5};int*ppa2;// pp 指向 a2 的首元素pp[1]10;// 等价于 a2[1] 10printf(指针改原数组: %d,%d,%d\n,*(pp1),pp[1],a2[1]);// 10,10,10int *pp a2;让 pp 和 a2共享同一块内存所以通过 pp 改原数组也变。对比基本类型赋值intb19;intqb;// q 是 b 的一份拷贝q18;printf(普通变量拷贝: b%d, q%d\n,b,q);// 19, 18b 不受影响指针赋值 指向同一块空间别名普通变量赋值 复制一份值互不影响这就是传指针能改原值、传普通变量改不了的根本原因。六、可运行的全套代码#includestdio.hintmain(void){// 示例1数组名退化、步长差异intarr[]{10,11,12,13,14};printf(arr : %p\n,arr);printf(arr : %p\n,arr);printf(arr[0] : %p\n,arr[0]);printf(arr1 : %p\n,arr1);printf(arr1 : %p\n,arr1);printf(arr[0]1:%p\n,arr[0]1);printf(*arr : %d\n,*arr);printf(sizeof(arr) : %ld\n,sizeof(arr));printf(sizeof(arr0) : %ld\n,sizeof(arr0));intval100;int*pval;printf(sizeof(p) : %ld\n,sizeof(p));// 示例2a[i] *(ai)intx[3]{1,2,3};printf(x[0]%d, *(x0)%d, *(0x)%d, 0[x]%d\n,x[0],*(x0),*(0x),0[x]);printf(2[x]%d, x[2]%d\n,2[x],x[2]);// 示例3指针共享空间 vs 普通变量拷贝inta2[3]{3,4,5};int*ppa2;pp[1]10;printf(指针改原数组: %d,%d,%d\n,*(pp1),pp[1],a2[1]);intb19;intqb;q18;printf(普通变量拷贝: b%d, q%d\n,b,q);return0;}运行输出实测将上面的完整代码编译运行后输出结果如下具体地址值因运行环境而异arr : 0x7ffffcbf0 arr : 0x7ffffcbf0 arr[0] : 0x7ffffcbf0 arr1 : 0x7ffffcbf4 arr1 : 0x7ffffcc04 arr[0]1:0x7ffffcbf4 *arr : 10 sizeof(arr) : 20 sizeof(arr0) : 8 sizeof(p) : 8 x[0]1, *(x0)1, *(0x)1, 0[x]1 2[x]3, x[2]3 指针改原数组: 10,10,10 普通变量拷贝: b19, q18输出解读地址验证arr、arr、arr[0]三个地址值完全相同都是0x7ffffcbf0印证了“值相同”的结论。步长差异arr1和arr[0]1的结果是0x7ffffcbf4比基址增加了4一个int的大小。arr1的结果是0x7ffffcc04比基址增加了200x14即整个int[5]数组的大小。sizeof 差异sizeof(arr)输出20整个数组sizeof(arr0)和sizeof(p)输出864位系统指针大小。下标等价性x[0]、*(x0)、*(0x)、0[x]全部输出12[x]和x[2]都输出3直观证明了a[i]等价于*(ai)且满足交换律。内存共享通过指针pp修改a2[1]后*(pp 1)、pp[1]、a2[1]都输出10说明它们访问的是同一块内存。变量拷贝b的值保持19q被改为18验证了普通变量赋值是值拷贝。这份输出将前面所有理论结论可视化可对照代码逐行验证。七、小结数组名 ≠ 指针只是在表达式里退化成首元素地址。arr和arr值相同、类型不同1步长天差地别。a[i]就是*(a i)所以i[a]也合法。指针赋值共享内存普通变量赋值是拷贝。