数据结构之双链表(C语言)


数据结构之双链表(C语言)

  • 1 链表的分类
  • 2 双向链表的结构
  • 3 双向链表的节点创建与初始化
    • 3.1 节点创建函数
    • 3.2 初始化函数
  • 4 双向链表插入节点与删除节点的前序分析
  • 5 双向链表尾插法与头插法
    • 5.1 尾插函数
    • 5.2 头插函数
  • 6 双向链表的尾删法与头删法
    • 6.1尾删函数
    • 6.2 头删函数
  • 7 双向链表在指定位置插入或删除数据
    • 7.1 查找指定节点函数
    • 7.2 在指定位置插入数据
    • 7.3 在指定位置删除数据
  • 8 销毁双向链表与打印函数
    • 8.1 销毁函数
    • 8.2 打印函数
  • 9 双向链表全程序及演示结果
    • 9.1 List.h文件
    • 9.2 List.c文件
    • 9.3 test.c文件

1 链表的分类

链表根据是否带头(哨兵位)、是否双向(单、双)、是否循环三种特性而分为8种不同的链表。上一节我们讲述了最常见的两种链表之一的单链表(不带头单向不循环链表),本节我们来介绍另一种类型:双链表(带头双向循环链表)。

2 双向链表的结构

双向链表也是由每个节点构成的,只不过相较于单链表,双向列表的每个节点由三部分组成。第一部分是该节点所指向的上一节点的指针(地址),第二部分是该节点所存储的数据,第三部分是该节点所指向的下一节点的指针(地址)。
在这里插入图片描述

代码定义如下:

typedef int LTDatatype;//对涉及链表内的int类型进行重命名typedef struct ListNode
{LTDatatype data;//该节点所存储的数据struct ListNode* prev;//指向前一个节点的指针struct ListNode* next;//指向后一个节点的指针
}LTNode;

3 双向链表的节点创建与初始化

3.1 节点创建函数

在单链表中,若单链表为空,我们便直接将其phead置为NULL。而在双向链表为空时,链表中只剩下一个头节点(哨兵位),在对其初始化时能否将prevnext指针都置为NULL

解决这一问题需要我们从双向链表的定义入手,双向链表是带头双向循环链表。在上一个问题中prevnext指针都为空,并不是一个循环。故此时的头节点并不能构成一个空的双向链表。正确操作是将头节点的prev指针与next指针都指向头节点自身,形成自循环。如此才是一个空的双向链表。在节点创建的其他方面与单链表的节点创建函数无异。
代码如下:

LTNode* LTBuyNode(LTDatatype x)
{LTNode* Newnode = (LTNode* )malloc(sizeof(LTNode));//申请一个节点,用一级指针Newnode存储该节点地址if (Newnode == NULL)//判断是否申请成功{perror("malloc fail");exit(1);}//申请成功Newnode->data = x;//将节点数据复制给dataNewnode->prev = Newnode->next = Newnode;//将prev与next指针都指向本节点地址,即Newnode所存储的地址return Newnode;
}

3.2 初始化函数

对一个空的双向链表进行初始化即对其头节点进行初始化。我们不妨将头节点命名为phead,内部数据data一般情况下均赋值为**-1**。在3.1中,我们之所以将节点创建函数的返回值类型设置为LTNode*,目的就是为了在初始化函数中直接调用它,再将它的返回值赋值给phead完成初始化。
代码如下:

void Init(LTNode** pphead)//注意这里使用的是二级指针来接受形参
{*pphead = LTBuyNode(-1);//对头节点进行初始化
}

在上面函数的形参中我们使用的是二级指针来接受传参,这种传参方式取决于测试文件test.c中的代码编写。具体见下:
test.c:

LTNode* plist = NULL;//定义一个空指针
Init(&plist);//初始化这个指针,使其成为双向空链表的头节点

我们若想避免在初始化函数中使用二级指针来接受传参,可以使用如下编写方式:
test.c:

