如何使用GLib的单向链表GSList

单向链表是一种基础的数据结构,也是一种简单而灵活的数据结构,本文讨论单向链表的基本概念及实现方法,并着重介绍使用GLib的GList实现单向链表的方法及步骤,本文给出了多个实际范例源代码,旨在帮助学习基于GLib编程的读者较快地掌握GSList的使用方法,本文程序在 ubuntu 20.04 下编译测试完成,gcc 版本号 9.4.0;本文适合初学者阅读。

1 单向链表及其实现

  • 在文章《使用GLib进行C语言编程的实例》中,简单介绍了 GLib,建议阅读本文前先阅读这篇文章;

  • 单向链表是一种基础的数据结构,‌它由一系列节点组成,‌每个节点都包含数据部分和指向下一个节点的指针;

  • 这种链表的特点是数据只能在一个方向上流动,‌即从头节点开始,‌通过每个节点的指针依次访问后续节点,‌直到链表的末尾;

  • 在单向链表中,‌头节点是链表的起始点,‌它存储了链表的第一个数据元素以及指向下一个节点的指针;

  • 随后的每个节点都存储了自己的数据和一个指向下一个节点的指针,‌这样形成了链表的结构;

  • 链表的最后一个节点,‌也就是尾节点,‌它的指向下一个节点的指针指向 NULL,‌表示链表的结束。‌

  • 单向链表的主要操作包括:‌

    1. 插入节点:‌可以在链表的头部、‌尾部或中间某个位置插入新的节点。‌
    2. 删除节点:‌可以删除链表中的任意节点,‌并通过调整指针来保持链表的完整性。‌
    3. 遍历链表:‌通过从头节点开始,‌依次访问每个节点,‌直到到达链表的末尾。‌
    4. 查找节点:‌根据特定的条件或值,‌在链表中查找节点。‌
  • 单向链表的优势在于其动态的内存分配和高效的插入与删除操作,‌特别是在链表中间或头部插入和删除节点时,‌只需调整指针,‌无需移动其他数据;

  • 然而,‌单向链表的访问效率较低,‌因为访问特定位置的元素需要从头节点开始遍历。‌

  • 总的来说,‌单向链表是一种简单而灵活的数据结构,‌适用于需要频繁插入和删除操作,‌但访问操作相对较少的场景。‌

  • 下面程序是一个简单的单向链表的 C 语言标准库实现,sllist-c.c(点击文件名下载源程序)

  • 编译:gcc -Wall -g sllist-c.c -o sllist-c

  • 运行:./sllist-c

  • 该程序实现了单向链表的插入、删除、遍历和查找;

  • 该程序首先建立一个单向链表,并在链表中加入 4 个节点,数据分别为:1、2、3、5,然后显示整个链表;

  • 在第 2 个节点(数据为 3,索引号为 2)的后面插入节点,数据为 4,然后显示整个链表;

  • 将第 2 个节点(数据为 3,索引号为 2)删除,然后显示整个链表;

  • 最后释放整个链表;

  • 运行截图:

    screenshot of sllist-c

