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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/767404.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】表的操作{创建/查看/修改/删除}

文章目录 1.创建表1.1comment&#xff1a;注释信息1.2存储引擎 2.查看表3.修改表3.1add添加列&#xff0c;对原数据无影响3.2drop删除列3.3modify修改列类型3.4change修改列名3.5rename [to]修改表名 4.删除表5.总结 1.创建表 CREATE TABLE table_name (field1 datatype,field…

昇思MindSpore学习笔记3-02热门LLM及其他AI应用--K近邻算法实现红酒聚类

摘要&#xff1a; 介绍了K近邻算法&#xff0c;记录了MindSporeAI框架使用部分wine数据集进行KNN实验的步聚和方法。包括环境准备、下载红酒数据集、加载数据和预处理、搭建模型、进行预测等。 一、KNN概念 1. K近邻算法K-Nearest-Neighbor(KNN) 用于分类和回归的非参数统计…

2024年下半年系统集成项目管理工程师使用新版教材,该如何备考?

2024年下半年&#xff0c;新版的《系统集成项目管理工程师教程》&#xff08;第3版&#xff09;将被系统集成项目管理工程师使用。许多考生可能会感到迷茫&#xff0c;不知道该如何复习。毕竟教材更新后&#xff0c;以往的资料和真题都变得无用&#xff0c;重点内容和考试方向也…

llm学习-3(向量数据库的使用)

1&#xff1a;数据读取和加载 接着上面的常规操作 加载环境变量---》获取所有路径---》加载文档---》切分文档 代码如下&#xff1a; import os from dotenv import load_dotenv, find_dotenvload_dotenv(find_dotenv()) # 获取folder_path下所有文件路径&#xff0c;储存在…

OV SSL证书年度成本概览:为企业安全护航的经济之选

在当今数字化时代&#xff0c;企业网站不仅是品牌展示的窗口&#xff0c;更是与客户沟通的桥梁。然而&#xff0c;随着网络威胁的不断升级&#xff0c;保护网站安全成为了企业不可忽视的任务。SSL证书&#xff0c;特别是OV SSL证书&#xff0c;因其对企业身份的严格验证&#x…

Halcon OCR字符识别(极坐标转换,字符识别)

Halcon OCR字符识别&#xff08;极坐标转换&#xff0c;字符识别&#xff09; 代码 * 1.加载图片 *************************************************** dev_close_window () read_image (Image, ./img) get_image_size (Image, Width, Height) dev_get_window (WindowHandle…

聚鼎贸易:装饰画开店教程新手入门

当艺术遇上商业&#xff0c;每一幕交易皆是文化的交流。本篇将引领有志于开设装饰画店铺的新手们&#xff0c;迈入创业的门槛&#xff0c;以独特的视角和步骤&#xff0c;探索如何成功经营一家装饰画店。 精选货源乃基石。货源的选取不仅反映店主的品味&#xff0c;更直接影响到…

NPDP|产品经理的沟通协调能力:塑造产品成功的核心力量

在快速发展的商业环境中&#xff0c;产品经理的角色愈发重要。他们不仅要负责产品的战略规划、需求管理、项目管理&#xff0c;更要与团队内外各方进行有效的沟通协调。那么&#xff0c;产品经理的沟通协调能力到底有多重要呢&#xff1f;本文将深入探讨这一话题。 沟通是产品成…

使用css做一个旋转的八卦图