LTNode* plist = Init();//将Init的返回值直接赋值给plist,在Init内即完成头节点phead的创建

List.c

LTNode* Init()
{LTNode* phead = LTBuyNode(-1);//创建头节点并完成初始化return phead;
}

4 双向链表插入节点与删除节点的前序分析

在单链表中我们是使用二级指针来来作为形参接收传参,这是因为单链表在为空链表是其头节点地址为NULL,传参后形参开辟空间所临时拷贝的值也是NULL,在栈区空间为NULL基础上进行插入与删除对原链表plist不会有影响,原链表plist仍然保持初始状态。
但是,在双向链表中由于空链表的情况下依然有一个头节点(哨兵位),其一级指针plist所存储的就不再是NULL,而是头节点(哨兵位)的地址。并且头节点(哨兵位)的地址不能发生改变,也就意味着后续的插入与删除都不能改变头节点(哨兵位)的位置。故我们对以下两种插入与删除函数形参部分的设置进行分析:
一、与单链表的插入保持一致,依然使用二级指针接收传参。
首先这种设置是肯定行得通的,但在删除节点时会存在将头节点(哨兵位)也删除的情况。若使用这种形参设置就要做好对头节点(哨兵位)的保护。
二、使用一级指针接收传参
之所以双向链表能使用一级指针接收传参,而单链表不能如此是因为双向链表为空时其头节点(哨兵位)有实际的地址,而单链表却为NULL。在头节点(哨兵位)有实际地址的情况下,将地址作为实参传给对应的插入与删除函数的形参(一级指针),之后无论是插入还是删除都是在这个实际地址之后进行操作故同样会对原链表头节点(哨兵位)之后的节点位置造成改变。相比之下由于单链表的头节点地址为NULL,便不会有上面的变化。此外,使用一级指针来作为形参的好处在于无论是插入还是删除都不会影响头节点(哨兵位)的位置。这是因为头节点(哨兵位)地址作为实参传到形参后,是由形参phead存储。此时一级指针plistphead均同时指向头节点(哨兵位)地址,不仅在phead存储的地址后进行插入与删除可以影响plist存储的实参后的内容,而且即使误将phead所存储的头节点(哨兵位)删除,还有plist指向头节点地址(虽然此时这块地址存储的内容已经为空),保证了头节点(哨兵位)位置的恒定,所以并没有违反双向空链表的规则。
综上所述,二者相较之下建议使用设置二作为形参设置。(后续的插入与删除操作以设置二为例)

5 双向链表尾插法与头插法

5.1 尾插函数

执行尾插操作时关键在于链接建立的顺序。首先我们要让新节点newnodeprevnext指针同原链表连接,然后改变原链表尾节点的位置,让尾节点为新插入的节点newnode
具体步骤一:
在双向链表中,找尾十分容易,头节点(哨兵位)的prev指针指向即为尾节点。所以让newnode->prev = phead->prev接完成了新节点与尾节点的连接,再让newnode->next = phead接完成了新节点与头节点phead的连接。循环设置的一半完成。
具体步骤二:
原本尾节点的next指针指向的是pheadnewnode尾插进来后要使其指向newnode让newnode成为新的尾节点,即phead->prev->next = newnode。然后再将头节点(哨兵位)的prev指针指向新的尾节点newnode,即phead->prev = newnode注意:步骤二中两步的先后顺序不能发生改变。
​如图所示:
在这里插入图片描述
代码如下:

void LTPushBack(LTNode* phead, LTDatatype x)
{//判断传参不为空assert(phead);LTNode* newnode = LTBuyNode(x);//步骤一newnode->prev = phead->prev;//newnode->prev指向尾节点newnode->next = phead;//newnode->next指向头节点//步骤二phead->prev->next = newnode;//原本尾节点指向新节点newnodephead->prev = newnode;//将新节点newnode变为新的尾节点//注意步骤二中两步的先后顺序不能发生改变
}

