• C语言-二级指针应用场景


    二级指针应用

    引子:在线性表 销毁函数中,传入二级指针作为参数,可以实现对线性表的销毁操作。

    //销毁已存在的线性表
    void DestroyList(list_t **L){
        // Step 1: 检查L是否为非空指针
        if(L) 
            // Step 2: 释放L指向的内存空间
            free(*L);
        // Step 3: 将L所指向的指针置为NULL,避免悬挂指针
        *L=NULL;
    }
    

    上述代码主要功能是销毁一个已存在的线性表。通过接收一个二级指针(指向线性表的指针),函数首先检查传入的二级指针是否为非空。然后释放线性表所占用的内存空间,并将一级指针置为 NULL,从而避免悬挂指针的问题。这是对动态内存管理中销毁操作的安全处理方式。

    list_t **L 为什么使用二级指针

    在这个函数中,list_t **L 是一个二级指针,而不仅仅是一级指针,这是为了能够在函数内部修改指向线性表的指针。

    原因:

    1.直接操作并修改原指针: 如果传入的是一个一级指针 list_t *L,那么在函数内部只能操作其指向的内存内容,但无法修改这个指针本身。如果要让外部也能感知到这个指针已经被置为 NULL,需要使用二级指针。

    2.避免悬挂指针: 如果只是释放内存而不置 NULL,外部仍然有一个一级指针指向已释放的内存,这会导致悬挂指针,进而可能引发崩溃或不可预知的错误。

    例子对比
    void DestroyList(list_t *L) {
        if (L) free(L);
        // 此处无法将传入的一级指针置为 NULL
    }
    
    void test() {
        list_t *list = malloc(sizeof(list_t));
        DestroyList(list);
        // 此时 list 仍然指向原内存,但内存已经被释放,存在悬挂指针风险
    }
    
    

    通过使用二级指针,我们可以在函数内部将指针置为 NULL,避免悬挂指针:

    void DestroyList(list_t **L) {
        if (L && *L) {
            free(*L);
            *L = NULL;
        }
    }
    
    void test() {
        list_t *list = malloc(sizeof(list_t));
        DestroyList(&list);
        // 此时 list 已经被置为 NULL,避免悬挂指针
    }
    

    详细示例解释

    void DestroyList(list_t **L) {
        if (L && *L) {
            // 释放 L 所指向的内存
            free(*L);
            // 将 L 所指向的第一个指针 (即原来的指针) 置为 NULL
            *L = NULL;
        }
    }
    
    void test() {
        list_t *list = malloc(sizeof(list_t)); // 分配内存给 list
        DestroyList(&list); // 传入 list 的地址,即传入 &list,从而在函数内部操作的是 list 的地址
        // 此时 list 已经被安全地置为 NULL
    }
    
    总结

    使用二级指针的主要目的是为了安全地管理和修改指针,避免悬挂指针问题。在函数内部,能够直接修改调用者的指针,使其指向 NULL,从而提升代码的健壮性。

    在什么情况下要使用这种方法

    使用二级指针(也称为指针的指针)主要出现在以下几种情况下:

    1.修改传入函数的指针本身

    如果你需要在函数内部修改传入的指针,使得修改对调用者可见,就需要使用二级指针。
    例如,在许多动态内存管理操作中,我们需要确保在释放对象内存后,将指针置为 NULL。

    void destroy_list(list_t **L) {
        if (L && *L) {
            free(*L);
            *L = NULL;
        }
    }
    
    

    2.动态分配内存并返回一个结果

    如果函数需要动态分配内存,并将地址返回给调用者,同样需要使用二级指针。

    #include 
    
    int allocate_list(list_t **L) {
        *L = (list_t *)malloc(sizeof(list_t));
        if (*L == NULL) {
            return INFEASIBLE;
        }
        // 初始化操作
        memset(*L, 0, sizeof(list_t));
        return OK;
    }
    
    void test() {
        list_t *list;
        int result = allocate_list(&list);
        if (result == OK) {
            // 使用 list
        }
    }
    
    

    3.链表操作

    在链表(如单链表、双链表)的插入、删除操作中,也经常使用二级指针来简化操作。
    例如,删除链表中的某个节点时,通过二级指针可以减少对头节点的特殊处理

    typedef struct Node {
        int data;
        struct Node *next;
    } Node;
    
    void delete_node(Node **head, int key) {
        Node **current = head;
        while (*current) {
            if ((*current)->data == key) {
                Node *temp = *current;
                *current = (*current)->next;
                free(temp);
                return;
            }
            current = &((*current)->next);
        }
    }
    
    

    4.动态数组的重分配

    在动态数组的重分配中,也可以使用二级指针来更新数组的指针。

    int resize_array(int **array, size_t new_size) {
        int *temp = (int *)realloc(*array, new_size * sizeof(int));
        if (temp == NULL) {
            return INFEASIBLE;
        }
        *array = temp;
        return OK;
    }
    
    void test() {
        int *array = (int *)malloc(10 * sizeof(int));
        if (array) {
            int result = resize_array(&array, 20);
            if (result == OK) {
                // 使用重分配后的 array
            }
            free(array);
        }
    }
    
    

    结论

    修改调用者的指针: 二级指针可以在函数内部修改传入的指针,使得调用者能够感知到这些修改。

    动态内存分配: 在动态内存分配和重新分配时,使用二级指针可以返回新的分配地址。

    链表操作: 在链表操作中,二级指针可以简化代码,减少特殊情况的处理。

    管理复杂数据结构: 二级指针可以用来管理复杂的数据结构,如数组的数组、结构体的数组等。

    通过使用二级指针,可以使代码更加灵活,避免悬挂指针,提升程序的健壮性和可维护性。

    更多相关的补充

    5.用于二维数组

    C语言中,要创建动态二维数组,不得不使用双重指针。

    #include 
    #include 
    
    int** create_2d_array(int rows, int cols) {
        int** array = malloc(rows * sizeof(int*));
        for (int i = 0; i < rows; i++) {
            array[i] = malloc(cols * sizeof(int));
        }
        return array;
    }
    
    void free_2d_array(int** array, int rows) {
        for (int i = 0; i < rows; i++) {
            free(array[i]);
        }
        free(array);
    }
    
    int main() {
        int rows = 5, cols = 5;
        int** array = create_2d_array(rows, cols);
        
        // 使用数组
        
        free_2d_array(array, rows);
        return 0;
    }
    

    6.递归链表和树的操作

    在递归函数中使用,可以简化很多链表或树的插入和删除操作。

    void insert_sorted(Node **head, int value) {
        if (*head == NULL || (*head)->data >= value) {
            Node *new_node = malloc(sizeof(Node));
            new_node->data = value;
            new_node->next = *head;
            *head = new_node;
        } else {
            insert_sorted(&(*head)->next, value);
        }
    }
    
    

    7.回调函数的参数

    当编写需要传入函数指针作为参数的函数时,有时候也需要使用,以便在回调函数中修改外部变量。

    void modify_value(int **ptr) {
        **ptr = 100;
    }
    
    int main() {
        int *p = malloc(sizeof(int));
        *p = 10;
        modify_value(&p);
        printf("%d\n", *p);  // 输出100
        free(p);
        return 0;
    }
    
    

    8.泛型容器的实现

    在实现像链表、数组等泛型容器时,可以用来简化大量的指针操作。
    例如,void* 类型的链表可以存储任何类型的数据,但需要使用双重指针来实现泛型操作。

    typedef struct GenericNode {
        void *data;
        struct GenericNode *next;
    } GenericNode;
    
    void add_node(GenericNode **head, void *data) {
        GenericNode *new_node = malloc(sizeof(GenericNode));
        new_node->data = data;
        new_node->next = *head;
        *head = new_node;
    }
    
    

    9.跟踪指针的原位置

    当处理需要返回多个结果时,利用指针可以避免多次传参。

    void split(char *str, char delimiter, char **left, char **right) {
        char *pos = strchr(str, delimiter);
        if (pos) {
            *pos = '\0';  // 分隔符位置置为\0
            *left = str;
            *right = pos + 1;
        } else {
            *left = str;
            *right = NULL;
        }
    }
    
    int main() {
        char str[] = "hello,world";
        char *left, *right;
        split(str, ',', &left, &right);
        printf("left: %s, right: %s\n", left, right);  // 输出 left: hello, right: world
        return 0;
    }
    
    
  • 相关阅读:
    CentOS 7操作系统使用LAMP架构安装Zabbix 5.0监控系统的详细指南
    【Java】图书管理系统,完整版+源代码!!!
    多版本node的安装与切换详细操作
    算法进阶-2sat-cf-1400C
    9月总共面试34次,我人麻了....
    Java和前端都不好找工作,计算机毕业可以干嘛?
    手把手教你一键部署自己的HTML静态网页
    猿创征文| 怎么提高 Java 开发效率?使用 Idea 和它的插件
    Uniform Asymptotics by Alexei
    JVM对象内存分配
  • 原文地址:https://blog.csdn.net/gopher9511/article/details/139996949