C语言实现单链表(超多配图,这下不得不学会单链表了)
目录
一:什么是链表?
二:创建源文件和头文件
(1)头文件
(2)源文件
三:实参和形参
四:一步步实现单向链表
(1)建立一个头指针并置空
(2)打印链表,便于观察测试
(3)创建一个新的结点
(4)尾部插入数据
(5)头部插入
(6)尾部删除
(7)头部删除
(8)查找
(9)指定位置插入
(10)指定删除
(11)清空链表
(12)最终代码
SingleLinkedList.h
SingleLinkedList.c
text.c
五:小结
一:什么是链表?
我们先看下面这个结构体。
这个结构体存储数据的同时保存了一个结构体指针。
链表其实就是一个个结构体(后文把这样的一个结构体称为结点)通过保存地址的方式找到下一个结构体,最后一个结构体保存的地址为空。
链表的两种实现方式
(1)带头结点
(2)不带头结点
区别:带头结点有一个哨兵结点,这个节点作为第一个节点,它的数据域不存储数据。
两者各有利弊:我们进行结点删除时需要用到待删除结点的前一个结点。对于没有哨兵的单链表,当链表中只存在一个节点,需要进行单独处理。从而代码的复杂性增加。但如果设计了哨兵结点,则第一个结点的处理与其他结点一致。但处理链表数据时这个哨兵结点属于无效数据,我们需要规避这个数据,也需要进行处理。
本文选择的是无哨兵链表。
二:创建源文件和头文件
(1)头文件
头文件SingleLinkedList.h用来包含一些必要的头文件,声明函数以及定义结构体。
(2)源文件
源文件SingleLinkedList.c用来实现链表的具体功能。
源文件text.c用来对各个功能进行测试。
三:实参和形参
在实现链表之前,我们需要先深入的认识一下实参和形参的关系。
我们看下面这个代码:
我们可以看到a的值并没有发生变化,那我们如果传入a的地址进行解引用呢?我们看下面这个代码。
我们可以看到a成功被修改为了5,但这是为什么呢?
答:其实在传入参数的时候系统临时开辟了一块空间用来接收数据,函数调用结束时这一块空间就会被释放,这意味着如果我们直接传入a的值,我们只是在对这一块临时开辟的空间内的数据进行修改,无论如何都不会影响到a,但如果我们传入的是a的地址,对a进行解引用就能直接找到并修改a。
图解:
这是否意味着只要我们传入的是地址就一定能改变实参的值呢?我们看下面这个代码。
我们发现虽然传入的是地址,但p依旧指向a[0],并没有改变。这是为什么呢?
答:与前面的原理一致,我们传入p的时候也临时开辟了一片空间来保存p的值,无论我们怎么改变p值,在函数调用结束后这片空间会被释放,所以p实际上还是指向a[0]的。
图解:
那如果我们传入p的地址,是不是就能改变p了呢?我们看代码。
图解:
![]()
四:一步步实现单向链表
(1)建立一个头指针并置空
struct SListNode* head = NULL;
(2)打印链表,便于观察测试
我们用头指针的地址是否为空为循环条件。
我们可以分成两种情况讨论,如果链表为空,我们不进行遍历,直接打印NULL。
如果链表中有元素,从头指针(第一个结点)开始,我们打印结点数据,并让头指针指向下一个结点,一直到NULL。代码:
图解(以有三个结点为例子):
(3)创建一个新的结点
只要插入新结点,我们就一定要生成新的结点,我们可以把生成新结点的功能单独封装成函数BuyListNode()。
代码:
(4)尾部插入数据
进行数据插入,我们要改变实参的值(即改变指针的指向),必须传入头指针的地址(二级指针)。
基础思路:【1】在进行数据插入之前,我们要先生成一个新的结点。
【2】要进行尾部插入,我们需要找到链表的最后一个结点,并将它存储的指针指向新生成的结构体。
【3】我们设计一个指针tail来找尾部结点,如果tail->next为NULL,我们就找到了尾部结点,结束循环。
图解:
现阶段代码:
我们对代码进行测试:
可以发现程序崩溃了,这是为什么呢?
答:这是因为我们没有考虑链表为空的情况,如果链表为空,我们会直接对空指针进行解引用,导致程序崩溃。
为了解决这个问题,我们可以对这种情况进行单独处理。
代码:
再次测试,观察结果。
我们发现数据成功插入了。
(5)头部插入
进行数据插入,我们要改变实参的值(即改变指针的指向),必须传入头指针的地址(二级指针)。
思路:头部插入我们只需要让头指针指向新结点,让新结点的指针域指向原来的头结点。
代码:
前面进行尾部插入的时候需要考虑链表为空的情况,那头部插入需不需要单独进行这个临界条件的处理呢?
图解:
我们可以发现最后结点的指针域会指向空,所以不需要考虑这个临界情况。
(6)尾部删除
进行数据插入,我们要改变实参的值(即改变指针的指向),必须传入头指针的地址(二级指针)。
思路:和尾部插入一样我们需要使用一个tail指针找到尾部结点(方法与前面一致),然后释放这个结点。
我们看代码和运行结果:
我们发现程序打印的是随机数,这是为什么呢?
答:因为我们释放最后一个结点的时候上一个结点的指针域没有指空,但空间已经被系统回收了,此时我们进行指针的引用是非法的,也就是我们常说的野指针。
解决方案:我们可以设计一个指针prev来记录倒数第二个结点,在释放尾结点后让倒数第二个结点的指针域指向空。
但此时程序依然存在不足。
如果链表为空,我们就会对空指针进行解引用,所以我们需要单独处理这种情况,这里提供两种解决方案
第一,我们可以直接返回空。
第二,我们可以使用断言让程序直接报错。这里使用第一种方法。
代码和测试结果:
(7)头部删除
进行数据插入,我们要改变实参的值(即改变指针的指向),必须传入头指针的地址(二级指针)。
思路:进行头部删除,可以将第一个结点释放,然后让头指针指向第二个结点。
代码和运行结果:
(8)查找
查找有两种实现方式,一种返回结点地址,一种返回数据在第几个结点。
思路:遍历整个链表,一直到找到要查找的数据或最后一个结点为止。
如果没有查找到数据,返回NULL或者0。第一种(我用的):
第二种:
上述函数我们只能找到第一个数,后面相同的找不到,如果我们需要查找链表中所有该数的位置 ,我们可以设计一个pos指针并进行循环,循环结束条件为pos为空,这样就可以实现多次查找,我们看代码。(这个方法只适用于第一种)
(9)指定位置插入
这里给出两种插入方式,一种是指定在那个结点前插入,一种是指定在那个结点后插入
第一种前插入:
第一种后插入:
在进行插入前我们需要找到要插入的那个结点的前面(后面),可以先使用查找函数找到位置,在进行插入。
测试结果:
(10)指定删除
思路:删除的思路同尾删类似,我们需要找到待删除的结点并保存待删除的结点的指针域。
代码:
在进行删除前我们需要找到要删除的那个结点的前一个结点(目的是让前面的结点指针域指向下下个结点),可以先使用查找函数找到位置,再进行删除。
测试:
(11)清空链表
思路:从第一个结点开始,设计一个pos指针,每次循环把头指针指向的结点地址赋给pos,让头指针指向下一个结点地址,调用free()释放pos指向的结点。
图解(以三个结点为例子):
代码和测试结果:
(12)最终代码
SingleLinkedList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
//#include <assert.h> 要使用断言的话注意包含头文件//结构体数据类型重定义,方便我们更改要存储的元素类型
typedef int SLTDataType;struct SListNode
{SLTDataType data; //要存储的数据(数据域)struct SListNode* next; //用来存储下一个结构体的地址(指针域)
};//打印
void SListPrint(struct SListNode* phead);
//创建一个新节点
struct SListNode* BuyListNode(SLTDataType x);
//尾部插入
void SListPushBack(struct SListNode** pphead, SLTDataType x);
//头部插入
void SListPushFront(struct SListNode** pphead, SLTDataType x);
//尾部删除
void SListPopBack(struct SListNode** pphead);
//头部删除
void SListPopFront(struct SListNode** pphead);
//查找,返回对应结点地址
//int SListFind(struct SListNode* phead, SLTDataType x);
struct SListNode* SListFind(struct SListNode* phead, SLTDataType x);
//指定插入(还有一种按输入位置来插入)(在前面插入)
void SListInsertF(struct SListNode** pphead, struct SListNode* pos, SLTDataType x);
void SListInsertB(struct SListNode** pphead, struct SListNode* pos, SLTDataType x);//指定删除
void SListEarse(struct SListNode** pphead, struct SListNode* pos);
//销毁链表
void SListDestory(struct SListNode** pphead);
SingleLinkedList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SingleLinkedList.h"//打印链表
void SListPrint(struct SListNode* phead)
{//一直循环,直到找到最后一个结点while (phead){printf("%d-> ", phead->data); //依次打印结点存储的数据phead = phead->next; //让phead指向下一个结点}printf("NULL\n");
}//生成新节点
struct SListNode* BuyListNode(SLTDataType x)
{//调用maoolc()函数生成一个结点struct SListNode* newNode = (struct SListNode*)malloc(sizeof(struct SListNode));//如果申请失败,打印错误并结束程序if (newNode == NULL){printf("malloc error\n");exit(-1);}//将要插入的数据赋给新结点newNode->data = x;//新节点的next置空newNode->next = NULL;//返回生成的结点的地址return newNode;
}//尾部插入
void SListPushBack(struct SListNode** pphead, SLTDataType x)
{//生成一个新的结点struct SListNode* newnode = BuyListNode(x);//如果链表为空,直接把新结点地址赋给*ppheadif (*pphead == NULL){*pphead = newnode;}else{ //设置一个指针tail用来找到尾部结点struct SListNode* tail = *pphead;//不断循环,直到找到尾部结点while (tail->next){tail = tail->next;//指向下一个结点}//让原本置空的指针指向新生成的结点tail->next = newnode;}
}//头部插入
void SListPushFront(struct SListNode** pphead, SLTDataType x)
{//生成新结点struct SListNode* newnode = BuyListNode(x);//保存原来第一个结点的地址struct SListNode* prev = *pphead;//让头指向新结点*pphead = newnode;newnode->next = prev;
}//尾部删除
void SListPopBack(struct SListNode** pphead)
{//如果链表为空,就直接返回空,也可以使用assert(*pphead!=NULL)if (*pphead == NULL){ return;}//如果只有一个结点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//找尾部struct SListNode* tail = *pphead;//记录尾部的前一个结点的地址struct SListNode* prev = NULL;//找尾部结点,并保存尾部结点的前一个结点的地址while (tail->next){prev = tail;tail = tail->next;}//找到尾部结点,释放free(tail);//置空tail = NULL;//把尾部的前一个结点保存的地址置空prev->next = NULL;}
}//头部删除
void SListPopFront(struct SListNode** pphead)
{//如果链表为空,返回空,也可以使用assert(*pphead!=NULL)if (*pphead == NULL){return;}else{//找到下一个结点的地址struct SListNode* prev = (*pphead)->next;//释放第一个结点free(*pphead);//头指针指向第二个结点*pphead = prev;}
}//查找
struct SListNode* SListFind(struct SListNode* phead, SLTDataType x)
{struct SListNode* cur = phead;//循环查找while (cur){//找到返回该结点地址if (cur->data == x){return cur;}//没找到指向下一个结点else{cur = cur->next;}}//如果没找到,返回NULLreturn NULL;
}
//第二种
//int SListFind(struct SListNode* phead, SLTDataType x)
//{
// //记录第几个结点
// int i = 1;
// struct SListNode* cur = phead;
// //循环查找
// while (cur)
// {
// //找到返回该结点地址
// if (cur->data == x)
// {
// return i;
// }
// //没找到指向下一个结点,i加1
// else
// {
// i = i + 1;
// cur = cur->next;
// }
// }
// //如果没找到,返回0
// return 0;
//}//指定结点前插入
void SListInsertF(struct SListNode**pphead,struct SListNode* pos,SLTDataType x)
{//生成一个新的结点struct SListNode* newnode = BuyListNode(x);//只有一个结点或者链表为空,进行头插if (*pphead == pos){newnode->next = *pphead;*pphead= newnode;}else{//设计一个结构体指针来找pos的前一个结点struct SListNode* posprev = *pphead;while (posprev->next != pos){posprev = posprev->next;}posprev->next = newnode;newnode->next = pos; }
}
//指定结点后插入
void SListInsertB(struct SListNode**pphead,struct SListNode* pos,SLTDataType x)
{//生成一个新的结点struct SListNode* newnode = BuyListNode(x);//新结点指针域指向该结点的后一个newnode->next = pos->next;//结点的指针域指向新结点pos->next = newnode;
}//指定位置删除
void SListEarse(struct SListNode** pphead, struct SListNode* pos)
{//如果链表为空,返回空,也可以使用assert(*pphead!=NULL)if (*pphead == NULL){return;}//要删除的结点是第一个结点if (pos == *pphead){//找到下一个结点的地址struct SListNode* prev = (*pphead)->next;//释放第一个结点free(*pphead);//头指针指向第二个结点*pphead= prev;}else{//要找到pos结点的前一个结点位置struct SListNode* posprev = *pphead;while (posprev->next != pos){posprev = posprev->next;}//让posprev的指针域指向下下个结点posprev->next = pos->next;//释放结点pos的空间free(pos);pos= NULL;}
}//清空链表
void SListDestory(struct SListNode** pphead)
{struct SListNode* prev = *pphead;while ((*pphead)!= NULL){//找到头指针指向的结点prev = *pphead;//让头指针指向下一个结点*pphead = (*pphead)->next;//释放前面的结点free(prev);}
}
text.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SingleLinkedList.h"void text1()
{struct SListNode* head = NULL;SListPushFront(&head, 5);SListPushFront(&head, 50);SListPushFront(&head, 50);SListPushFront(&head, 5);struct SListNode* pos = SListFind(head, 50);int i = 1;//查找多个相同的值while (pos){printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);//pos指向目标结点的下一个结点pos = SListFind(pos->next, 50);}SListPrint(head);//修改pos = SListFind(head, 50);if (pos){pos->data = 30;}SListPrint(head);}void text2()
{struct SListNode* head = NULL;//插入SListPushBack(&head, 2);SListPushBack(&head, 5);SListPushBack(&head, 15);//查找14位置struct SListNode* pos = SListFind(head, 14);//判断是否有14if (pos == NULL){printf("没有该数据\n");}else{//删除14SListEarse(&head, pos);}SListPrint(head);//清空SListDestory(&head);//插入SListPushBack(&head, 2);SListPushBack(&head, 5);SListPrint(head);
}int main()
{/*text1();*/text2();return 0;
}
五:小结
相较于顺序表,链表能够更好的利用零散的空间,并且插入数据不需要大量移动数据,但是单链表在物理层上不是连续存储的,我们只能前找后却无法后找前,而且一旦指针域的数据丢失我们就没法找到后续结点,后续的双向链表可以很好的解决这个问题。
顺序表讲解链接:http://t.csdn.cn/V96aI
相关文章:

C语言实现单链表(超多配图,这下不得不学会单链表了)
目录 一:什么是链表? 二:创建源文件和头文件 (1)头文件 (2)源文件 三:实参和形参 四:一步步实现单向链表 (1)建立一个头指针并置空 (2)打印链表,便于…...

SQL编写优化技巧
一、底层原理 sql慢是因为没有走索引,因此需要添加索引然它走索引联合索引需要匹配最左匹配原则(索引回表)如果查询列超出索引的key, 会导致回表,回表数量多,则会走全表扫描 索引是分聚集索引、非聚集索引…...

【基础算法】单链表的OJ练习(6) # 复制带随机指针的链表 #
文章目录🍇前言🍎复制带随机指针的链表🍑写在最后🍇前言 本章的链表OJ练习,是最后的也是最难的。对于本题,我们不仅要学会解题的思路,还要能够通过这个思路正确的写出代码,也就是思路…...
Activity生命周期完成EvenetLog回调
Activity 生命周期 系统EvenetLog回调 EventLog路径: Android13/frameworks/base/core/java/android/app/EventLogTags.logtags wm_on_create_called wm_on_restart_called wm_on_start_called wm_on_resume_called wm_on_top_resumed_gained_called wm_on_top_resumed_lost_c…...

西安石油大学C语言期末真题实战
很简单的一道程序阅读题,pa’默认为a【0】,接下来会进行3次循环 0 1 2 输出结果即可 前3题就是一些基础定义,在此不多赘述 要注意不同的数据类型的字节数不同 a<<2 b>>1(b>>1;就是说b自身右位移一位(…...
【Shell】Shell变量
Shell变量系统预定义变量自定义变量基本语法定义变量撤销变量命名规则使用变量只读变量删除变量变量类型系统预定义变量 $HOME、$PWD、$SHELL、$SUSER等 实例 yysubuntu:~$ echo $HOME #查看系统变量的值 /home/yys yysubuntu:~$ set #显示当前shell中所有变量自定义变量…...

你是真的“C”——结构体中鲜有人知的“秘密”
你是真的“C”——结构体中的精髓剖析【内存对齐】 【位段】 😎前言🙌结构体内存对齐:😊结构体内存对齐存在的意思是什么?😘内存对齐例子详细剖析:😘结构体中的位段:&…...
2023年“网络安全”赛项江苏省淮安市赛题解析(超详细)
2023年中职组江苏省淮安市“网络空间安全”赛项 ①.2023年中职组江苏省淮安市任务书②.2023年中职组江苏省淮安市解析③.需要环境或者不懂的可以私信博主!①.2023年中职组江苏省淮安市任务书 任务一:服务器内部信息获取 任务环境说明: 服务器场景:Server210510(关闭链接…...

【二分查找】
二分查找704. 二分查找35. 搜索插入位置34. 在排序数组中查找元素的第一个和最后一个位置结语704. 二分查找 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在…...

Vue学习 -- 如何用Axios发送请求(get post)Promise对象 跨域请求问题
什么是Axios Vue本身是不支持发送axios请求,需要使用第三方插件,这里推荐使用Axios,Axios是基于promise的HTTP库;它会从浏览器中创建XMLHttpRequset对象。 安装Axios npm install axios -S下载后把axios.js文件复制进项目目录 …...

TVS和稳压管的相同点和不同点
大家好,我是记得诚。 文章目录 介绍相同点不同点介绍 TVS和稳压管都是电路中很常用的电子元器件,都是二极管的一个种类。 TVS二极管全称是Transient voltage suppression diode,也叫瞬态电压抑制二极管。 稳压二极管英文名字Zener diode,又叫齐纳二极管。 关于稳压二极…...

微信小程序项目实例——扫雷
今日推荐💁♂️ 2023许嵩演唱会即将到来🎤🎤🎤大家一起冲冲冲🏃♂️🏃♂️🏃♂️ 🔮🔮🔮🔮🔮往期优质项目实例🔮…...

2022-2023年度广东省职业院校学生专业技能大赛 中职组网络安全赛项竞赛规程
2022-2023年度广东省职业院校学生专业技能大赛 中职组网络安全赛项竞赛规程 一、赛项名称 赛项编号:Z27 赛项名称:网络安全赛项组别:中职 赛项归属:信息技术类 二、竞赛目的 为检验中职学校网络信息安全人才培养成效,促…...

超详细的堆排序,进来看看吧。
1.堆的基本概念1.1什么是堆堆是一种叫做完全二叉树的数据结构,1.2大堆和小堆大堆:每个节点的值都大于或者等于他的左右孩子节点的值小根堆:每个结点的值都小于或等于其左孩子和右孩子结点的值1.3完全二叉树节点之间的关系leftchild parent*2 1rightchild parent*…...

线性回归 特征扩展的原理与python代码的实现
文章目录1 多项式扩展的作用2 多项式扩展的函数2.1 接收参数2.2 多项式扩展示例3 多项式扩展的完整实例1 多项式扩展的作用 在线性回归中,多项式扩展是种比较常见的技术,可以通过增加特征的数量和多项式项的次数来提高模型的拟合能力。 举个例子&#…...
订阅关系一致
订阅关系一致指的是同一个消费者Group ID下所有Consumer实例所订阅的Topic、Tag必须完全一致。如果订阅关系不一致,消息消费的逻辑就会混乱,甚至导致消息丢失。本文提供订阅关系一致的正确示例代码以及订阅关系不一致的可能原因,帮助您顺畅地订阅消息。 背景信息 消息队列Ro…...

测试老鸟都在用的接口抓包常用工具以及接口测试工具都有哪些?
目录 接口 接口测试的重要性 常用抓包工具 常用接口测试工具 接口 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换,传递和控制管理过程,以及系统间…...

Delphi 一个函数实现腾讯云最新版(API3.0)短信发送
目录 一、腾讯云短信基本知识 1. 需要在腾讯云后台注册账号 2. 需要在腾讯云中开通短信功能 3. 腾讯云短信版本说明 4. 短信内容的组成 特定规范 二、短信发送函数 三、下载源代码(收费) 一、腾讯云短信基本知识 如今我们随时都收到短信验证码,注册码等等。这是…...

2023年Android现代开发
2023年现代Android开发 下面与大家分享如何构建具有2023年最新趋势的Android应用程序。 Android是什么? Android 是一种基于 Linux 内核并由 Google 开发的开源操作系统。它用于各种设备,包括智能手机、平板电脑、电视和智能手表。 目前,…...

自然语言处理(NLP)在医疗领域的应用
自然语言处理(Natural Language Processing,NLP)是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。在各个领域都有其应用。 其在生物医学领域迅速发展,已经…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能
指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...