使用css做一个旋转的八卦图 1, html部分 <div class"tai"><div class"bai"></div><div class"hei"></div> </div>2, css部分 .tai{width: 200px;height: 200px;border: 1px solid #000;background: linea…

【Python机器学习】模型评估与改进——回归指标

对于回归问题&#xff0c;可以像分类问题一样进行详细评估&#xff0c;例如&#xff0c;对目标值估计过高与目标值估计过低进行对比分析。 但是&#xff0c;对于我们见过的大多数应用来说&#xff0c;使用默认就足够了&#xff0c;它由所有回归器的score方法给出。业务决策有时…

全面详解菲律宾slots游戏本土网盟广告CPI流量效果分析

全面详解菲律宾slots游戏本土网盟广告CPI流量效果分析 一、引言 随着互联网的普及和移动设备的广泛应用&#xff0c;网络游戏行业迅速崛起&#xff0c;成为全球娱乐市场的一大热门。菲律宾作为东南亚地区的重要国家&#xff0c;其网络游戏市场也呈现出蓬勃的发展势头。在这样的…

AI数字人直播源码出售价格公布!

随着数字人行业的兴起&#xff0c;以数字人直播为代表的应用场景逐渐成为人们日常生活中不可分割的一部分&#xff0c;再加上艾媒研究数据显示&#xff0c;超五成以上的被调查群体的企业使用过虚拟人技术&#xff0c;超三成被调查群体的企业计划使用虚拟人技术等结论的公布&…

网友炸锅:这款iPhone壳竟能直接放保时捷车钥匙?

在当今这个个性化消费时代&#xff0c;高端智能手机及其配件已经成为了展示个人身份和品味的重要途径。最近&#xff0c;一款专为保时捷车主量身定制的iPhone手机壳&#xff0c;在互联网上引发了广泛的关注和讨论。 这款手机壳不仅在设计上凸显了保时捷的品牌logo&#xff0c;…

游戏工作室如何巧妙应对IP封禁风险?

游戏工作室在使用IP时&#xff0c;面临着封号的风险&#xff0c;因此需要采取一些防封技巧来保护自己的运营。以下是一些游戏工作室常用的防封技巧。 1. 多IP轮换 游戏工作室可以使用多个代理IP&#xff0c;并定期轮换它们。这样做可以减少单个IP被频繁访问同一游戏服务器而被…

for循环中list触发fast-fail或不触发的原理和方法

Iterable和Iterator Iterator接口位于的位置是java.util.Iterator&#xff0c;它主要有两个抽象方法供子类实现。hasNext()用来判断还有没有数据可供访问&#xff0c;next()用来访问下一个数据。 集合Collection不是直接去实现Iterator接口&#xff0c;而是去实现Iterable接口…

Pycharm设置远程解释器(本地代码+远程服务器环境)

Pycharm设置远程解释器(本地代码+服务器环境) Pycharm设置远程开发环境: 1.点击IDE左上角“文件”-》点击其中的设置 2:点击项目->点击添加解释器 3: 4根据提示填入远程服务器的IP地址,端口,和用户名: 5:连接上后选择现有环境: 6:选择刚才建立好的conda环境…

ubnutu20.04-vscode安装leetcode插件流程

1.在vscode插件商城选择安装leetcode 2.安装node.js 官网下载一个版本安装流程&#xff1a; ①tar -xvf node-v14.18.0-linux-x64.tar.xz ①sudo ln -s /app/software/nodejs/bin/npm /usr/local/bin/ ②ln -s /app/software/nodejs/bin/node /usr/local/bin/ 查看版本&…

G1 垃圾收集器

从 JDK1.9 开始默认 G1&#xff0c;应用在多处理器和大容量内存环境中。 基础概念 Region G1 给整一块Heap内存区域均匀等分了N个 Region&#xff0c;N 默认情况下是 2048。 Region的大小只能是1M、2M、4M、8M、16M或32M (1-32M,并且为2的指数)&#xff0c;比如-Xmx16g -Xms…

【Unity 角色控制器组件】

【Unity 角色控制器组件】 Character Controller&#xff1a; Unity 内置的一个组件&#xff0c;用于提供高级的物理控制&#xff0c;允许开发者控制角色的移动、跳跃和碰撞。 csharp csharp // 假设你已经有了一个带有Character Controller组件的游戏对象// 获取Character Co…

第十四章 路由器 OSPF 动态路由配置

实验目的 掌握 OSPF 协议的配置方法&#xff1a; 掌握查看通过动态路由协议 OSPF 学习产生的路由&#xff1b; 熟悉广域网线缆的链接方式&#xff1b; 实验背景 假设校园网通过一台三层交换机连到校园网出口路由器上&#xff0c; 路由器再和校园外的另一台路由器连接。…