C语言中的指针和数组
前言
指针和数组是非常容易混淆的东西,这里我想捋捋几个容易混淆的地方。
假如我们有一个数组
int arr[] <- {1,2,3,4,5};
数组名:有时候会转换成指针?
根据D老师,C语言标准规定,在大多数使用数组名的表达式中,数组名会发生坍缩,数组名的返回值,会变成数组的第一个元素的地址,变成指向数组第一个元素的指针。
所以有以下性质:
arr == &arr[1]
*arr == arr[1]
比如,arr+1表示的是数组第二个元素的位置。
*(arr+1) 表示数组的第二个元素
比如arr[2]的表达式,数组名arr先坍缩为指针,然后进行指针运算arr+2,然后使用*(arr+2)这个解引用获取该地址的值。
例外的情况(数组名不被转换为指针的情况):
在少数几种情况下,数组名保持其“数组”身份,不会 decay 成指针:
-
作为 sizeof 运算符的操作数:
sizeof(arr)返回的是整个数组占用的字节大小(例如数组arr有 5 * sizeof(int) = 20 字节)。 -
作为取地址运算符&的操作数:
&arr产生的是一个指向整个数组的指针,其类型是 int (*)[5]。虽然&arr和&arr[0]的数值(地址值)是相同的,两个指针的大小也是相同的,但它们的类型不同。对&arr + 1进行指针运算,会跳过整个数组的大小(20字节)。 -
作为字符串字面量初始化字符数组时:
char str[] = "hello"; 这里 "hello" 是字符串字面量,本质是 const char[6],在初始化时,字符串字面量不会退化为指针,编译器直接复制字符串内容到新数组。
数组名成为函数的参数时退化为指针
当数组名(例如下方代码中的numbers)作为实参传递给函数时,实际上传递的是指向该数组首元素的指针。并且在函数中建立了新的指针变量 (例如下方代码的形参arr)。
因此,传递参数时,要传递数组名和数组的大小
# include <stdio.h>
void modifyArray(int arr[], int size) {
// 这里写int arr[], int arr[10](这里10会被编译器忽略),int *arr都是一样的效果,创立的新变量都是int *arr
// 这里的size就是数组大小,即数组中元素的多少
for (int i = 0; i < size; i++) {
arr[i] *= 2; // 通过指针直接修改原数组 乘以2
}
}
int main() {
int numbers[3] = {1, 2, 3};
int size = sizeof(numbers) / sizeof(numbers[0]);
modifyArray(numbers, size);
//实际上在函数中发生的是:int *arr = &numbers[0] 或者 int *arr = numbers
// 验证结果
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]); // 输出:2 4 6
}
return 0;
}
数组指针的解引用是什么?
我们创建一个直接指向数组的指针,那么这个指针的解引用是什么?
这里直接揭晓答案,其实就是数组arr本身。当然要注意数组名的返回值的问题。
我们用代码验证一下。
# include <stdio.h>
int main() {
int arr[] = {1,2,3,4,5}; // 创造数组arr
int (*p) [5] = &arr; // 创造一个数组指针p,指向数组arr
// 那么*p会是什么? 也就是指针p的解引用是什么?
printf("数组指针p的解引用是: %p\n", *p);
// 为什么会是指针类型呢?因为解引用为数组arr,当数组作为函数参数传递时,它被转换为指向其首元素的指针,类型是int *。
// 然后为了匹配 %p 格式,int* 被隐式转换为 void*
printf("数组名的返回值 (即指向数组的首个元素的指针的值) 是: %p\n", arr);
// 结果我们发现这两行的结果是一致的。
printf("数组指针p的解引用的大小是: %lu\n", sizeof(*p)); // 结果是20
// 综上,我们证实了*p就是数组arr本身
return 0;
}
为什么不能赋值给数组名
int a[10] = {1,2,3,4,5,6,7,8,9,10}; // 正确
//以下错误
int a[10];
a = {1,2,3,4,5,6,7,8,9,10}; // 错误!
数组名的本质
数组名是一个符号,不是变量
在编译时,编译器知道数组名代表数组的起始地址
运行时,数组名不会在栈或堆上分配额外内存来存储这个地址
它只是在代码中被直接替换为数组的起始地址
数组名不占用任何额外的内存来存储一个地址。它只是一个符号,代表一块内存的起始位置。编译器在编译期就知道这个位置。
正是因为数组名不占用内存存储地址值,所以:
-数组名在编译时就被固定为特定的内存地址
-试图赋值就像试图改变一个常量的值

浙公网安备 33010602011771号