5.2 头插函数

5.1知,将新节点插入到头节点的前面即尾插(因为是循环链表)。故将新节点插入到头节点的后面即头插。头插法的关键与尾插一致,均为链接建立的顺序。首先我们要让新节点newnode与原链表的第二个节点phead->next建立联系,然后再将新节点newnode与头节点phead连接。
具体步骤一:
首先我们要找到第二个节点phead->next,然后让新节点newnodenext指针指向第二个节点phead->next,即newnode->next = phead->next再将第二个节点phead->nextprev指针指向新节点newnode,即phead->next->prev = newnode
具体步骤二:
原本pheadnext指针是指向第二个节点phead->next,新节点newnode插入进来后要让pheadnext指针指向newnode,即phead->next = newnode。然后再让newnodeprev指针指向phead,即newnode->prev = phead
代码如下:

void LTPushFront(LTNode* phead, LTDatatype x)
{//判断传参不为空assert(phead);LTNode* newnode = LTBuyNode(x);//步骤一newnode->next = phead->next;phead->next->prev = newnode;//步骤二newnode->prev = phead;phead->next = newnode;
}

6 双向链表的尾删法与头删法

6.1尾删函数

既然要删除链表中的节点,我们就要保证链表中不能只有一个头节点,即链表不能为空。此外,在删除的过程中,我们要确保双向链表的结构不受到破坏,也就是我们不能直接删除掉对应节点,否则该节点前面的节点与后面的节点就无法连接到一起,故应该先把要删除的节点保存下来,完成上述连接操作后再删除该节点。
代码如下:

void LTPopBack(LTNode* phead)
{//链表必须有效且不能为空(只有一个哨兵位)assert(phead && phead->next != phead);//将尾节点先保存下来LTNode* del = phead->prev;//步骤一:重新设置尾节点del->prev->next = phead;phead->prev = del->prev;//步骤二:删除原本尾节点(del)free(del);del = NULL;
}

6.2 头删函数

6.1中尾删函数步骤相同,先把要删除的节点保存下来,将头节点(哨兵位)与第三个节点连接起来,在将先前所保存的第二个节点删除。
代码如下:

void LTPopFront(LTNode* phead)
{//链表必须有效且不能为空(只有一个哨兵位)assert(phead && phead->next != phead);//将第二个节点先保存下来LTNode* del = phead->next;//步骤一:重新设置第二个节点del->next->prev = phead;phead->next = del->next;//步骤二:删除原本第二个节点(del)free(del);del = NULL;
}

7 双向链表在指定位置插入或删除数据

7.1 查找指定节点函数

要想在指定位置插入或删除数据就要先找到指定位置的节点。在双向链表中查找指定节点需要对链表进行遍历。代码如下:

LTNode* Find(LTNode* phead, LTDatatype x)
{//判断传参不为空,并且链表不为空(只有一个哨兵位)assert(phead && phead->next != phead);//从第一个有效节点开始查找,该节点用pcur存储LTNode* pcur = phead->next;while (pcur != phead)//当查找到头节点(哨兵位)时表示没找到,退出循环{if (pcur->data == x)//注意这里是判断语句,要用“==”而不是赋值的“=”{return pcur;//找到指定节点}pcur = pcur->next;//没找到指定节点,继续向后遍历}return NULL;//在该链表中不存在指定节点
}

7.2 在指定位置插入数据

与尾插和头插思路一致,先完成新节点newnode与插入位置之后的节点的连接,再完成newnode与插入位置之前的节点的连接。我们以在指定的pos节点之后插入数据为例:
代码如下:

void Insert(LTNode* pos, LTDatatype x)
{//判断传参不为空assert(pos);//创建要插入的节点LTNode* newnode = LTBuyNode(x);//步骤一:完成与插入位置后的节点的连接newnode->next = pos->next;pos->next->prev = newnode;//步骤二:完成与插入位置前的节点的连接newnode->prev = pos;pos->next = newnode;
}

