1. 指针与数组的本质区别
在C语言中,指针和数组是两个经常被混淆的概念。很多初学者会认为数组名就是一个指针,但实际上它们有着本质的区别。
指针是一个变量,它存储的是另一个变量的内存地址。而数组名则是一个常量指针,它代表的是数组首元素的地址,但这个地址不能被修改。举个例子:
int arr[5] = {1, 2, 3, 4, 5}; int *ptr = arr; // ptr指向数组第一个元素 // 以下操作是合法的 ptr++; // 指针可以移动 // 但以下操作是非法的 arr++; // 数组名是常量指针,不能修改注意:虽然arr和ptr都指向同一个地址,但sizeof(arr)会返回整个数组的大小(5*sizeof(int)),而sizeof(ptr)只会返回指针本身的大小(通常是4或8字节)。
2. 数组名的特殊性质
数组名在大多数情况下会退化为指向其首元素的指针,但这种退化有几个重要的例外情况:
2.1 sizeof运算符
当对数组名使用sizeof时,它不会退化为指针,而是返回整个数组占用的字节数:
int arr[10]; printf("%zu\n", sizeof(arr)); // 输出40(假设int是4字节)2.2 &运算符
对数组名使用&运算符时,得到的是指向整个数组的指针,而不是指向数组首元素的指针:
int arr[10]; int (*p1)[10] = &arr; // 指向整个数组的指针 int *p2 = arr; // 指向数组首元素的指针虽然p1和p2的值相同,但它们的类型不同。p1+1会跳过整个数组的大小(40字节),而p2+1只会跳过一个int的大小(4字节)。
3. 指针与数组的相互转换
尽管数组名不是指针,但在很多情况下它们可以互换使用:
3.1 数组下标访问
指针可以使用数组下标语法:
int arr[5] = {1, 2, 3, 4, 5}; int *ptr = arr; printf("%d\n", ptr[2]); // 输出33.2 指针算术运算
数组名可以用于指针算术运算:
int arr[5] = {1, 2, 3, 4, 5}; printf("%d\n", *(arr + 2)); // 输出3实际上,arr[i]在编译器内部会被转换为*(arr + i)。
4. 多维数组与指针
多维数组与指针的关系更为复杂。考虑一个二维数组:
int matrix[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };4.1 二维数组的存储
在内存中,二维数组是按行优先顺序连续存储的。上面的matrix在内存中的布局是: 1,2,3,4,5,6,7,8,9,10,11,12
4.2 访问二维数组元素
可以通过多种方式访问二维数组元素:
// 常规数组访问 printf("%d\n", matrix[1][2]); // 输出7 // 使用指针算术 int *ptr = &matrix[0][0]; printf("%d\n", *(ptr + 1*4 + 2)); // 同样输出74.3 数组指针
可以定义指向数组的指针:
int (*p)[4] = matrix; // 指向包含4个int的数组的指针 printf("%d\n", p[1][2]); // 输出75. 指针数组与数组指针
这是两个容易混淆的概念:
5.1 指针数组
指针数组是一个数组,其元素都是指针:
int a = 1, b = 2, c = 3; int *ptr_arr[3] = {&a, &b, &c}; // 指针数组5.2 数组指针
数组指针是一个指针,它指向一个数组:
int arr[3] = {1, 2, 3}; int (*arr_ptr)[3] = &arr; // 数组指针6. 函数参数中的数组与指针
当数组作为函数参数传递时,它总是会退化为指针:
void func(int arr[]) { // 实际上等同于int *arr printf("%zu\n", sizeof(arr)); // 输出指针大小,不是数组大小 }因此,在函数内部无法通过sizeof获取数组的实际大小,通常需要额外传递数组长度参数。
7. 动态内存分配与指针
指针与数组的关系在动态内存分配中表现得尤为明显:
int *dynamic_arr = malloc(5 * sizeof(int)); // 动态分配数组 if (dynamic_arr) { for (int i = 0; i < 5; i++) { dynamic_arr[i] = i + 1; // 可以像数组一样使用 } free(dynamic_arr); // 记得释放内存 }8. 常见错误与陷阱
8.1 数组越界访问
int arr[5] = {0}; printf("%d\n", arr[5]); // 未定义行为8.2 返回局部数组的指针
int *bad_func() { int arr[5] = {1, 2, 3, 4, 5}; return arr; // 错误:arr是局部变量,函数返回后失效 }8.3 混淆指针与数组
char *str1 = "hello"; // 字符串常量 char str2[] = "hello"; // 字符数组 str1[0] = 'H'; // 运行时错误:尝试修改字符串常量 str2[0] = 'H'; // 正确:修改数组内容9. 实际应用示例
9.1 字符串处理
char str[] = "Hello, World!"; char *p = str; while (*p) { putchar(*p++); // 逐个字符输出字符串 }9.2 数组排序
void bubble_sort(int *arr, int size) { for (int i = 0; i < size - 1; i++) { for (int j = 0; j < size - i - 1; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }10. 性能考虑
指针和数组在性能上有些微妙的差异:
- 数组访问通常会被编译器优化为直接内存访问
- 指针解引用可能需要额外的指令
- 但对于现代编译器来说,这种差异通常可以忽略不计
在实际编程中,应该更注重代码的可读性和正确性,而不是过早优化。