
数组对象和首元素很多人学习 C 语言时都会听过一句话数组就是指针。也有人说数组名就是一个指针。甚至有人说数组和指针完全一样。答案是否定的。数组不是指针。指针也不是数组。这是从 C89 到 C23 都没有改变的核心规则。在这个表达式中数组名arr发生了数组退化转换成了指向数组的首元素的指针因此它的值与arr[0]相同。arrarr[0]首元素指针地址1000数组对象本身没有发生任何改变。数组对象↓数组名 arr↓在普通表达式中发生转换↓arr[0]↓首元素地址1000所以,arr不是第一个元素。它只是在需要指针值的场景下转换成首元素指针地址1000。数组退化:arrarr[0]首元素指针地址为什么叫“退化”其实很好理解。数组原本拥有自己完整类型。例如int arr[5];它的类型是int[5] 表示 一个包含 5 个 int 元素的数组对象。但是在很多表达式中C 语言会把数组名arr转换成int * 指针类型,指向第一个元素的指针1000 。这就叫数组退化数组退化后: int *p arrarr[0]首元素指针地址1000int *p arr; 等价于int *p arr[0];数组退化过程数组对象arr┌────┬────┬────┬────┬────┐│ 10 │ 20 │ 30 │ 40 │ 50 │└────┴────┴────┴────┴────┘↓数组退化↓arr[0]↓int *↓数组退化后: int *p arrarr[0]首元素指针地址1000注意这里没有复制整个数组。没有创建新的内存。也没有把数组变成指针。只是数组名在当前表达式中产生了一个指向首元素的指针值。请记住一句非常重要的话数组对象不会退化发生退化的是数组名在表达式中的转换。为什么很多人会误以为数组就是指针int arr[5] { 10,20,30,40,50 }; int * p arr; printf(arr[0]%p\n, arr[0]); //arr[0]000000000000000A printf(p[0]%p\n, p[0]); //p[0]000000000000000A printf(arr[2]%p\n, arr[2]); //arr[2]000000000000001E printf(p[2]%p\n, p[2]); //p[2]000000000000001E printf(*(arr 3)%p\n, *(arr3)); //*(arr 3)0000000000000028 printf(*(p 3)%p\n, *(p3)); //*(p 3)0000000000000028通过以上案例,很多人认为 数组就是指针。其实真正正确的理解是数组退化以后得到的指针和普通指针具有相同的使用方式。数组本身依然不是指针。真正参与指针运算的是谁数组对象 arr┌────┬────┬────┬────┬────┐│ 10 20 30 40 50└────┴────┴────┴────┴────┘1000 1004 1008 1012 1016↓数组退化首元素地址 arr1000↓ 指针运算arr 1 1004arr 2 1008arr 3 1012真正参与arr 1 arr 2 arr 3 运算的并不是数组对象。而是 数组退化之后得到的指针参与运算。数组到底是什么数组是一块连续存放多个相同类型元素的内存空间。int arr[5];表示5 个连续的 int 元素.而不是一个指针变量.数组真正的结构┌────────┬────────┬────────┬────────┬────────┐│ int │ int │ int │ int │ int │└────────┴────────┴────────┴────────┴────────┘│ 10 │ 20 │ 30 │ 40 │ 50 │└────────┴────────┴────────┴────────┴────────┘数组对象 arr 一整块连续内存这里没有int *这样的指针变量。数组对象本身就是这块连续空间。例如int arr[5];创建数组arr的 地址1000┌────┬────┬────┬────┬────┐│ │ │ │ │ │└────┴────┴────┴────┴────┘1000 1004 1008 1012 1016而int *p arr;创建的是p┌────────┐│ 1000 │└────────┘也就是说arr是一整块连续内存p是保存地址的指针变量两者从本质上完全不同。本节小结到这里我们已经得到三个重要结论✅数组不是指针。✅数组对象是一块连续内存。✅数组名arr在大多数表达式中会转换成指向首元素的指针。为什么arr[i]等于*(arr i)很多人第一次看到arr[i] 等价于*(arr i)都会觉得不可思议。一个看起来像数组。一个看起来像指针。为什么它们完全一样其实这是 C 语言定义数组下标运算时的规则。C 标准规定E1[E2]等价于*((E1) (E2))也就是说arr[i] 等价于 *(arr i)arr[i] 等于*(arr i)推到过程:int arr[5] { 10,20,30,40,50 }数组名arr数组名arr数组名arr数组名arr数组名arr数组arr退化为 arr[0]数组arr退化为 arr[0]数组arr退化为 arr[0]数组arr退化为 arr[0]数组arr退化为arr[0]第一个元素的 指针运算0后内存地址: arr[0]0第二个元素的指针运算1后内存地址: arr[0]1第三个元素的指针运算2后内存地址: arr[0]2第四个元素 的指针运算3后内存地址: arr[0]3第五个元素的指针运算4后内存地址arr[0 ]4*解引用*(arr[0]0)10*解引用*(arr[0]1)20*解引用* (arr[0]2) 30*解引用* (arr[0]3) 40*解引用*(arr[0]4) 50数组退化后arrarr[0]数组退化后arrarr[0]数组退化后arrarr[0]数组退化后arrarr[0]数组退化后arrarr[0]简写*(arr[0]0)*(arr0)10arr[0]简写*(arr[0]1)*(arr1)20arr[1]简写* (arr[0]2)*(arr2) 30arr[2]简写* (arr[0]3)*(arr3) 40arr[3]简写*(arr[0]4)*(arr4) 50arr[4]结论:arr[i] *(arr i)数据内容数组arr[0]*(arr 0)10arr[1]*(arr 1)20arr[2]*(arr 2)30arr[3]*(arr 3)40arr[4]*(arr 4)50数组下标操作本质上就是指针运算加解引用。数组下标操作本质上就是指针运算加解引用。所以:p[i]*(p i)元素值还有一个 C 语言经典冷知识a[b] *(ab)p[i]*(p i)推到过程:int arr[5] { 10,20,30,40,50 };int* p arr;因为 arr[i] *(arr i)所以 p[i]*(p i)p[0]*(p 0)10p[1]*(p 1)20p[2]*(p 2)30p[3]*(p 3)40p[4]*(p 4)50int arr[5] { 10,20,30,40,50 }; int* p arr; printf(p[0]%d\n, p[0]); //p[0]10 printf(*(p 0)%d\n, *(p 0));//*(p 0)10 printf(p[1]%d\n, p[1]);//p[1]20 printf(*(p 1)%d\n, *(p 1));//*(p 1)20 printf(p[2]%d\n, p[2]);//p[2]30 printf(*(p 2)%d\n, *(p 2));//*(p 2)30 printf(p[3]%d\n, p[3]); //p[3]40 printf(*(p 3)%d\n, *(p 3)); //*(p 3)40 printf(p[4]%d\n, p[4]); //p[4]50 printf(*(p 4)%d\n, *(p 4)); //*(p 4)50arr、arr和arr[0]到底有什么区别int arr[5] { 10,20,30,40,50 }; printf(arr%p\n, (void *)arr); //arr0000004DA8AFF5E8 printf(arr[0]%p\n, (void *)arr[0]); //arr[0]0000004DA8AFF5E8 printf(arr%p\n, (void *)arr); //arr0000004DA8AFF5E8通过以上案例 发现arr、arr和arr[0]的内存地址值都一样,很多人就认为 arrarr[0]arr 这是错误的认识。正确理解虽然它们的内存地址数值可能相同但类型和含义完全不同。arr单独使用arr在普通表达式中会发生数组退化。类型: int *表示指向第一个 int 元素的指针。arrarr[0]00000079070FF4D8arr[0]arr[0]更加直接。它就是第一个元素的内存地址。类型: int *所以arr 退化后arrarr[0]00000079070FF4D8arrarr表示指向的是整个数组对象的内存地址。类型int (*)[5]读作指向包含 5 个 int 元素数组的指针。注意它不是int *而是int (*)[5]为什么arr 1和arr 1完全不同arr 1arr 数组退化后: int * 类型指针 指向第一个元素的内存地址int *p arrarr[0]首元素指针地址00000036E47DF658(当前p指向第一个元素的内存地址)p指针移动一个元素。 移动一个 sizeof(int)4Byte字节意识就是p 指向第二个元素的内存地址 arr 1 int * 100000036E47DF65Cint arr[5] { 10,20,30,40,50 }; //首先arr 数组退化后: int * 类型指针 指向第一个元素的内存地址 // int *p arrarr[0]首元素指针地址00000036E47DF658 int* p arr; printf(p%p\n, p);//p第一个元素的内存地址00000036E47DF658 // (当前p指向第一个元素的内存地址)p指针移动一个元素。 移动一个 sizeof(int)4Byte字节 // 意识就是p 指向第二个元素的内存地址 arr 1 int * 100000036E47DF65C int* p1 arr1; printf(p1%p\n, p1); //p100000036E47DF65Carr 1arr 表示指向的是整个数组对象的内存地址000000814ACFFBA8。 类型int (*)[5]arr 1 移动单位不是一个sizeof(int)4字节。而是 把整个数组对象的内存地址000000814ACFFBA8 移动sizeof(arr)5*sizeof(int)20 Byte字节长度后的新的内存地址000000814ACFFBBC。int arr[5] { 10,20,30,40,50 }; //arr 表示指向的是整个数组对象的内存地址000000814ACFFBA8。 类型int (*)[5] //arr 1 移动单位不是一个sizeof(int)4字节。 // 而是 把整个数组对象的内存地址000000814ACFFBA8 // 移动sizeof(arr)5*sizeof(int)20 Byte字节长度后的新的内存地址000000814ACFFBBC。 printf(arr%p\n, (void*)arr);// arr 000000814ACFFBA8 printf(arr 1%p\n, arr 1);// arr 1 000000814ACFFBBC数组arr不退化成指针的场景前面一直说数组名arr在大多数表达式中会转换成指针。注意是大多数。不是全部。1. sizeof(arr)里的数组arr 不会退化AHHDHF HF HD HF