7.3 在指定位置删除数据

首先使用查找函数找到pos节点,然后将pos节点的前后节点连接,再将pos节点删除即可。
代码如下:

void LTErase(LTNode* phead, LTNode* pos)
{//判断传参不为空,并且pos节点不是头节点(哨兵位)assert(pos && pos != phead);//步骤一:将pos节点前后的节点连接pos->prev->next = pos->next;pos->next->prev = pos->prev;//步骤二:删除pos节点free(pos);pos = NULL;
}

8 销毁双向链表与打印函数

8.1 销毁函数

与前文中删除节点类似,只不过销毁时需要借助循环来完成,并且在销毁过程中我们需要保存本次循环删除的节点的下一个节点,并且在后续循环中不断更新。
代码如下:

void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//此时pcur也指向phead,phead还没有被删除free(phead);phead = NULL;
}

其实无论是LTErase还是LTDestroy理论上都要传二级指针,因为我们需要让形参影响实参,但是我们为了接口的一致性都是用一级指针来接收传参。用一级来接收传参的弊端在于当我们在函数内将phead(或find)指向的地址的空间(存储内容)释放掉后,在函数内将形参phead(或find)置为NULL却并不能将函数外的实参plist(或Pos)也置为NULL。因此解决办法是:在调用完函数后手动将实参置为NULL

8.2 打印函数

总体与单链表的打印相同,只不过双向链表的打印需要我们跳过头节点(哨兵位)。

void LTPrint(LTNode* phead)
{assert(phead && phead->next != phead);LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}

9 双向链表全程序及演示结果

9.1 List.h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDatatype;typedef struct ListNode
{LTDatatype data;struct ListNode* prev;struct ListNode* next;
}LTNode;//节点创建函数
LTNode* LTBuyNode(LTDatatype x);//初始化函数01
//void Init(LTNode** pphead)//初始化函数02
LTNode* Init();//尾插函数
void LTPushBack(LTNode* phead, LTDatatype x);//头插函数
void LTPushFront(LTNode* phead, LTDatatype x);//尾删函数
void LTPopBack(LTNode* phead);//头删函数
void LTPopFront(LTNode* phead);//查找函数
LTNode* LTFind(LTNode* phead, LTDatatype x);//在pos位置之后插入
void LTInsert(LTNode* pos, LTDatatype x);//删除POS节点
void LTErase(LTNode* phead, LTNode* pos);//销毁链表
void LTDestroy(LTNode* phead);//打印函数
void LTPrint(LTNode* phead);

9.2 List.c文件

