
目录
描述
在 C 语言里,指针属于变量,其作用是存放内存地址。而二级指针指的是指向指针的指针,也就是这个指针变量所存储的内容是另一个指针变量的地址。
定义与声明
下面给出一个简单的定义和声明二级指针的例子:
#include <stdio.h>
int main() {
int num = 10;
int *ptr = # // 一级指针,指向int类型变量
int **pptr = &ptr; // 二级指针,指向一级指针
printf("num的值: %d\n", num);
printf("通过一级指针访问num的值: %d\n", *ptr);
printf("通过二级指针访问num的值: %d\n", **pptr);
return 0;
}
在上述代码中,ptr
是一个一级指针,它存储的是 num
变量的地址;pptr
是一个二级指针,它存储的是 ptr
这个一级指针的地址。要访问 num
的值,对于一级指针使用一个 *
操作符(*ptr
),对于二级指针则需要使用两个 *
操作符(**pptr
)。
内存模型
从内存模型的角度来看,普通变量(如 num
)在内存中占据一块存储空间,存储着具体的数据。一级指针(如 ptr
)也有自己的存储空间,不过其中存储的是另一个变量(num
)的地址。而二级指针(如 pptr
)同样有自己的存储空间,它存储的是一级指针(ptr
)的地址。
二级指针的使用场景
动态内存分配二维数组
在处理二维数组时,如果数组的大小在编译时无法确定,就需要使用动态内存分配。这时可以利用二级指针来实现动态二维数组。
-
动态二维数组的创建与操作
使用二级指针动态分配内存,管理二维数组的行和列,适用于需要灵活调整数组尺寸的场景。
-
函数内修改一级指针的值
当函数需要修改外部指针的指向(如动态分配内存后返回)时,需传递指针的地址(二级指针)。
-
字符串数组或指针数组的操作
处理字符指针数组(如
char* arr[]
)或需要交换指针的场景(如排序),通过二级指针高效操作。 -
数据结构中的节点操作
在链表、树等结构中,用二级指针直接修改头节点或父节点的指针,简化插入/删除逻辑。
注意事项
-
内存管理
-
分配内存时需逐层分配(如先分配行,再分配每行的列)。
-
释放内存时顺序相反,避免内存泄漏。
-
示例错误:
free(ptr)
后未置NULL
,导致野指针。
-
-
空指针检查
对动态分配的内存进行判空,避免解引用未初始化的指针。
-
类型匹配
确保二级指针的类型与实际数据一致(如
int**
不能指向char*
数组)。 -
避免越界访问
操作指针数组时,需确保索引在有效范围内。
案例
案例1:动态二维数组
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3;
int cols = 4;
int **matrix;
// 为二维数组分配内存
matrix = (int **)malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
matrix[i] = (int *)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++) {
for (int j = 0; j < cols; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
// 释放二维数组的内存
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
return 0;
}
在这个例子中,matrix
是一个二级指针。首先为指向一级指针的数组分配内存,接着为每个一级指针指向的一维数组分配内存,从而构建出动态二维数组。
案例2:函数中修改指针的值
当需要在函数内部修改一个指针的值时,就需要传递这个指针的地址,也就是使用二级指针。
#include <stdio.h>
#include <stdlib.h>
void allocate_memory(int **ptr) {
*ptr = (int *)malloc(sizeof(int));
if (*ptr != NULL) {
**ptr = 100;
}
}
int main() {
int *num_ptr = NULL;
allocate_memory(&num_ptr);
if (num_ptr != NULL) {
printf("分配的内存中的值: %d\n", *num_ptr);
free(num_ptr);
}
return 0;
}
在这个代码里,allocate_memory
函数接收一个二级指针 ptr
。在函数内部,通过 *ptr
可以修改传入的一级指针的值,也就是为其分配内存并赋值。
案例3:处理指针数组
在处理指针数组时,也会用到二级指针。例如,在处理字符串数组时,字符串数组实际上是一个指针数组,每个指针指向一个字符串。
#include <stdio.h>
void print_strings(char **strings, int n) {
for (int i = 0; i < n; i++) {
printf("%s\n", strings[i]);
}
}
int main() {
char *fruits[] = {"apple", "banana", "cherry"};
int num_fruits = sizeof(fruits) / sizeof(fruits[0]);
print_strings(fruits, num_fruits);
return 0;
}
在这个例子中,fruits
是一个指针数组,每个元素都是一个指向字符串的指针。print_strings
函数接收一个二级指针 strings
,通过它可以遍历并打印指针数组中的每个字符串。
案例4:函数内修改外部指针
void allocate(int **ptr) {
*ptr = (int*)malloc(sizeof(int));
**ptr = 42;
}
int main() {
int *p = NULL;
allocate(&p); // 通过二级指针修改p的指向
printf("%d\n", *p); // 输出42
free(p);
return 0;
}
案例5:链表头节点插入
typedef struct Node {
int data;
struct Node *next;
} Node;
void insert_head(Node **head, int value) {
Node *new_node = (Node*)malloc(sizeof(Node));
new_node->data = value;
new_node->next = *head;
*head = new_node; // 直接修改头节点指针
}
int main() {
Node *head = NULL;
insert_head(&head, 10); // 无需返回新头节点
insert_head(&head, 20);
return 0;
}
案例6:二叉搜索树(BST)的节点插入
在树结构中,通过二级指针直接修改父节点的子指针(左/右子树),避免冗余的返回值或全局变量。
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
// 递归插入节点,用二级指针直接修改父节点的 left/right 指针
void bst_insert(TreeNode **root, int value) {
if (*root == NULL) {
*root = (TreeNode*)malloc(sizeof(TreeNode));
(*root)->val = value;
(*root)->left = (*root)->right = NULL;
} else {
if (value < (*root)->val) {
bst_insert(&((*root)->left), value); // 传递左子树的地址
} else {
bst_insert(&((*root)->right), value); // 传递右子树的地址
}
}
}
int main() {
TreeNode *root = NULL;
bst_insert(&root, 5); // 插入根节点
bst_insert(&root, 3); // 插入左子树
bst_insert(&root, 7); // 插入右子树
return 0;
}
-
通过
TreeNode**
直接修改父节点的子指针,无需返回新节点地址。 -
递归逻辑中传递
&((*root)->left)
,直接操作下一层指针的地址
案例7:动态字符串数组的排序
对字符串指针数组(char**
)进行排序时,通过二级指针交换指针而非复制字符串内容,提升效率。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 比较函数:按字符串长度排序
int compare(const void *a, const void *b) {
char **str1 = (char**)a;
char **str2 = (char**)b;
return strlen(*str1) - strlen(*str2);
}
int main() {
// 动态分配字符串数组(二级指针)
char **strings = (char**)malloc(4 * sizeof(char*));
strings[0] = strdup("apple");
strings[1] = strdup("banana");
strings[2] = strdup("cherry");
strings[3] = strdup("date");
// 使用 qsort 排序,传递二级指针
qsort(strings, 4, sizeof(char*), compare);
// 输出排序结果
for (int i = 0; i < 4; i++) {
printf("%s\n", strings[i]);
free(strings[i]); // 释放每个字符串
}
free(strings); // 释放指针数组
return 0;
}
-
使用
char**
管理动态字符串数组,排序时仅交换指针(而非字符串内容),时间复杂度为 O(1)。 -
qsort
的比较函数需正确处理二级指针的解引用(char**
→char*
)。