2 GLib 中单向链表结构 GSList

  • GLib API version 2.0 手册 (点击查看手册)
  • GLib API 手册中 GSList 部分 (点击查看手册)
  • 在 GLib 中,‌单向链表是通过GSList结构体实现的。‌GSList是一个简单的单向链表结构,‌用于存储各种类型的数据;
  • GSList 定义如下:
    struct GSList {
        gpointer data;
        GSList *next;
    }
    
  • data 为单向链表的数据指针,可以指向任何类型或结构的数据;
  • next 为指向该单向链表下一个节点的指针;
  • GLib 为单向链表结构 GSList 的操作提供了大量的函数,本文仅就其中的一部分函数进行介绍;
  1. 添加、插入新节点
    • g_slist_append() 在单向链表的最后添加一个新节点;
      GSList *g_slist_append(GSList *list, gpointer data)
      
      • list - 指向单向链表的指针
      • data - 指向添加节点的数据
      • 返回指向单向链表的起始指针;
    • g_slist_prepend() 在单向链表的最前面添加一个新节点;
      GSList *g_slist_prepend(GSList *list, gpointer data)
      
      • list - 指向单向链表的指针
      • data - 指向添加节点的数据
      • 返回指向单向链表的指针,在单向链表的开头添加一个节点,单向链表的指针是肯定会变化的;
      • 返回该单向链表的起始指针;
    • g_slist_insert() 在单向链表的中间插入一个新节点;
      GSList *g_slist_insert(GSList *list, gpointer data, gint position)
      
      • list - 指向单向链表的指针
      • data - 指向添加节点的数据
      • position - 插入节点的位置,如果是负数或者超过了该单向链表的节点的数量,新节点将插到单向链表的最后;
      • 返回该单向链表的起始指针;
    • g_slist_insert_before() 在包含指定数据的节点之前插入一个新节点;
      GSList *g_slist_insert_before(GSList *slist, GSList *sibling, gpointer data)
      
      • slist - 指向单向链表的指针
      • data - 指向添加节点的数据
      • sibling - 指向一个节点的指针,将在这个节点前插入新节点
      • 返回该单向链表的起始指针;
  2. 删除节点
    • g_slist_remove_link() 从单向链表中删除一个节点,但并不释放该节点占用的内存
      GSList *g_slist_remove_link(GSList *list, GSList *link_)
      
      • list - 指向单向链表的指针;
      • link_ - 指向单向链表中一个节点的指针,该节点将被删除;
      • 返回该单向链表的起始指针;
      • 该函数并不释放被删除的节点内存,被删除的节点的 next 指针将指向 NULL,所以可以认为被删除的节点变成了一个只有一个节点的新的单向链表;
    • g_slist_delete_link() 从单向链表中删除一个节点,并释放该节点占用的内存;
      GSList *g_slist_delete_link(GSList *list, GSList *link_)
      
      • list - 指向单向链表的指针;
      • link_ - 指向单向链表中一个节点的指针,该节点将被删除;
      • 返回该单向链表的起始指针;
      • 该函数与 g_slist_remove_link() 的唯一区别是该函数在删除节点后释放了被删除节点占用的内存;
    • g_slist_remove() 从单向链表中删除指定数据的一个节点,如果链表中有指定数据的节点有多个,将只删除第一个;
      GSList *g_slist_remove(GSList *list, gconstpointer data)
      
      • list - 指向单向链表的指针
      • data - 指向要删除节点的数据
      • 返回该单向链表的起始指针;
    • g_slist_remove_all() 从单向链表中删除指定数据的所有节点;
      GSList *g_slist_remove_all(GSList *list, gconstpointer data)
      
      • list - 指向单向链表的指针
      • data - 指向要删除节点的数据
      • 返回该单向链表的起始指针;
  3. 遍历链表
    • g_slist_foreach() 遍历单向链表,每个节点都会调用一个指定函数;
      void g_slist_foreach(GSList *list, GFunc func, gpointer user_data)
      
      • list - 指向单向链表的指针
      • func - 一个指向函数的指针,遍历到单向链表的每个节点时,都会调用这个函数;
      • GFunc 的定义如下:
      void (* GFunc) (gpointer data, gpointer user_data)
      
      • GFunc 的定义表明,传递给 func 的参数有两个,一个是 data - 指向当前节点的节点数据指针,另一个就是指向自定义参数 user_data 的指针
      • user_data - 指针指向调用 func 时传递的用户参数;
  4. 查找节点
    • g_slist_find() 查找链表中包含给定数据的节点;
      GSList *g_slist_find(GSList *list, gconstpointer data)
      
      • list - 指向单向链表的指针
      • data - 指向要查找节点的数据
      • 返回在单向链表中找到的节点的指针,如果没有找到相应节点,返回 NULL;
    • g_slist_index() 获取包含给定数据的节点的位置(从 0 开始);
      gint g_slist_index(GSList *list, gconstpointer data)
      
      • list - 指向单向链表的指针;
      • data - 指向要查找节点的数据;
      • 返回数据为 data 的节点在单向链表中的位置(从 0 开始),如果没找到相应节点,则返回 -1;
    • g_slist_position() 获取给定节点在链表中的位置(从 0 开始);
      gint g_slist_position(GSList *list, GSList *llink)
      
      • list - 指向单向链表的指针;
      • llink - 指向单向链表中的一个节点的指针;
      • 返回 llink 指向的节点在单向链表中的位置(从 0 开始),如果没找到相应节点,则返回 -1;
  5. 释放链表
    • g_slist_free() 释放链表使用的所有内存,该函数不会释放节点中动态分配的内存;
      void g_slist_free(GSList *list)
      
      • list - 指向单向链表的指针;
      • 该函数仅释放 GSList 占用的内存,并不释放单向链表中各个节点动态申请的内存,如果链表中有动态申请内存,考虑使用 g_slist_free_full() 或手动释放内存;
    • g_slist_free_full() 释放链表使用的所有内存,并对每个节点的数据调用指定的销毁函数
      void g_slist_free_full(GSList *list, GDestroyNotify free_func)
      
      • list - 指向单向链表的指针;
      • free_func - 销毁函数,对单向链表中的每个节点数据将调用该函数,可用于释放节点中动态分配的内存;
      • GDestroyNotify 的定义如下:
      void (* GDestroyNotify) (gpointer data)
      
      • 所以在调用 free_func 时会将指向节点数据的指针传递给该函数;
  6. 其它
    • g_slist_length() 获取单向链表的长度;
      guint g_slist_length(GSList *list)
      
      • list - 指向单向链表的指针;
      • 返回单向链表中节点的数量。
    • g_slist_last() 获取单向链表的最后一个节点;
      GSList *g_slist_last(GSList *list)
      
      • list - 指向单向链表的指针;
      • 返回单向链表的最后一个节点的指针,如果单向链表没有节点,则返回 NULL;
    • g_slist_concat() 连接两个单向链表;
      GSList *g_slist_concat(GSList *list1, GSList *list2)
      
      • list1 - 指向第 1 个单向链表的指针;
      • list2 - 指向准备连接到第 1 个单向链表后面的单向链表的指针;
      • 返回连接好的单向链表的指针,