#include"List.h"//节点创建函数
LTNode* LTBuyNode(LTDatatype x)
{LTNode* Newnode = (LTNode* )malloc(sizeof(LTNode));//申请一个节点,用一级指针Newnode存储该节点地址if (Newnode == NULL)//判断是否申请成功{perror("malloc fail");exit(1);}//申请成功Newnode->data = x;//将节点数据复制给dataNewnode->prev = Newnode->next = Newnode;//将prev与next指针都指向本节点地址,即Newnode所存储的地址
}//初始化函数01
//void Init(LTNode** pphead)//注意这里使用的是二级指针来接受形参
//{
//	*pphead = LTBuyNode(-1);//对头节点进行初始化
//}//初始化函数02
LTNode* Init()
{LTNode* phead = LTBuyNode(-1);return phead;
}//尾插函数
void LTPushBack(LTNode* phead, LTDatatype x)
{//判断传参不为空assert(phead);LTNode* newnode = LTBuyNode(x);//步骤一newnode->prev = phead->prev;//newnode->prev指向尾节点newnode->next = phead;//newnode->next指向头节点//步骤二phead->prev->next = newnode;//原本尾节点指向新节点newnodephead->prev = newnode;//将新节点newnode变为新的尾节点//注意步骤二中两步的先后顺序不能发生改变
}//头插函数
void LTPushFront(LTNode* phead, LTDatatype x)
{//判断传参不为空assert(phead);LTNode* newnode = LTBuyNode(x);//步骤一newnode->next = phead->next;phead->next->prev = newnode;//步骤二newnode->prev = phead;phead->next = newnode;
}//尾删函数
void LTPopBack(LTNode* phead)
{//链表必须有效且不能为空(只有一个哨兵位)assert(phead && phead->next != phead);//将尾节点先保存下来LTNode* del = phead->prev;//步骤一:重新设置尾节点del->prev->next = phead;phead->prev = del->prev;//步骤二:删除原本尾节点(del)free(del);del = NULL;
}//头删函数
void LTPopFront(LTNode* phead)
{//链表必须有效且不能为空(只有一个哨兵位)assert(phead && phead->next != phead);//将第二个节点先保存下来LTNode* del = phead->next;//步骤一:重新设置第二个节点del->next->prev = phead;phead->next = del->next;//步骤二:删除原本第二个节点(del)free(del);del = NULL;
}//查找函数
LTNode* LTFind(LTNode* phead, LTDatatype x)
{//判断传参不为空,并且链表不为空(只有一个哨兵位)assert(phead && phead->next != phead);//从第一个有效节点开始查找,该节点用pcur存储LTNode* pcur = phead->next;while (pcur != phead)//当查找到头节点(哨兵位)时表示没找到,退出循环{if (pcur->data == x)//注意这里是判断语句,要用“==”而不是赋值的“=”{return pcur;//找到指定节点}pcur = pcur->next;//没找到指定节点,继续向后遍历}return NULL;//在该链表中不存在指定节点
}//在pos位置之后插入
void LTInsert(LTNode* pos, LTDatatype x)
{//判断传参不为空assert(pos);//创建要插入的节点LTNode* newnode = LTBuyNode(x);//步骤一:完成与插入位置后的节点的连接newnode->next = pos->next;pos->next->prev = newnode;//步骤二:完成与插入位置前的节点的连接newnode->prev = pos;pos->next = newnode;
}//删除POS节点
void LTErase(LTNode* phead, LTNode* pos)
{//判断传参不为空,并且pos节点不是头节点(哨兵位)assert(pos && pos != phead);//步骤一:将pos节点前后的节点连接pos->prev->next = pos->next;pos->next->prev = pos->prev;//步骤二:删除pos节点free(pos);pos = NULL;
}//销毁链表
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//此时pcur也指向phead,phead还没有被删除free(phead);phead = NULL;
}//打印函数
void LTPrint(LTNode* phead)
{assert(phead && phead->next != phead);LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}

9.3 test.c文件

运行结果图:
在这里插入图片描述

#include"List.h"
void Test01()
{/*LTNode* plist = NULL;Init(&plist);*///若想让初始化函数Init内不用二级指针来接受传参//可按如下方式编写LTNode* plist = Init();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPrint(plist);LTNode* find = LTFind(plist, 3);//LTInsert(find, 66);LTErase(plist,find);find = NULL;LTPrint(plist);LTDestroy(plist);
}int main()
{Test01();
}

全文至此结束!!!
写作不易,不知各位老板能否给个一键三连或是一个免费的赞呢(▽)(▽),这将是对我最大的肯定与支持!!!谢谢!!!(▽)(▽)

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

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

相关文章

Banana Pi BPI-RV2 RISC-V路由开发板采用矽昌通信SF2H8898芯片

Banana Pi BPI-RV2 开源网关是⼀款基于矽昌SF2H8898 SoC的设备&#xff0c;1 2.5 G WAN⽹络接⼝、5 个千兆LAN ⽹络接⼝、板载 512MB DDR3 内存 、128 MiB NAND、16 MiB NOR、M.2接⼝&#xff0c;MINI PCIE和USB 2.0接⼝等。 Banana Pi BPI-RV2 开源网关是矽昌和⾹蕉派开源社…

