C语言中易混淆问题【数组指针与指针数组详解】
数组指针与指针数组
核心定义与语法的区别
数组指针(指向数组的指针)
- 定义:数组指针是一个指针变量,它指向一个固定长度的数组.
- 语法:
类型(*指针名)[数组长度]
- 示例:
int (*ptr)[5]
; 表示 ptr 是一个指针,指向包含 5 个 int 的数组。 - 关键特征:
- 指针指向数组的
首地址
,解引用后得到整个数组
。 - 指针的步长为 数组长度 × 单个元素大小。
- 指针指向数组的
指针数组(数组存储指针)
- 定义:指针数组是一个数组,其元素均为指针变量。
- 语法:
类型* 数组名[数组长度]
- 示例:
int* ptr_arr[5]
表示:ptr_arr是一个包含5个int*
类型元素的数组。 - 关键特征:
- 数组元素是指针,可指向不同的内存地址(如不同的变量、数组或动态内存)。
- 本质是普通数组,遵循数组的访问规则(通过下标访问指针元素)。
内存布局对比
1.数组指针:(以int (*ptr)[3]
为例)
- 指向对象:一维数组:
int arr[3] = {1,2,3};
- 内存示意图:
arr: ┌───┬───┬───┐│ 1 │ 2 │ 3 │└───┴───┴───┘地址:0x1000 0x1004 0x1008 ptr: ────────────────► arr (0x1000)
-
指针运算:
ptr + 1
偏移3 * sizeof(int) = 12
字节(指向 arr 之后的下一个数组)。2.指针数组(以
int* ptr_arr[2]
为例) -
存储的内容:指针数组存储多个指针,每个指针指向独立的内存区域。
-
内存示意图:
ptr_arr: ┌─────────┬─────────┐│ 0x2000 │ 0x3000 │ (假设分别指向 arr1 和 arr2)└─────────┴─────────┘地址:0x4000 0x4004 arr1: ┌───┐ arr2:┌───┐│ 1 │ │ 4 │└───┘ └───┘0x2000 0x3000
-
访问方式:
ptr_arr[0]
得到0x2000
(指向arr1
),*ptr_arr[0]
解引用得到 1。
典型示例对比:
1.数组指针示例:处理二维数组
#include <stdio.h>int main()
{// 二维数组(2行3列)int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};// 数组指针指向二维数组的行(列数必须与数组一致)int (*row_ptr)[3] = arr; // 等价于 int (*row_ptr)[3] = &arr[0];// 访问第一行第二列元素printf("第一行第二列:%d\n", (*row_ptr)[1]); // 输出 2// 指针移动到第二行row_ptr++;printf("第二行第一列:%d\n", (*row_ptr)[0]); // 输出 4return 0;
}
- 关键点:二维数组名
arr
隐式转换为数组指针int (*)[3]
,指向首行。
2.指针数组示例:管理多个动态数组
#include<stdio.h>
int main()
{//指针数组:存储3个指向int 数组的指针int* ptr_arr[3];//动态分布三个一维数组ptr_arr[0] = (int*)malloc(sizeof(int));ptr_arr[1] = (int*)malloc(2 * sizeof(int));ptr_arr[2] = (int*)malloc(3 * sizeof(int));// 赋值*ptr_arr[0] = 10;ptr_arr[1][0] = 20; ptr_arr[1][1] = 21;ptr_arr[2][0] = 30; ptr_arr[2][1] = 31; ptr_arr[2][2] = 32; // 访问printf("第一个数组元素:%d\n", *ptr_arr[0]); // 10printf("第二个数组元素:%d\n", ptr_arr[1][1]); // 21// 释放内存for (int i = 0; i < 3; i++) {free(ptr_arr[i]);}return 0;
}
维度
数组指针
指针数组
本质
指针(指向一个固定大小的数组)
数组(元素为指针类型)
语法核心
(*) 优先(先声明为指针)
[] 优先(先声明为数组)
定义示例
int (ptr)[5];
int ptr[5];
内存占用
占一个指针的大小(如 8 字节,64 位)
占 n × 指针大小(如 5×8=40 字节)
典型用途
二维数组行操作、固定列数的数组传递
动态数组管理、字符串数组、指针集合
初始化方式
指向已有数组(如 ptr = &arr;)
逐个初始化指针元素(如 ptr[i] = &var;)
访问元素
通过 (*ptr)[i] 或 ptr[i][j] 访问
通过 ptr[i] 访问指针指向的值
常见场景
函数参数传递二维数组(列数固定)
存储多个动态分配的内存地址
经典应用场景
1. 数组指针:
1.处理多维数组:当需要指针遍历或多维数组时
//ptr 直接指向二维数组的行,偏移量为 3*sizeof(int),无需额外转换,可直接通过 ptr[i][j] 访问元素
#include <stdio.h>
int main()
{int arr[2][3] = {{1,2,3}, {4,5,6}};int (*ptr)[3] = arr; // 指向第一行(类型为int(*)[3])// 遍历数组for (int i = 0; i < 2; i++) {for (int j = 0; j < 3; j++) {printf("%d ", ptr[i][j]); // 等价于*(*(ptr+i)+j)}printf("\n");}return 0;
}
int arr[2][3] = {{1,2,3},{4,5,6}};
int (*ptr)[3] = arr //指向第一行
printf("%d\n",ptr[1][2]); //输出6
2.动态分配二维数组(连续内存)
//优势:内存连续,缓存命中率高释放简单,只需一次 free
#include <stdio.h>
#include <stdlib.h>int main()
{int rows = 3, cols = 4;int (*matrix)[cols] = malloc(rows * sizeof(*matrix));// 初始化for (int i = 0; i < rows; i++)for (int j = 0; j < cols; j++)matrix[i][j] = i * cols + j;// 释放内存free(matrix); // 一次释放全部内存return 0;
}
3.函数参数传递多维数组
//注意:函数参数中 ,int (*arr)[3] 必须指定列数,等价于 int arr[][3],但指针形式更清晰
#include <stdio.h>// 计算二维数组的和(必须指定列数)
int sum(int (*arr)[3], int rows)
{int total = 0;for (int i = 0; i < rows; i++)for (int j = 0; j < 3; j++)total += arr[i][j];return total;
}int main()
{int arr[2][3] = {{1,2,3}, {4,5,6}};printf("Sum: %d\n", sum(arr, 2)); // 输出21return 0;
}
// 函数声明:处理二维数组(列数固定为3)
// 参数说明:
// - int (*arr)[3]:数组指针,指向包含3个int的一维数组(即二维数组的行)
// - rows:二维数组的行数
void process_2d_array(int (*arr)[3], int rows)
{// 外层循环:遍历每一行(i表示行号)for (int i = 0; i < rows; i++) {// 内层循环:遍历当前行的每一列(j表示列号,列数固定为3)for (int j = 0; j < 3; j++) {// 访问二维数组元素并乘以2// 等价于:(*(arr + i))[j] *= 2;// 解析:// - arr + i:指向第i行(数组指针偏移i行,每行3个int)// - *(arr + i):解引用得到第i行的一维数组(类型为int[3])// - [j]:访问该行的第j个元素arr[i][j] *= 2; }}
}int main()
{// 定义二维数组:2行3列int arr[2][3] ={{1, 2, 3}, // 第0行{4, 5, 6} // 第1行};// 调用函数处理二维数组// 传递数组名arr时,会隐式转换为数组指针int (*)[3](指向首行)process_2d_array(arr, 2); // 若需要验证结果,可添加打印代码:// for (int i = 0; i < 2; i++){// for (int j = 0; j < 3; j++) {// printf("%d ", arr[i][j]);// }// }return 0;
}
// 2 4 6 8 10 12
2.指针数组:
1.动态二维数组(非连续内存)
#include <stdio.h>
#include <stdlib.h>int main()
{int rows = 3, cols = 4;int* matrix[rows]; // 创建指针数组// 为每行分配内存(每行可不同长度)for (int i = 0; i < rows; i++) {matrix[i] = malloc(cols * sizeof(int));}// 初始化for (int i = 0; i < rows; i++)for (int j = 0; j < cols; j++)matrix[i][j] = i * cols + j;// 释放内存(必须逐行释放)for (int i = 0; i < rows; i++) {free(matrix[i]);}return 0;
}
//matrix[0] → [0, 1, 2, 3]
//matrix[1] → [4, 5, 6, 7]
//matrix[2] → [8, 9, 10, 11]
//优势:每行可独立分配不同长度,适合不规则数组
2.指向独立内存块
#include <stdio.h>int main()
{int a = 10, b = 20, c = 30;int* ptr[3] = {&a, &b, &c}; // 每个指针指向不同变量for (int i = 0; i < 3; i++){printf("%d ", *ptr[i]); // 输出: 10 20 30}return 0;
}
3.字符串数组
#include <stdio.h>int main()
{char* names[3] = {"Alice", // 指向字符串常量的指针"Bob","Charlie"};for (int i = 0; i < 3; i++) {printf("Name %d: %s\n", i, names[i]);}return 0;
}
//本质:names 是一个包含3个 char* 的数组,每个指针指向一个字符串常量
字符串数组(存储字符串指针)
#include <string.h> // 提供strcpy等字符串处理函数int main()
{// 指针数组:存储多个字符串常量的地址// 每个字符串常量存储在只读内存区,str_arr存储它们的首地址char* str_arr[] = {"apple", "banana", "cherry"};// 遍历指针数组,输出每个字符串for (int i = 0; i < 3; i++) {printf("字符串 %d: %s\n", i+1, str_arr[i]);// str_arr[i] 是第i个字符串的首地址,%s自动解析字符串直到'\0'}// 动态创建指针数组(在堆区分配内存)char** dyn_str_arr = (char**)malloc(2 * sizeof(char*));// 分配2个指针的空间,dyn_str_arr指向该内存块// 为第一个指针分配内存并复制字符串dyn_str_arr[0] = (char*)malloc(5 * sizeof(char)); // 分配5字节(含'\0')strcpy(dyn_str_arr[0], "test"); // 复制"test"到分配的内存中// 注意:代码未释放内存(存在内存泄漏)// 正确做法应在return前添加:free(dyn_str_arr[0]);free(dyn_str_arr);return 0;
}
//字符串 1: apple
//字符串 2: banana
//字符串 3: cherry
-
静态字符串数组
str_arr
-
str_arr ┌─────────┬─────────┬─────────┐│ 0x1000 │ 0x1006 │ 0x1013 │ (字符串常量地址)└─────────┴─────────┴─────────┘↓ ↓ ↓"apple\0" "banana\0" "cherry\0"0x1000 0x1006 0x1013 (只读内存区)
-
str_arr
是栈上的指针数组,每个元素指向一个字符串常量。
-
-
动态指针数组
dyn_str_arr
-
dyn_str_arr ───► ┌─────────┬─────────┐ (堆区:2个指针)│ 0x2000 │ NULL │└─────────┴─────────┘↓┌─────────────┐ (堆区:5字节)│ t e s t \0 │└─────────────┘0x2000
-
dyn_str_arr
指向堆区的指针数组 -
dyn_str_arr[0]
指向另一段堆内存,存储 “test”
-
-
动态内存分配
-
char** ptr = (char**)malloc(n * sizeof(char*)); // 分配指针数组 ptr[i] = (char*)malloc(len * sizeof(char)); // 为每个指针分配字符串空间
总结:
处理固定列数的二维数组 |数组指针 |保持行列逻辑,高效传递二维数组
管理动态或变长数组 |指针数组 |灵活分配内存,每个指针可独立操作
存储字符串集合 | 指针数组 |字符串本质是 char*,适合用指针数组管理
按行操作二维数组 |数组指针 |直接操作行数据,符合内存布局特性
内存布局 | 连续存储整个多维数组 | 存储多个指针,每个指针可能指向不同内存区域 |
---|---|---|
指针类型 | ptr 是一个指针,类型为 int(*)[n] | ptr 是数组,类型为 int*[n] |
偏移量 | ptr + 1 跳过 n*sizeof(int) 字节 | ptr + 1 跳过 sizeof(int*) 字节 |
初始化 | int arr[2][3]; int (*ptr)[3] = arr; | int* ptr[3] = {malloc(4), malloc(4), malloc(4)}; |
访问方式 | ptr[i][j] 直接访问 | ptr[i][j] 通过指针间接访问 |
内存释放 | 一次 free(ptr) | 需逐个释放每个指针:for(i) free(ptr[i]) |
典型场景 | 规则矩阵运算、高效遍历 | 不规则数组、动态长度数组、字符串数组 |
常见错误与注意事项
错误1:混淆指针类型
int arr[2][3];
int (*ptr)[3] = arr; // 正确:ptr指向第一行
int* ptr2 = arr; // 错误:类型不匹配(int* 与 int(*)[3])
错误2:错误释放内存
int rows = 3, cols = 4;
int* matrix[rows];
for (int i = 0; i < rows; i++)
{matrix[i] = malloc(cols * sizeof(int));
}free(matrix); // 错误!只释放了指针数组,未释放每行内存
// 正确做法:
for (int i = 0; i < rows; i++)
{free(matrix[i]);
}