3 如何使用 GSList 实现单向链表

  • 文章的一开始有一个使用标准 C 语言函数库的单向链表的实例,使用 GLib 的 GSList 操作单向链表要容易得多;

  • 下面程序是使用 C 语言,基于 GLib 实现的单向链表,sllist-glib.c(点击文件名下载源程序)

  • 该程序实现的功能与文章开头的程序 sllist-c.c 完全一样,但程序看上去要简洁很多,我们不妨把源程序列在这里

    #include <stdio.h>
    #include <glib.h>
    
    void print_node(gpointer data, gpointer user_data) {
        printf("%d -> ", GPOINTER_TO_INT(data));
    }
    
    void print_list(GSList *list) {
        g_slist_foreach(list, &print_node, NULL);
        printf("NULL\n");
    }
    
    int main() {
        GSList *list = NULL;
    
        printf("Append 4 nodes, the data are 1, 2, 3, 5.\n");
        list = g_slist_append(list, GINT_TO_POINTER(1));
        list = g_slist_append(list, GINT_TO_POINTER(2));
        list = g_slist_append(list, GINT_TO_POINTER(3));
        list = g_slist_append(list, GINT_TO_POINTER(5));
        print_list(list);
    
        printf("Insert a new node after node with the data 3.\n");
        list = g_slist_insert(list, GINT_TO_POINTER(4), 3);
        print_list(list);
    
        printf("Remove node with the data 3.\n");
        list = g_slist_remove(list, GINT_TO_POINTER(3));
        print_list(list);
    
        // Free the list
        g_slist_free(list);
    
        return 0;
    }
    
  • 编译:

    gcc -Wall -g sllist-glib.c -o sllist-glib `pkg-config --cflags --libs glib-2.0`
    
  • 其中,pkg-config --cflags --libs glib-2.0 的含义在文章《使用GLib进行C语言编程的实例》中做过介绍;

  • 运行:./sllist-glib

  • 该程序实现了单向链表的插入、删除、遍历和查找;

  • print_list() 中使用 g_slist_foreach() 对链表进行遍历,对链表中的每个节点数据,将调用函数 print_node()

  • 运行截图:

    screenshot of sllist-glib

