16. C语言二级指针

目录

描述

定义与声明

内存模型

二级指针的使用场景

注意事项

案例

案例1:动态二维数组

案例2:函数中修改指针的值

案例3:处理指针数组

案例4:函数内修改外部指针

案例5:链表头节点插入

案例6:二叉搜索树(BST)的节点插入

案例7:动态字符串数组的排序


描述

在 C 语言里,指针属于变量,其作用是存放内存地址。而二级指针指的是指向指针的指针,也就是这个指针变量所存储的内容是另一个指针变量的地址。

定义与声明

下面给出一个简单的定义和声明二级指针的例子:

复制代码
#include <stdio.h>

int main() {
    int num = 10;
    int *ptr = &num;  // 一级指针,指向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)的地址。

二级指针的使用场景

动态内存分配二维数组

在处理二维数组时,如果数组的大小在编译时无法确定,就需要使用动态内存分配。这时可以利用二级指针来实现动态二维数组。

  1. 动态二维数组的创建与操作

    使用二级指针动态分配内存,管理二维数组的行和列,适用于需要灵活调整数组尺寸的场景。

  2. 函数内修改一级指针的值

    当函数需要修改外部指针的指向(如动态分配内存后返回)时,需传递指针的地址(二级指针)。

  3. 字符串数组或指针数组的操作

    处理字符指针数组(如 char* arr[])或需要交换指针的场景(如排序),通过二级指针高效操作。

  4. 数据结构中的节点操作

    在链表、树等结构中,用二级指针直接修改头节点或父节点的指针,简化插入/删除逻辑。

注意事项

  1. 内存管理

    • 分配内存时需逐层分配(如先分配行,再分配每行的列)。

    • 释放内存时顺序相反,避免内存泄漏。

    • 示例错误:free(ptr) 后未置 NULL,导致野指针。

  2. 空指针检查

    对动态分配的内存进行判空,避免解引用未初始化的指针。

  3. 类型匹配

    确保二级指针的类型与实际数据一致(如 int** 不能指向 char* 数组)。

  4. 避免越界访问

    操作指针数组时,需确保索引在有效范围内。

案例

案例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*)。

相关推荐
genispan33 分钟前
python基础8 单元测试
开发语言·python·单元测试
钢铁男儿5 小时前
Python 生成数据(随机漫步)
开发语言·python·信息可视化
正经教主5 小时前
【菜鸟飞】在vsCode中安装python的ollama包出错的问题
开发语言·人工智能·vscode·python·ai·编辑器
Dongliner~5 小时前
【QT:多线程、锁】
开发语言·qt
鹏神丶明月天6 小时前
mybatis_plus的乐观锁
java·开发语言·数据库
极客代码6 小时前
Unix 域套接字(本地套接字)
linux·c语言·开发语言·unix·socket·unix域套接字·本地套接字
Zhuai-行淮6 小时前
施磊老师高级c++(一)
开发语言·c++
ylfhpy6 小时前
Java面试黄金宝典1
java·开发语言·算法·面试·职场和发展
神秘的土鸡6 小时前
Centos搭建Tomcat服务器:我的实战经验分享(成功版本 详细!)
linux·开发语言·python·tomcat·web