双向链表及如何使用GLib的GList实现双向链表

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

1 双向链表及其实现

  • 在文章《单向链表以及如何使用GLib中的GSList实现单向链表》中,介绍了单向链表以及基于 GLib 实现单向链表的方法,建议阅读本文前先阅读这篇文章;

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

  • 双向链表(Doubly Linked List)是一种链式数据结构,每个节点包含三个主要部分:

    1. 数据部分:存储节点的数据
    2. 前向指针:指向链表中的下一个节点
    3. 后向指针:指向链表中的上一个节点
  • 可以看出,和单向链表相比较,双向链表多了一个指向前一个节点的指针

  • 双向链表的基本特性

    1. 双向性:与单向链表不同,双向链表允许从两个方向遍历,可以从头节点向尾节点遍历,也可以从尾节点向头节点遍历;
    2. 动态大小:双向链表的大小可以动态增长或缩小,不需要提前定义大小;
    3. 节点插入和删除:在双向链表中,插入和删除节点操作相对简单,因为每个节点都有指向前后节点的指针;
  • 双向链表的节点结构:

    struct Node {int data;               // 数据部分struct Node *next;      // 指向下一个节点的指针struct Node *prev;      // 指向前一个节点的指针
    };
    
  • 双向链表的基本操作

    1. 插入节点:可以在链表的开头、结尾或任意位置插入节点;
    2. 删除节点:可以删除链表中的任意节点,操作相对简单,因每个节点都知道其前一个和后一个节点;
    3. 遍历链表:可以从头到尾遍历链表(正向遍历)或从尾到头遍历链表(反向遍历);
  • 与单向链表相比,双向链表有以下特点:

    1. 由于数据结构中增加了后向指针,使链表可以双向遍历,而单向链表仅能单向遍历;
    2. 通过后向指针可以直接访问前一个节点,与单向链表相比,可以简化节点删除操作的复杂度;
    3. 在插入节点时,比单向链表更快捷更灵活;
    4. 与单向链表相比,由于增加了后向指针,内存开销增加;
    5. 与单向链表相比,双向链表需要操作两个指针,其操作和维护的复杂度要高一些;
  • 总的来说,‌双向链表比单向链表更加灵活,‌适用场景也要多一些。;

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

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

  • 运行:./dllist-c

  • 该程序实现了双向链表的插入、删除以及正向遍历;

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

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

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

  • 最后释放整个链表;

  • 运行截图:

    screenshot of dllist-c