4 单向链表的应用场景

  • 单向链表是一种基础的数据结构,具有节点之间按顺序相连的特性,在特定场景下非常有用,以下是一些典型的应用场景:

    1. 动态数据集

      当数据量不确定且频繁增删时,单向链表比数组更适用,它可以方便地在任意位置插入或删除节点,而不需要像数组那样移动大量元素;

    2. 队列和栈的实现

      单向链表常用于实现队列(FIFO)和栈(LIFO),因为它支持高效的插入和删除操作,尤其在头部或尾部进行操作时性能更好;

    3. 浏览历史记录或撤销操作

      在一些应用程序中,如浏览器的历史记录,单向链表可以用来保存用户的浏览路径或操作步骤,方便逐步返回或撤销;

    4. 分配器管理内存块

      操作系统的内存管理器中,单向链表经常被用于管理空闲的内存块(free lists),通过链表可以快速地找到可用的内存块;

  • 由于单向链表的简单结构,它在上述场景下既灵活又高效,特别是当增删操作频繁时。

5 基于 GLib 的 GSList 实现的 FIFO 队列

  • FIFO(First Input First Output)队列,也就是先进先出队列,是一种简单的机制,操作一个 FIFO 队列需要队列的头指针和尾指针;

  • 当向 FIFO 队列中加入数据时,数据添加到队列的尾指针处,当从队列中取出数据时,要从队列的头指针处取;

  • FIFO 队列的重要参数是队列的最大长度,当队列中数据的数量达到队列的最大长度时,则不能再向队列中添加数据;

  • FIFO 队列的两个重要判断就是判断队列为空(队列中没有数据)或者队列已满(数据数量达到最大长度);

  • 源程序 queue-glib.c(点击文件名下载源程序) 基于 GLib 的 GSList 实现了一个简单的 FIFO 队列;

  • 该程序实现了 FIFO 队列的两个基本操作:入队操作和出队操作,基于 GLib 使得程序相当的简单;

    #include <stdio.h>
    #include <glib.h>
    
    #define QUEUE_MAX_LEN           10
    GSList *queue_head, *queue_tail;        // head and tail pointers of the queue
    guint32 queue_max_len;                  // Max. length of the queue
    
    void queue_init(int maxn) {
        queue_head = queue_tail = NULL;
        queue_max_len = maxn;
    }
    
    gboolean queue_put(gpointer data) {
        guint queue_len = g_slist_length(queue_head);           // length of the queue
        if (queue_len >= queue_max_len) {
            // the queue is full
            return FALSE;
        } else {
            queue_head = g_slist_append(queue_head, data);      // append a node with data to the queue
            queue_tail = g_slist_last(queue_head);              // get the pointer of last node
        }
        return TRUE;
    }
    
    gpointer queue_get() {
        guint queue_len = g_slist_length(queue_head);           // length of the queue
        if (queue_len == 0) {
            // the queue is empty
            return NULL;
        }
        gpointer queue_data = queue_tail->data;                 // data pointer of the last node
        queue_head = g_slist_delete_link(queue_head, queue_tail);   // delete the last node
        if (queue_head == NULL) {
            queue_tail = queue_head;                            // the queue is empty
        } else {
            queue_tail = g_slist_last(queue_head);              // get the pointer of last node
        }
        return queue_data;
    }
    
    int main(int argc, char **argv) {
        guint64 len;
        if (argc >= 2) {
            len = g_ascii_strtoll(argv[1], NULL, 10);           // Convert string to int
            if (len <= 0 || len > (QUEUE_MAX_LEN * 10)) {
                len = QUEUE_MAX_LEN;
            }
        } else {
            len = QUEUE_MAX_LEN;
        }
    
        printf("Max. length of the queue is %ld.\n", len);
        queue_init(len);            // Initialize the queue
    
        guint16 i;
        // append some data to the queue
        for (i = 0; i < (queue_max_len << 1); ++i) {
            if (queue_put(GINT_TO_POINTER(i + 1))) {
                printf("Put data %d into the queue.\n", i + 1);
            } else {
                printf("The queue is full.\n");
                break;
            }
        }
        // get some data from the queue
        for (i = 0; i < (queue_max_len << 1); ++i) {
            gpointer queue_data = queue_get();
            if (queue_data != NULL) {
                printf("Get data %d from the queue.\n", GPOINTER_TO_INT(queue_data));
            } else {
                printf("The queue is empty.\n");
                break;
            }
        }
    
        return 0;
    }
    
  • 可以看出,用 GLib 实现的 FIFO 队列非常简洁;

  • 编译:

    gcc -Wall -g queue-glib.c -o queue-glib `pkg-config --cflags --libs glib-2.0`
    
  • 其中,pkg-config --cflags --libs glib-2.0 的含义在文章《使用GLib进行C语言编程的实例》中做过介绍;

  • 运行:./queue-glib 8

  • 运行截图:

    screenshot of queue-glib

  • 该程序并不完整,如果实际运用,至少要加一个互斥锁,以保证 FIFO 队列的线程安全;

  • 使用 GLib 的 GSList 实现的 FIFO 队列,其中的数据并不需要是相同的数据类型,因为队列中存储的数据的指针,这一点在某些应用场景下会带来一些方便,但也会增加开销,而且在数据使用完成后有可能需要释放额外申请的内存空间。


