1. 指针与数组的深度解析
在C语言中,指针和数组的关系可以说是最基础也最容易被误解的概念之一。很多初学者会把它们混为一谈,但实际上它们有着本质的区别和微妙的联系。
数组本质上是一块连续的内存空间,用来存储相同类型的数据。当我们声明一个数组时,编译器会分配足够的内存来容纳所有元素。例如int arr[10]会在内存中分配40字节的空间(假设int是4字节)。
指针则是一个变量,它存储的是内存地址。指针本身也占用内存空间(通常是4或8字节,取决于系统架构),但它指向的是另一个内存位置。
关键区别:数组名在大多数情况下会退化为指向数组首元素的指针,但它不是指针变量。数组名没有自己的存储空间,而指针变量有。
c复制int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // arr退化为指向第一个元素的指针
printf("%p\n", arr); // 输出数组首地址
printf("%p\n", &arr); // 同样输出数组首地址,但类型不同
printf("%p\n", ptr); // 输出ptr存储的地址值
printf("%p\n", &ptr); // 输出ptr变量本身的地址
1.1 数组名与指针的微妙差异
虽然数组名在很多情况下可以当作指针使用,但它们有几个重要区别:
- sizeof操作符:对数组名使用sizeof会返回整个数组的大小,而对指针使用sizeof返回的是指针本身的大小。
c复制int arr[10];
int *p = arr;
printf("%zu\n", sizeof(arr)); // 输出40(假设int是4字节)
printf("%zu\n", sizeof(p)); // 输出4或8(指针大小)
-
取地址操作:对数组名取地址(&arr)得到的是指向整个数组的指针,类型是
int (*)[10],而对指针取地址得到的是指针变量的地址。 -
赋值操作:指针变量可以被重新赋值,而数组名不能。
c复制int arr1[5], arr2[5];
int *p = arr1;
p = arr2; // 合法
arr1 = arr2; // 非法,数组名不能作为左值
1.2 数组与指针的访问方式
虽然语法相似,但数组下标访问和指针解引用在底层实现上有区别:
c复制int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
// 以下四种访问方式是等价的
arr[2] = 100;
*(arr + 2) = 100;
p[2] = 100;
*(p + 2) = 100;
在实际编译中,数组访问通常会被转换为"基地址+偏移量"的机器指令,而指针访问可能需要额外的加载指令来获取指针当前值。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 字符数组与字符串传参的实践
字符数组是C语言中表示字符串的主要方式,理解如何正确传递字符串参数对于编写健壮的程序至关重要。
2.1 字符数组的初始化
字符数组有多种初始化方式,每种方式有细微差别:
c复制// 方式1:指定大小,部分初始化
char str1[32] = {0}; // 全部初始化为0
char str2[32] = "hello"; // 前5字节为'h','e','l','l','o',第6字节为'\0',其余为0
// 方式2:不指定大小,由初始化内容决定
char str3[] = "world"; // 自动分配6字节(包含'\0')
char str4[] = {'w', 'o', 'r', 'l', 'd'}; // 没有'\0',不是合法字符串
// 方式3:动态分配
char *str5 = malloc(32);
if (str5) {
strcpy(str5, "dynamic");
}
重要提示:确保字符串以'\0'结尾,否则标准字符串函数可能导致内存越界访问。
2.2 字符串传参的最佳实践
在函数间传递字符串时,通常有以下几种方式:
- 传递字符数组:函数参数声明为数组形式,但实际上会退化为指针。
c复制void print_string(char str[32]) { // 32被忽略,实际是char *str
printf("%s\n", str);
}
- 传递字符指针:更明确的表达方式。
c复制void print_string(char *str) {
printf("%s\n", str);
}
- 传递指向数组的指针:当需要知道数组大小时使用。
c复制void process_string(char (*str
