C语言 二级指针应用场景

简介: 本文介绍了二级指针在 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 <stdlib.h>

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 <stdio.h>
#include <stdlib.h>

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;
}
相关文章
|
2月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
186 9
|
2月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
65 7
|
2月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
124 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
3月前
|
机器学习/深度学习 算法 数据挖掘
C语言在机器学习中的应用及其重要性。C语言以其高效性、灵活性和可移植性,适合开发高性能的机器学习算法,尤其在底层算法实现、嵌入式系统和高性能计算中表现突出
本文探讨了C语言在机器学习中的应用及其重要性。C语言以其高效性、灵活性和可移植性,适合开发高性能的机器学习算法,尤其在底层算法实现、嵌入式系统和高性能计算中表现突出。文章还介绍了C语言在知名机器学习库中的作用,以及与Python等语言结合使用的案例,展望了其未来发展的挑战与机遇。
78 1
|
3月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
99 1
|
3月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
3月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
99 5
|
3月前
|
网络协议 物联网 数据处理
C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势
本文探讨了C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势。文章详细讲解了使用C语言实现网络通信程序的基本步骤,包括TCP和UDP通信程序的实现,并讨论了关键技术、优化方法及未来发展趋势,旨在帮助读者掌握C语言在网络通信中的应用技巧。
81 2
|
3月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
72 1
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
37 3

热门文章

最新文章