从简单到复杂
为了理解 int ***ppp,我们最好从最基础的开始,一步步构建起来。

(图片来源网络,侵删)
一维数组
一个普通的一维数组:
int arr[5] = {1, 2, 3, 4, 5};
arr是一个包含 5 个int类型元素的数组。&arr[0]是数组第一个元素的地址,其类型是int *。int *p = arr;定义了一个指向int的指针p,它可以指向arr中的任何一个元素。
二维数组
一个普通的二维数组:
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
matrix是一个包含 3 个元素的数组,这 3 个元素本身都是一个包含 4 个int类型元素的数组。matrix[0]是第一行,它本身是一个包含 4 个int的数组。&matrix[0]是第一行的地址。&matrix[0]的类型是什么?它是一个指向“包含 4 个int的数组”的指针,类型是int (*)[4],我们称之为“数组指针”。int (*p_matrix)[4] = matrix;定义了一个数组指针p_matrix,它可以指向matrix的任意一行。
指针的指针 (Pointer to a Pointer) - int **
int ** 是一个指向“指向 int 的指针”的指针,它通常用于表示一个动态的、不规则的二维数组(即数组的每一行长度可以不同),或者更常见地,表示一个字符串数组(char **)。
int *row1 = malloc(4 * sizeof(int)); int *row2 = malloc(4 * sizeof(int)); // ... int **dynamic_matrix = malloc(3 * sizeof(int *)); dynamic_matrix[0] = row1; dynamic_matrix[1] = row2; // ...
dynamic_matrix是一个int **类型的变量。dynamic_matrix指向一个int *类型的指针(row1的地址)。*dynamic_matrix(即dynamic_matrix[0]) 本身是一个int *,它指向row1数组的第一个元素。**dynamic_matrix(即dynamic_matrix[0][0]) 是一个int,即row1的第一个元素的值。
目标:指针的指针的指针 - int ***
我们终于可以理解 int *** 了。

(图片来源网络,侵删)
int *** 是一个指向“指向 int 的指针的指针”的指针。
***ppp最终解引用到一个int类型的值。**ppp是一个int *类型(指向int的指针)。*ppp是一个int **类型(指向int *的指针)。ppp是一个int ***类型(指向int **的指针)。
定义与使用示例
int ***ppp 这种结构通常用于处理三维数据,或者更常见地,用于动态分配和传递一个不规则的二维数组(`int `)的指针**。
让我们来看一个完整的例子,这个例子将动态分配一个二维数组,然后使用 int *** 来修改这个二维数组的“行指针”本身。
示例代码
#include <stdio.h>
#include <stdlib.h>
// 函数声明
void allocate_and_init_2d_array(int ***ppp, int rows, int cols);
void print_2d_array(int **matrix, int rows, int cols);
void free_2d_array(int **matrix, int rows);
int main() {
int rows = 3;
int cols = 4;
int **my_matrix = NULL; // 初始化为 NULL
// 1. 分配二维数组,并让 my_matrix 指向它
// 我们将 my_matrix 的地址传递给函数,这样函数内部就可以修改 my_matrix 本身
allocate_and_init_2d_array(&my_matrix, rows, cols);
// 2. 打印二维数组
printf("The 2D array after allocation:\n");
print_2d_array(my_matrix, rows, cols);
// 3. 释放二维数组
free_2d_array(my_matrix, rows);
return 0;
}
/**
* @brief 动态分配一个 rows x cols 的二维数组,并通过指针的指针的指针来返回它。
*
* @param ppp 一个指向 "int **" (即二维数组指针) 的指针。
* 也就是 my_matrix 的地址。
* @param rows 行数
* @param cols 列数
*/
void allocate_and_init_2d_array(int ***ppp, int rows, int cols) {
// *ppp 等价于 main 函数中的 my_matrix
// *ppp = ... 的意思是 my_matrix = ...
*ppp = (int **)malloc(rows * sizeof(int *));
if (*ppp == NULL) {
fprintf(stderr, "Memory allocation failed for rows.\n");
exit(EXIT_FAILURE);
}
for (int i = 0; i < rows; i++) {
// 解引用 *ppp 得到 my_matrix,my_matrix[i] 就是分配每一行的内存
(*ppp)[i] = (int *)malloc(cols * sizeof(int));
if ((*ppp)[i] == NULL) {
fprintf(stderr, "Memory allocation failed for columns in row %d.\n", i);
exit(EXIT_FAILURE);
}
// 初始化数据
for (int j = 0; j < cols; j++) {
// (*ppp)[i][j] 等价于 my_matrix[i][j]
(*ppp)[i][j] = i * cols + j + 1;
}
}
}
/**
* @brief 打印一个二维数组
*/
void print_2d_array(int **matrix, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
}
/**
* @brief 释放一个动态分配的二维数组
*/
void free_2d_array(int **matrix, int rows) {
for (int i = 0; i < rows; i++) {
// 先释放每一行的内存
free(matrix[i]);
}
// 再释放存放行指针的数组
free(matrix);
printf("\n2D array freed successfully.\n");
}
代码解析
-
main函数:
(图片来源网络,侵删)int **my_matrix = NULL;我们定义了一个int **类型的指针,并初始化为NULL,这个指针将最终指向我们动态分配的二维数组。allocate_and_init_2d_array(&my_matrix, rows, cols);关键点在这里!我们传递的不是my_matrix,而是&my_matrix(my_matrix的地址)。
-
allocate_and_init_2d_array函数:- 函数参数是
int ***ppp,这意味着ppp是一个指向int **的指针。 - 在
main函数中,我们传递了&my_matrix,在allocate_and_init_2d_array内部,ppp就指向了main函数中的my_matrix变量。 *ppp通过解引用,就得到了main函数中的my_matrix本身,它的类型是int **。*ppp = (int **)malloc(...)这行代码的含义是:修改main函数中my_matrix的值,让它指向新分配的内存块(这个内存块里存放的是指向每一行的指针)。(*ppp)[i] = ...这里的括号是必须的。*ppp的优先级低于[],(*ppp)[i]表示“先解ppp得到my_matrix,然后访问my_matrix的第i个元素”,这等价于my_matrix[i]。
- 函数参数是
int *** 的用途
| 指针类型 | 含义 | 常见用途 |
|---|---|---|
int ***ppp |
指向“指向 int 的指针的指针”的指针 |
动态三维数组(int *** 指向一个 int ** 数组,每个 int * 指向一个 int 数组)。修改一个二维数组指针(`int )本身**,当你需要在一个函数内部分配一个二维数组,并让调用者的指针变量指向这个新分配的数组时,就需要传递int ***`。 |
理解 int *** 的关键在于从里向外逐层解引用,并始终记住每一层指针的类型,虽然看起来复杂,但只要掌握了 int *、int ** 的逻辑,int *** 也就水到渠成了。