2 GLib 中双向链表结构 GList

  • GLib API version 2.0 手册 (点击查看手册)
  • GLib API 手册中 GList 部分 (点击查看手册)
  • 在 GLib 中,‌双向链表是通过 GList 结构体实现的,GList 是一个简单的双向链表结构,‌用于存储各种类型的数据;
  • GSList 定义如下:
    struct GList {gpointer data;GList *next;GList *prev;
    }
    
  • data 为双向链表的数据指针,可以指向任何类型或结构的数据;
  • next 为指向该双向链表当前节点的下一个节点的指针;
  • prev 为指向该双向链表当前节点的前一个节点的指针;
  • GLib 为双向链表结构 GList 的操作提供了大量的函数,本文仅就其中的一部分函数进行简单介绍;
  1. 添加、插入新节点

    • g_list_append() 在双向链表的最后添加一个新节点;
      GList *g_slist_append(GList *list, gpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向添加节点的数据
      • 返回指向双向链表的起始指针;
      • 说明:在双向链表的最后添加节点,必须要遍历整个链表才能找到链表的尾部,这种做法效率很低,通常的做法是使用 g_list_prepend() 在链表的起始位置添加节点,当所有节点添加完毕后,再使用 g_list_reverse() 将整个链表反转;
    • g_list_prepend() 在双向链表的最前面添加一个新节点;
      GList *g_list_prepend(GList *list, gpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向添加节点的数据
      • 返回指向双向链表的指针,在双向链表的开头添加一个节点,双向链表的指针是肯定会变化的;
    • g_list_insert() 在双向链表的中间插入一个新节点;
      GList *g_list_insert(GList *list, gpointer data, gint position)
      
      • list - 指向双向链表的指针
      • data - 指向添加节点的数据
      • position - 插入节点的位置,如果是负数或者超过了该双向链表的节点的数量,新节点将插到双向链表的最后;
      • 返回该双向链表的起始指针;
    • g_list_insert_before() 在包含指定数据的节点之前插入一个新节点;
      GList *g_list_insert_before(GList *list, GSList *sibling, gpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向添加节点的数据
      • sibling - 指向一个节点的指针,将在这个节点前插入新节点
      • 返回该双向链表的起始指针;
  2. 删除节点

    • g_list_remove_link() 从双向链表中删除一个节点,但并不释放该节点占用的内存
      GList *g_list_remove_link(GList *list, GList *llink_)
      
      • list - 指向双向链表的指针;
      • llink_ - 指向双向链表中一个节点的指针,该节点将被删除;
      • 返回该双向链表的起始指针;
      • 该函数并不释放被删除的节点内存,被删除的节点的 next 和 prev 指针将指向 NULL,所以可以认为被删除的节点变成了一个只有一个节点的新的双向链表;
    • g_list_delete_link() 从双向链表中删除一个节点,并释放该节点占用的内存;
      GList *g_list_delete_link(GList *list, GList *link_)
      
      • list - 指向双向链表的指针;
      • link_ - 指向双向链表中一个节点的指针,该节点将被删除;
      • 返回该双向链表的起始指针;
      • 该函数与 g_list_remove_link() 的唯一区别是该函数在删除节点后释放了被删除节点占用的内存;
    • g_list_remove() 从双向链表中删除指定数据的一个节点,如果链表中有指定数据的节点有多个,将只删除第一个;
      GList *g_list_remove(GList *list, gconstpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向要删除节点的数据
      • 返回该双向链表的起始指针;
    • g_list_remove_all() 从双向链表中删除指定数据的所有节点;
      GList *g_list_remove_all(GList *list, gconstpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向要删除节点的数据
      • 返回该双向链表的起始指针;
  3. 遍历链表

    • g_list_foreach() 遍历双向链表,每个节点都会调用一个指定函数;
      void g_list_foreach(GList *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_list_find() 查找链表中包含给定数据的节点;
      GList *g_list_find(GList *list, gconstpointer data)
      
      • list - 指向双向链表的指针
      • data - 指向要查找节点的数据
      • 返回在双向链表中找到的节点的指针,如果没有找到相应节点,返回 NULL;
    • g_list_index() 获取包含给定数据的节点的位置(从 0 开始);
      gint g_list_index(GList *list, gconstpointer data)
      
      • list - 指向双向链表的指针;
      • data - 指向要查找节点的数据;
      • 返回数据为 data 的节点在双向链表中的位置(从 0 开始),如果没找到相应节点,则返回 -1;
    • g_list_position() 获取给定节点在链表中的位置(从 0 开始);
      gint g_list_position(GList *list, GList *llink)
      
      • list - 指向双向链表的指针;
      • llink - 指向双向链表中的一个节点的指针;
      • 返回 llink 指向的节点在双向链表中的位置(从 0 开始),如果没找到相应节点,则返回 -1;
  5. 释放链表

    • g_list_free() 释放链表使用的所有内存,该函数不会释放节点中动态分配的内存;
      void g_list_free(GList *list)
      
      • list - 指向双向链表的指针;
      • 该函数仅释放 GList 占用的内存,并不释放双向链表中各个节点动态申请的内存,如果链表中有动态申请内存,考虑使用 g_list_free_full() 或手动释放内存;
    • g_list_free_full() 释放链表使用的所有内存,并对每个节点的数据调用指定的销毁函数
      void g_list_free_full(GList *list, GDestroyNotify free_func)
      
      • list - 指向双向链表的指针;
      • free_func - 销毁函数,对双向链表中的每个节点数据将调用该函数,可用于释放节点中动态分配的内存;
      • GDestroyNotify 的定义如下:
      void (* GDestroyNotify) (gpointer data)
      
      • 所以在调用 free_func 时会将指向节点数据的指针传递给该函数;
  6. 其它

    • g_list_length() 获取双向链表的长度;
      guint g_list_length(GList *list)
      
      • list - 指向双向链表的指针;
      • 返回双向链表中节点的数量。
    • g_list_last() 获取双向链表的最后一个节点;
      GList *g_list_last(GList *list)
      
      • list - 指向单向链表的指针;
      • 返回双向链表的最后一个节点的指针,如果双向链表没有节点,则返回 NULL;
    • g_list_concat() 连接两个双向链表;
      GList *g_list_concat(GList *list1, GList *list2)
      
      • list1 - 指向第 1 个双向链表的指针;
      • list2 - 指向准备连接到第 1 个双向链表后面的双向链表的指针;
      • 返回连接好的双向链表的指针,
    • g_list_reverse() 反转整个双向链表
      GList *g_list_reverse(GList *list)
      
      • list - 指向双向链表的指针;
      • 返回该双向链表的起始指针;

3 如何使用 GList 实现双向链表

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

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

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

  • 该程序与文章《单向链表以及如何使用GLib中的GSList实现单向链表》中使用 GLib 实现单向链表的程序非常相似

    #include <stdio.h>
    #include <glib.h>void print_node(gpointer data, gpointer user_data) {printf("%d -> ", GPOINTER_TO_INT(data));
    }
    void print_list(GList *list) {g_list_foreach(list, &print_node, NULL);printf("NULL\n");
    }int main() {GList *list = NULL;printf("Append 4 nodes, the data are 1, 2, 3, 5.\n");list = g_list_append(list, GINT_TO_POINTER(1));list = g_list_append(list, GINT_TO_POINTER(2));list = g_list_append(list, GINT_TO_POINTER(3));list = g_list_append(list, GINT_TO_POINTER(5));print_list(list);printf("Insert a new node after node with the data 3.\n");list = g_list_insert(list, GINT_TO_POINTER(4), 3);print_list(list);printf("Remove node with the data 3.\n");list = g_list_remove(list, GINT_TO_POINTER(3));print_list(list);// Free the listg_list_free(list);return 0;
    }
    
  • 该程序中涉及到的两个宏:GINT_TO_POINTER(value)GPOINTER_TO_INT(p),在文章《单向链表以及如何使用GLib中的GSList实现单向链表》中有比较详细的介绍;

  • 编译:

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

  • 运行:./dllist-glib

  • 该程序实现了双向链表的插入、删除、遍历;

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

  • 运行截图:

    screenshot of dllist-glib

4 双向链表的应用场景

  • 双向链表是一种数据结构,它的每个节点包含对前一个节点和后一个节点的引用;这种结构在许多应用场景中非常有用,以下是一些常见的应用场景:
  1. 浏览器历史记录:

    双向链表可以用来实现浏览器的“后退”和“前进”按钮,用户可以在历史记录中前后移动当前指针;

  2. 音乐播放器:

    在音乐播放器中,双向链表可以用于管理播放列表,允许用户在歌曲之间前后切换;

  3. 文本编辑器:

    在实现撤销和重做功能时,双向链表可用于存储编辑历史,方便在不同操作间切换;

  4. LRU缓存:

    在实现最近最少使用(LRU)缓存时,双向链表可以高效地维护访问顺序,以便快速找到和删除最少使用的项;

  5. 操作系统中的进程调度:

    在某些调度算法中,双向链表可用于管理就绪队列,使得进程可以方便地添加和移除;

  6. 图形界面中的组件布局:

    在某些图形用户界面(GUI)框架中,双向链表用于管理组件的顺序和关系,使得组件之间的插入和删除变得灵活;

  7. 实现栈和队列:

    双向链表可以作为基础结构来实现栈和队列,提供灵活的插入和删除操作。

5 基于 GLib 的 GList 模拟终端命令的历史记录

  • 当我们在 Linux 终端上输入命令时,终端应用程序会记录你输入的命令并形成历史记录,可以使用 history 命令来查看这个历史记录;

  • 在终端上也可以使用上、下箭头键来翻看曾经输入过的前一个或者后一个历史命令,这个命令历史记录给使用终端带来了一定的便利;

  • 本实例模拟了终端输入命令并使用双向链表生成命令的历史记录,按上下箭头键可以查看上一条或下一条命令;

  • 源程序 cmd-history.c(点击文件名下载源程序) 基于 GLib 的 GList 模拟了终端历史记录;

  • 该程序首先建立了一个双向链表队列,然后模拟输入命令,链表中的每个节点存储一条命令,命令输入完成后显示最后一条命令,然后按上下箭头键可以从链表中取出上一条命令或者下一条命令并显示在屏幕上;

  • 很显然,使用单向链表实现命令历史记录是不方便的,但使用双向链表就很方便;

  • 编译:

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

  • 运行:./cmd-history

  • 运行截图:

    screenshot of cmd-history在这里插入图片描述

  • 该程序涉及到终端的操作,使用了结构 struct termios、函数 tcgetattr()tcsetattr(),这些并不在 C 标准库 libc 中,需要启用 GNU 扩展库,所以在程序的开始有 #define _GNU_SOURCE

  • 有关终端操作的相关数据结构、宏定义以及相关函数,并不在本文的讨论之内,请自行参考其它资料;

  • 该程序中还涉及到了使用 ESC 转义符对终端屏幕进行清屏操作,有关 ESC 转义符的含义,请参考另一篇文章《ANSI的ESC转义序列》

  • 该程序中还涉及到了从键盘缓冲区读取上、下箭头键的方法,上箭头键返回的编码为 ESC [ A,下箭头键返回的编码为 ESC [ B,这里说明一下有助于读者更快地读懂程序。


email: hengch@163.com

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 数据库之库操作

文章目录 1. 什么是数据库2. 基础概念2.1 连接数据库2.2 服务器&#xff0c;数据库&#xff0c;表关系2.3 SQL分类 3. 库的操作3.1 创建&#xff0c;选择&#xff0c;查看数据库3.2 字符集和默认校验规则 3.3 操纵数据库3.3.1 数据库查看3.3.2 数据库删除3.3.3 数据库修改 4. 其…

Windows安装多个NodeJS版本

下载nvm管理工具&#xff0c;下载完成解压安装 https://github.com/coreybutler/nvm-windows/releases 选择nvm安装位置 选择nvm安装node版本的安装位置 如果提示你已经安装的有nodejs&#xff0c;提示你是否通过nvm管理nodejs&#xff0c;选择是&#xff0c;继续安装即可…

使用NVM自由切换nodejs版本

一、NVM介绍 在日常开发中&#xff0c;我们可能需要同时进行多个不同NodeJS版本的项目开发&#xff0c;每个项目所依赖的nodejs版本可能不一致&#xff0c;我们如果只安装一个版本的nodejs&#xff0c;就可能出现node版本冲突问题&#xff0c;导致项目无法启动。这种情况下&am…

parseInt 是一个内置的 JavaScript 函数,用于将字符串转换为整数。

parseInt(options.checkNumber, 10) 中的 10 表示将字符串转换为十进制整数。 解释 parseInt 函数&#xff1a; parseInt 是一个内置的 JavaScript 函数&#xff0c;用于将字符串转换为整数。它有两个参数&#xff1a; 第一个参数是要转换的字符串。第二个参数是转换时使用的基…

Qt中的Model与View 4:QStandardItemModel与QTableView

目录 QStandardItemModel API QTableView 导航 视觉外观 坐标系统 API 样例&#xff1a;解析一个表格txt文件 QStandardItemModel QStandardItemModel 可用作标准 Qt 数据类型的存储库。它是模型/视图类之一&#xff0c;是 Qt 模型/视图框架的一部分。它提供了一种基于…

[Unity Demo]从零开始制作空洞骑士Hollow Knight第十九集:制作过场Cutscene系统

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、制作过场Cutscene系统 1.制作基本的视频过场和动画过场2.制作决定过场系统的播放顺序Sequence以及切换场景以后的逻辑处理二、制作跳过过场Cutscene的MenuS…

【设计模式系列】桥接模式(十三)

一、什么是桥接模式 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;其核心目的是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式主要用于处理那些在设计时无法确定实现细节的场合&#xff0c;或者需要在多个实现之间…

基于Multisim光控夜灯LED电路(含仿真和报告)

【全套资料.zip】光控夜灯LED电路设计Multisim仿真设计数字电子技术 文章目录 功能一、Multisim仿真源文件二、原理文档报告资料下载【Multisim仿真报告讲解视频.zip】 功能 光控夜灯LED电路 1.采用纯数字电路&#xff0c;非单片机。 2.通过检测周围光线&#xff0c;光线暗自…

html练习2

实现下列图片的效果 代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style>* {margin: 0;padding: 0;}#menu {background-color: #0c0048;width: 100%;height: 50px;margin: auto;…

【毫米波雷达(八)】车载毫米波前雷达遮挡检测功能

车载毫米波前雷达遮挡检测功能 一、概念二、功能指标1、遮挡检测功能2、功能流程3、实车验证 一、概念 随着汽车行业智能化发展&#xff0c;车载毫米波雷达在汽车市场应用越来越广泛。在驾驶过程中&#xff0c;当雷达受到泥土、纸巾、冰雪覆盖遮挡后&#xff0c;雷达检测性能受…

小新学习k8s第六天之pod详解

一、资源限制 Pod是k8s中的最小的资源管理组件&#xff0c;pod也是最小化运行容器化应用的资源对象。一个Pod代表着集群中运行的一个进程。k8s中其他大多数组件都是围绕着Pod来进行支撑和扩展Pod功能的&#xff0c;例如&#xff0c;用于管理Pod运行的StatefulSet和Deployment等…

java面试2.0

一.Zookeeper 1.定义 ZooKeeper 是一个开源的分布式协调服务&#xff0c;它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来&#xff0c;构成一个高效可靠的原语集&#xff0c;并以一系列简单易用的接口提供给用户使用。 ZooKeeper 为我们提供了高可用、高性能…

游戏测试|超越QA的常规:我们如何自动化回归测试

QA测试工作并不单调乏味&#xff0c;它是一项创造性的工作&#xff0c;蕴含着丰富的机会。公平地说&#xff0c;它也有枯燥乏味的一面--回归&#xff08;regression&#xff09;。因此&#xff0c;我们决定将回归测试自动化&#xff0c;具体方法如下。 ​ 在IT行业&#xff0c…

群分解(Swarm Decomposition,SWD)

代码原理 群体分解&#xff08;SWD&#xff09;是一种用于信号处理和数据分析的新兴方法。它通过将复杂的信号分解为多个群体成分&#xff08;Swarm Components&#xff09;&#xff0c;每个成分代表信号中的特定特征或模式。SWD的主要目标是提取信号中的不同特征模式&#xf…

flink实战-- flink任务的火焰图如何使用

火焰图 Flame Graphs 是一种有效的可视化工具,可以帮助我们排查如下问题: 目前哪些方法正在消耗 CPU 资源?一个方法的消耗与其他方法相比如何?哪一系列的堆栈调用导致了特定方法的执行?y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的…

CSS基础知识六(浮动的高度塌陷问题及解决方案)

目录 1.浮动高度塌陷概念 2.下面是几种解决高度塌陷的几种方案&#xff1a; 解决方案一&#xff1a; 解决方案二&#xff1a; 解决方案三&#xff1a; 1.浮动高度塌陷概念 在CSS中&#xff0c;高度塌陷问题指的是父元素没有正确地根据其内部的浮动元素或绝对定位元素来计…

拒绝事后背锅:测试项目中的风险管理一定要知道

在博主的公司中&#xff0c;测试经理除了要管理产品线的质量保障和日常部门事务工作外&#xff0c;另一项比较重要的就是测试项目全流程的管理。 今天不聊整体的测试项目流程如何开展&#xff0c;而是想聊一聊在同行中比较高频出现的一个字眼&#xff1a;风险管理。 什么是风…

基础算法——排序算法(冒泡排序,选择排序,堆排序,插入排序,希尔排序,归并排序,快速排序,计数排序,桶排序,基数排序,Java排序)

1.概述 比较排序算法 算法最好最坏平均空间稳定思想注意事项冒泡O(n)O( n 2 n^2 n2)O( n 2 n^2 n2)O(1)Y比较最好情况需要额外判断选择O( n 2 n^2 n2)O( n 2 n^2 n2)O( n 2 n^2 n2)O(1)N比较交换次数一般少于冒泡堆O( n l o g n nlogn nlogn)O( n l o g n nlogn nlogn)O( n l…

利用pythonstudio写的PDF、图片批量水印生成器,可同时为不同读者生成多组水印

现在很多场合需要将PDF或图片加水印&#xff0c;本程序利用pythonstudio编写。 第一步 界面 其中&#xff1a; LstMask:列表框 PopupMenu:PmnMark LstFiles:列表框 PopupMenu:PmnFiles OdFiles:文件选择器 Filter:PDF文件(.PDF)|.PDF|图像文件(.JPG)|.JPG|图像文件(.png…

如何区分实例化网格中的每个实例

1&#xff09;如何区分实例化网格中的每个实例 2&#xff09;项目在模拟器上切换程序后有概率画面冻结 3&#xff09;Unity工程导入团结引擎&#xff0c;GUID会变化&#xff0c;导致引用关系丢失 4&#xff09;Mask在Android平台下渲染异常 这是第407篇UWA技术知识分享的推送&a…