数组名退化为指针
需要预先了解:
1. 数组到指针的退化
数组名出现在大多数表达式中,会被编译器自动转化为指向数组首元素的指针:
int arr[5] = { 1, 2, 3, 4, 5 };
int* p = arr; // arr 这里的含义为 &arr[0],类型为 int*
1. 下标访问arr[i]在底层等价于*(arr + i)
甚至可以可以有3[arr]这样的写法,和arr[3]等效:
#include <iostream>
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
std::cout << 3[arr] << std::endl; // 并不建议的写法
return 0;
}
2. 可以用指针的方式访问数组,或用数组的方式访问指针
#include <iostream>
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
int *p = new int[5];
for (int i = 0; i < 5; i++)
{
p[i] = arr[i];
}
std::cout << *(arr + 3) << std::endl;
std::cout << p[3] << std::endl;
return 0;
}
2. 数组名与指针的区别
- 从内存属性上看:
- 数组名只是一个符号地址,等效于
&arr[0],其本身并不占用内存(数组名退化成的指针是一个右值) - 指针是一个独立的变量,自身会占用4字节或8字节的内存空间
- 数组名只是一个符号地址,等效于
- 从可修改性上看:数组名本身的指向不能修改,类似于
Type* const类型的指针常量。 - 从信息上看,
arr在退化前的信息包括:元素的类型,首个元素的地址,元素的个数;而退化后的指针只包括:元素的类型与地址,并无元素个数的信息:
int arr[5];
std::cout << sizeof(arr) << std::endl; // 输出20
std::cout << sizeof(int*) << std::endl; // 输出 4 或 8
3. 数组名不会退化的场景
1. 作为sizeof的操作数
sizeof(arr)计算的是整个数组占用的总字节数,而非指针的大小:
int arr[5];
std::cout << sizeof(arr) << std::endl; // 输出20
std::cout << sizeof(int*) << std::endl; // 输出 4 或 8
2. 作为取地址符&的操作数
对数组名取地址,得到的是指向整个数组的指针(数组指针),而非指向指针的指针。
arr在退化后的类型为int *,指向首元素&arr的类型为int (*)[5],类型是长度为5的int数组的指针,指向arr数组
int arr[5] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
printf("%p\n", arr + 1);
printf("%p\n", &arr + 1);
4. 多维数组的数组名退化
这里以二维数组为例,更高维的数组可以进行类比。
核心在于多维数组是数组的数组,因此对于int arr[3][4]里的arr可以理解为**arr是长度为3的数组,数组中的每个元素的类型都是int [4],即长度为4的 int 数组**。
因此,arr类型上会退化为int (*) [4],具体地址为&arr[0]
强调:arr退化为指针后,类型是int (*)[4],而不是int **,因为arr[0]是一个长度为4的数组,而不是一个指针。
5. 数组作为函数参数
需要预先了解:
数组在作为函数参数的时候,会退化为指向首元素的指针,丢失数组长度的信息,以下三种写法完全等价:
void func(int arr[]);
void func(int arr[10]); // 这里的10没有实际约束意义
void func(int* arr);
如果要保留数组的长度,可以采取以下两种写法:
void func(int* arr, int n); // 额外传入一个参数当数组的长度
void func(int (&arr)[10]); // 直接传入数组的引用
多维数组作为参数的时候,第一维会退化为指针,而其余维度必须明确指定:
void func(int arr[][3]);
void func(int (*arr)[3]);