email: hengch@163.com

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

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

相关文章

基于SpringBoot大学生就业管理系统设计与实现

1.1 研究背景 科学技术日新月异的如今&#xff0c;计算机在生活各个领域都占有重要的作用&#xff0c;尤其在信息管理方面&#xff0c;在这样的大背景下&#xff0c;学习计算机知识不仅仅是为了掌握一种技能&#xff0c;更重要的是能够让它真正地使用到实践中去&#xff0c;以…

马丁格尔EA交易策略,昂首平台优化操作指南来了

在金融市场中&#xff0c;选择合适的交易工具和策略至关重要。在昂首平台&#xff0c;我们为您提供了多种高效交易策略&#xff0c;其中马丁格尔EA便是备受推崇的选择。下面我们将为您详细介绍如何在非活动阶段优化马丁格尔EA的操作&#xff0c;确保交易的安全与盈利。 1. 非活…

一、Spring Boot集成Spring Security之自动装配

Spring Boot集成Spring Security之自动装配介绍 一、实现功能及软件版本说明二、创建Spring Boot项目三、查看自动装配配置类四、自动装配配置类之SecurityAutoConfiguration1、SecurityAutoConfiguration部分源码2、主要作用3、SpringBootWebSecurityConfiguration3.1、Spring…

Prometheus监控k8s环境构建

传统架构中比较流行的监控工具有 Zabbix、Nagios 等&#xff0c;这些监控工具对于 Kubernetes 这类云平台的监控不是很友好&#xff0c;特别是当 Kubernetes 集群中有了成千上万的容器后更是如此&#xff0c;本章节学习下一代的云原生监控平台---Prometheus。 一、基于kuberne…

【从零开始实现stm32无刷电机FOC】【实践】【7.2/7 完整代码编写】

目录 stm32cubemx配置芯片选择工程配置stm32基础配置SPI的配置定时器的配置ADC的配置中断优先级的配置生成工程 工程代码编写FOC代码结构搭建电机编码器角度读取PWM产生FOC开环代码编写确定电机正负旋转方向电机旋转速度计算多圈逻辑角度电流采样极对数转子角度确定 闭环控制控…

The Open Group 2024生态系统架构·可持续发展年度大会全面解读

在全球数字化转型加速的时代背景下&#xff0c;人工智能技术正以前所未有的速度重塑各行各业的生态系统。尤其是随着ChatGPT、Sora等技术的爆发&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;技术在多个领域展现出超越人类的能力&#xff0c;AGI&#xff08;通用人…

Stable Diffusion绘画 | SDXL模型使用注意事项

注意事项 SDXL模型的使用&#xff0c;对电脑配置要求更高&#xff0c;需要 8GB 以上显存的显卡SDXL模型兼容性不太好&#xff0c;容易出现错误&#xff0c;对 Mac 电脑不友好只能选择 SDXL模型 训练的 LoRA 使用不能使用旧的 VAE文件 SDXL 专用 VAE 文件&#xff1a;sdxl_vae.…

HTML流光爱心

文章目录 序号目录1HTML满屏跳动的爱心&#xff08;可写字&#xff09;2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心&#xff08;简易版&#xff09;7HTML粒子爱心8HTML蓝色动态爱心9HTML跳动的爱心&#xff08;双心版&#xff09;1…

类和对象(3)

博客ID&#xff1a;LanFuRenC系列专栏&#xff1a;C语言重点部分 C语言注意点 C基础 Linux 数据结构 C注意点 声明等级&#xff1a;黑色->蓝色->红色 欢迎新粉加入&#xff0c;会一直努力提供更优质的编程博客&#xff0c;希望大家三连支持一下啦 目录 1.拷贝构造 …