C语言:数据的存储

本文重点&#xff1a; 1. 数据类型详细介绍 2. 整形在内存中的存储&#xff1a;原码、反码、补码 3. 大小端字节序介绍及判断 4. 浮点型在内存中的存储解析 数据类型结构的介绍&#xff1a; 类型的基本归类&#xff1a; 整型家族 浮点家族 构造类型&#xff1a; 指针类型&…

从代码层面熟悉UniAD,开始学习了解端到端整体架构

0. 简介 最近端到端已经是越来越火了&#xff0c;以UniAD为代表的很多工作不断地在不断刷新端到端的指标&#xff0c;比如最近SparseDrive又重新刷新了所有任务的指标。在端到端火热起来之前&#xff0c;成熟的模块化自动驾驶系统被分解为不同的独立任务&#xff0c;例如感知、…

Go-Zero整合Goose实现MySQL数据库版本管理

推荐阅读 【系列好文】go-zero从入门到精通&#xff08;看了就会&#xff09; 教程地址&#xff1a;https://blog.csdn.net/u011019141/article/details/139619172 Go-Zero整合Goose实现MySQL数据库版本管理的教程 在开发中&#xff0c;数据库迁移和版本管理是必不可少的工作。…

day 27 日志文件(枚举,时间函数),目录io,多文件管理

0## 1.获得当前时间 # include <stdio.h> #include <stdlib.h> #include <time.h>int main() {struct tm* ptm;time_t sec time(NULL);ptm localtime(&sec);printf("%d-%d-%d %d:%d:%d\n",ptm->tm_year1900,ptm->tm_mon1,ptm->tm_…

使用Flink-JDBC将数据同步到Doris

在现代数据分析和处理环境中&#xff0c;数据同步是一个至关重要的环节。Apache Flink和Doris是两个强大的工具&#xff0c;分别用于实时数据处理和大规模并行处理&#xff08;MPP&#xff09;SQL数据库。本文将介绍如何使用Flink-JDBC连接器将数据同步到Doris。 一、背景介绍…

【python】OpenCV—Local Translation Warps

文章目录 1、功能描述2、原理分析3、代码实现4、效果展示5、完整代码6、参考 1、功能描述 利用液化效果实现瘦脸美颜 交互式的液化效果原理来自 Gustafsson A. Interactive image warping[D]. , 1993. 2、原理分析 上面描述很清晰了&#xff0c;鼠标初始在 C&#xff0c;也即…

灵活妙想学数学

灵活妙想学数学 题1&#xff1a;海星有几只&#xff1f; 一共有12只海洋生物&#xff0c;分别是5只脚的海星&#xff0c;8只脚的章鱼和10只脚的鱿鱼&#xff0c;这些海洋动物的脚一共有87只&#xff0c;每种生物至少有1只&#xff0c;问海星有几只&#xff1f; 解&#xff1a…

STM32-笔记40-BKP(备份寄存器)

一、什么是BKP&#xff08;备份寄存器&#xff09;&#xff1f; 备份寄存器是42个16位的寄存器&#xff0c;可用来存储84个字节的用户应用程序数据。他们处在备份域里&#xff0c;当VDD电源被切断&#xff0c;他们仍然由VBAT维持供电。当系统在待机模式下被唤醒&#xff0c;或…

Sprint Boot教程之五十八:动态启动/停止 Kafka 监听器

Spring Boot – 动态启动/停止 Kafka 监听器 当 Spring Boot 应用程序启动时&#xff0c;Kafka Listener 的默认行为是开始监听某个主题。但是&#xff0c;有些情况下我们不想在应用程序启动后立即启动它。 要动态启动或停止 Kafka Listener&#xff0c;我们需要三种主要方法…

编译pytorch——cuda-toolkit-nvcc

链接 https://blog.csdn.net/wjinjie/article/details/108997692https://docs.nvidia.com/cuda/cuda-installation-guide-linux/#switching-between-driver-module-flavorshttps://forums.developer.nvidia.com/t/can-not-load-nvidia-drivers-on-ubuntu-22-10/239750https://…

