我们知道C语言中指针也就是内存地址,指针变量是用来存放内存地址的变量。那么二级指针就是存放的指针的内存地址。
int **arr可以理解为一个二维数组的动态表示。它通常用于创建动态分配的二维数组,允许在运行时根据需要分配和管理内存。以下用一个例子和三个疑问来剖析二级指针和二维数组。
示例:
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
// Step 1: 分配行指针数组
int **arr = (int **)malloc(rows * sizeof(int *));
// Step 2: 为每一行分配内存
for (int i = 0; i < rows; i++) {
arr[i] = (int *)malloc(cols * sizeof(int));
}
// 填充数组
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
arr[i][j] = i * cols + j + 1; // 填充示例数据
}
}
// 打印数组
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
// 释放内存
for (int i = 0; i < rows; i++) {
free(arr[i]); // 释放每一行的内存
}
free(arr); // 释放行指针数组
return 0;
}
一、搞懂:真正的二维数组 vs 二级指针
真正的二维数组(栈上),它本质是:一维数组套一维数组。
int arr[3][4];内存是一整块连续的
1\]\[2\]\[3\]\[4\]\[5\]\[6\]\[7\]\[8\]\[9\]\[10\]\[11\]\[12
用 malloc + int** 写的,内存不是连续的,结构是这样:
arr → [ 指针0 ] → [1][2][3][4]
指针1 \] → \[5\]\[6\]\[7\]\[8
指针2 \] → \[9\]\[10\]\[11\]\[12
arr是一个指针,指向3 个指针- 这 3 个指针,每个又指向4 个 int
- 所以可以用
arr[i][j]
二、为啥arr[i][j] == *(*(arr + i) + j)
在 C 语言中,数组下标运算
a[b]被定义为*(a + b)。在表达式中,数组名通常会退化为指向首元素的指针,因此
a[0] == *a成立。但需要注意:数组名本身不是指针,且该规则成立的前提是指针运算合法。
通过一步步运算我们可以得到arr[i][j] == *(*(arr+i)+j)
(arr[i])[j] --> *(arr[i] +j) --> *(*(arr+i)+j)
含义:
arr+i→ 找到第 i 行指针*(arr+i)→ 拿到第 i 行地址*(arr+i)+j→ 第 i 行第 j 列元素地址*(*(arr+i)+j)→ 取出元素
三、为啥free先free每行再free掉arr
现在的结构是:
arr → 存了3个指针
指针0 → 一行数据
指针1 → 一行数据
指针2 → 一行数据
所以释放顺序必须是:
- 先把3 行数据分别 free 掉
- 再 free 掉
arr自己
如果先 free (arr),那三行数据的地址就丢了,变成内存泄漏。