ACM第三次考核题解

ACM第三次考核题解 题目序号难度题目编号题目考察知识点1签到题A这是一道很难的题&#xff01;&#xff01;&#xff01;输出2迷之难度F神说要有光&#xff0c;于是有了手电筒贪心3简单BThis is a real English problem&#xff01;思维 英语4简单C玩具简单排序5简单I“近义词…

速通数据结构与算法第六站 树堆

系列文章目录 速通数据结构与算法系列 1 速通数据结构与算法第一站 复杂度 http://t.csdnimg.cn/sxEGF 2 速通数据结构与算法第二站 顺序表 http://t.csdnimg.cn/WVyDb 3 速通数据结构与算法第三站 单链表 http://t.csdnimg.cn/cDpcC 4 速通…

一文上手SpringSecurity【九】

在校验token的过滤器当中, 由于需要根据用户名称, 去查询出要认证的对象,然后再去数据库当中查询出角色列表、权限列表.频繁的操作数据库,可能会带来性能瓶颈, 那么我们该如何解决呢? 我们可以引入Redis, 将认证的对象,存储到Redis当中,在校验token的过滤器当中,可以直接从Red…

9.29 LeetCode 3304、3300、3301

思路&#xff1a; ⭐进行无限次操作&#xff0c;但是 k 的取值小于 500 &#xff0c;所以当 word 的长度大于 500 时就可以停止操作进行取值了 如果字符为 ‘z’ &#xff0c;单独处理使其变为 ‘a’ 得到得到操作后的新字符串&#xff0c;和原字符串拼接 class Solution { …

ServletContainerInitializer接口详解

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhlServletContainerInitializer概述 ServletContainerInitializer是Servlet 3.0规范中引入的一个接口,它的主要目的是允许开发者在Servlet容器(如Tomcat、Jetty等)启动时执行一些自定义的初始化代…

synchronized相关知识

1、对象头Markword 2、锁升级过程 无锁 偏向锁&#xff1a;只有一个线程过来加锁&#xff0c;Markword对应变化&#xff1a;偏向线程ID存储当前线程ID&#xff0c;偏向锁标志位置成1&#xff0c;锁标志位置为01&#xff1b;此后如果当前线程再次获取锁&#xff0c;只需对比偏…

《十年国庆游,洞察中国旅游新趋势》

作者&#xff1a;侯炯 一、十年国庆旅游数据总览 过去十年&#xff0c;中国国庆旅游市场呈现出丰富的变化和强劲的发展态势。从接待游客人次来看&#xff0c;2014 年接待国内游客 4.75 亿人次&#xff0c;到 2019 年已增长至 7.82 亿人次&#xff0c;2023 年国内旅游出游人数更…

【预备理论知识——1】深度学习:概率论概述

简单地说&#xff0c;机器学习就是做出预测。 概率论 掷骰子 假设我们掷骰子&#xff0c;想知道看到1的几率有多大&#xff0c;而不是看到另一个数字。 如果骰子是公平的&#xff0c;那么所有六个结果{1,…, 6}都有相同的可能发生&#xff0c; 因此我们可以说 1 发生的概率为1…

【数据结构】图的最小生成树

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《C游记》《进击的C》《Linux迷航》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、最小生成树的概念二、Kruskal算法2.1 思想2.2 实现 三、Prim算法3.1 思想3.2 实现 四、Kruskal和Prim的对比…

container_of 函数的分析

这个函数的目的是&#xff0c; 通过结构体里面的内容 找到 大结构体的 基地址。 函数的原型是&#xff1a;  &#xff30;&#xff34;&#xff32;是指针 &#xff54;&#xff59;&#xff50;&#xff45; &#xff0c; &#xff4d;&#xff45;&#xff4d;&#xff…

新手上路:Anaconda虚拟环境创建和配置以使用PyTorch和DGL

文章目录 前言步骤 1: 安装 Anaconda步骤 2: 创建新的 Anaconda 环境步骤 3: 安装最新版本的 PyTorch步骤 4: 安装特定版本的 PyTorch步骤 5: 安装最新版本的 DGL步骤 6: 安装特定版本的 DGL步骤 7: Pycharm中使用虚拟环境解释器第一种情况&#xff1a;创建新项目第二种情况&am…