如何发布自己的第一个Chrome扩展程序

如何发布自己的Chrome扩展程序 只需要六步即可完成Chrome扩展程序的发布 &#xff08;1&#xff09;首先打开google chrome 应用商城注册开发者账号的页面 &#xff08;2&#xff09;现在进行一个绑卡支付5美元的一次性注册费用即可。【不知道如何绑卡的支付的&#xff0c;文…

SpringBoot入门实现简单增删改查

本例子的依赖 要实现的内容 通过get、post、put和delete接口,对数据库中的trade.categories表进行增删改查操作。 目录结构 com.test/ │ ├── controller/ │ ├── CateController.java │ ├── pojo/ │ ├── dto/ │ │ └── CategoryDto.java │ ├─…

electron 如何申请 Mac 系统权限

对于一些使用 Electron开发的app, 需要获取一些系统权限,比如录屏权限, 获取摄像头权限,麦克风等等,类似于以下界面: 那么Electron App 应该如何申请呢? 首先我们明确一下macOS中基础权限的分类,可以分为以下几种: 隐私权限(Private Permissions) : <!-- entitlements.ma…

浅谈云计算02 | 云计算模式的演进

云计算计算模式的演进 一、云计算计算模式的起源追溯1.2 个人计算机与桌面计算 二、云计算计算模式的发展阶段2.1 效用计算的出现2.2 客户机/服务器模式2.3 集群计算2.4 服务计算2.5 分布式计算2.6 网格计算 三、云计算计算模式的成熟与多元化3.1 主流云计算服务模式的确立3.1.…

An FPGA-based SoC System——RISC-V On PYNQ项目复现

本文参考&#xff1a; &#x1f449; 1️⃣ 原始工程 &#x1f449; 2️⃣ 原始工程复现教程 &#x1f449; 3️⃣ RISCV工具链安装教程 1.准备工作 &#x1f447;下面以LOCATION代表本地源存储库的安装目录&#xff0c;以home/xilinx代表在PYNQ-Z2开发板上的目录 ❗ 下载Vivad…

AI智能体实战|使用扣子Coze搭建AI智能体,看这一篇就够了(新手必读)

有朋友看到我使用Coze搭建的AI智能体蛮实用的&#xff0c;也想自己尝试一下。那今天我就分享一下如何使用Coze&#xff08;扣子&#xff09;搭建AI智能体&#xff0c;手把手教学&#xff0c;流程超级详细&#xff0c;学会了的话&#xff0c;欢迎分享转发&#xff01; 一、搭建A…

.NET8.0多线程编码结合异步编码示例

1、创建一个.NET8.0控制台项目来演示多线程的应用 2、快速创建一个线程 3、多次运行程序&#xff0c;可以得到输出结果 这就是多线程的特点 - 当多个线程并行执行时&#xff0c;它们的具体执行顺序是不确定的&#xff0c;除非我们使用同步机制&#xff08;如 lock、信号量等&am…

nginx 实现 正向代理、反向代理 、SSL(证书配置)、负载均衡 、虚拟域名 ,使用其他中间件监控

我们可以详细地配置 Nginx 来实现正向代理、反向代理、SSL、负载均衡和虚拟域名。同时&#xff0c;我会介绍如何使用一些中间件来监控 Nginx 的状态和性能。 1. 安装 Nginx 如果你还没有安装 Nginx&#xff0c;可以通过以下命令进行安装&#xff08;以 Ubuntu 为例&#xff0…

《数据思维》之数据可视化_读书笔记

文章目录 系列文章目录前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 数据之道&#xff0c;路漫漫其修远兮&#xff0c;吾将上下而求索。 一、数据可视化 最基础的数据可视化方法就是统计图。一个好的统计图应该满足四个标准&#xff1a;准确、有…