单链表复习 (C语言版)

目录

一.顺序表与链表的区别

二.链表概念

三.单链表

1.单链表的开始与初始化

2.单链表的打印

3.单链表的尾插

重难点:单链表实现时的指针详解

4.单链表的头插

5.单链表的尾删

6.单链表的头删

小结:

7.单链表的查找

8.在指定位置前插入数据

9.在指定位置后插入数据

10.删除pos结点

11.删除pos结点之后的结点

12.删除链表

13.可能存在的疑惑解答

 14.全部用于测试的代码

四.文章链接


 

一.顺序表与链表的区别

顺序表的问题及思考:

  1. 中间/头部插入删除,涉及到移动数据,时间复杂度为o(n)
  2. 增容需要申请新空间,拷贝数据,释放旧空间,会有损耗。(realloc)
  3. 增容一般呈2倍的增长,当前容量为100,增加到200,只存放5字节,浪费了95字节
  4. 顺序表中间头部插入效率低下,增容造成运行效率降低
  5. 链表解决了上述问题

顺序表相关文章链接:顺序表复习(C语言版)

f6f0317216824cfe8941ab0aff0724b3.jpg

 上图就形象地表示了线性表与链表的区别,链表存储位置不连续,是用指针相连;线性表(SeqList)在内存中是连续存放的

 

二.链表概念

线性表是一类相同元素的集合,例如苹果和香蕉(都是水果)

逻辑结构:一定线性

物理结构:不一定线性(因为在内存中的存放位置是不连续的)

int a =10;float f =0.1 变量a和f的物理空间不一定连续,即存放位置不一定连续

0c1f39d1dc964dc4b3c867789eca40af.jpg

链表和火车很相似

通过一个钩子连在一起,地址空间是链接在一起的,车头也是车厢,因为它也可以装人

旺季:增加车厢

淡季:减少车厢

链表是由一个一个节点组成,结点可以看成车厢

c048c7bf10a9408a860546cd6ab64c9a.jpg

 plist位置称之为头结点,头结点指向的结点叫做首元结点,最后一个指针域指向NULL的结点叫做尾元结点

结点和节点:同一个东西,随便用哪个

结点由什么组成的呢?

有两个组成部分

1.数据域  --->在该域内存储了数据

2.指针域  --->存储了指向下一个结点的指针

三.单链表

1.单链表的开始与初始化

链表就是在定义链表的结点结构

struct SListNode single list node{int data;                          //int a =10;//int* pa =&astruct SListNode* next;            //指向下一个节点的指针}SLTNode;typedef int SLTTDataType;

上述代码就是在对链表进行初始化,定义了一个结构体,并在里面定义了一个data还有一个指针next(next指向下一个结点)

void SListTest01(){//创建节点SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));node1->data = 1;SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));node2->data = 2;SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));node3->data = 3;SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));node4->data = 4;//将四个节点连接起来node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;//调用链表的打印SLTNode* plist = node1;void SLTPrint(plist); //为了让代码逻辑更加清晰}

上述代码依旧是对链表的初始化,将四个结点进行创建,然后相连接;在初始化代码写完以后,直接跟了一个链表打印函数

2.单链表的打印

920ec114d1554024aa1a8dbc538c0809.jpg

紧接上文代码,上文中 SLTNode* plist = node1就是在说明,plist是个指针,且与node1一样都是一级指针;因此它指向node1所在的这块空间,这就可以看成让node1和plist两个指针指向同一块空间,且这块空间在早些时候已经通过node1指针初始化完毕了;因此如果创建一个pcur,让他等于plist,那么pcur依旧指向node1所指向的空间,这就可以看出三个指针指向同一块空间

7fd278659e714d43aed2a9cc3fdf06e8.jpg

先是打印pcur所指向空间的data,然后让pcur里指向下一个结点的指针覆盖pcur这个结点,pcur就自然而然向后移动了

e99983d4b84f497582bb0098d17178bf.jpg

重复上述操作,先打印所指向空间的data值,然后再让其指向的下一个结点地址将现在这个结点地址覆盖

084955dd4e1d4191829f3f40f46e67b7.jpg

由上图可知,当pcur指向的空间为NULL时,打印操作完成;由上四图,不难得出下述代码:

void SLTPrint(SLTNode* phead) //传的是首元结点{SLTNode* pcur = phead;while (pcur)  //pcur != NULL{printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");}

 

3.单链表的尾插

因为在此以后我们需要多次进行单链表的增删查改操作,因此我们可以专门写一个函数,这个函数是专门用来创建新结点的,在顺序表那篇文章中已经讲解过,故在此省略

	SLTNode* SLTBuyNode(SLTDataType x){SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode*));if (newnode == NULL){perror("malloc fail!");exit(-1);}newnode->data = x;newnode->next = NULL; //此处指向空指针是为了方便后续操作}

接下来就要开始进行尾插操作

89f24ff48fd94fe1a9a10b62150ffa27.jpg

77fc5ec222b34612b9b24215ac2a42b7.jpg

	void SLTPushBack(SLTNode** pphead, SLTDataType x){SLTNode* newnode = SLTBuyNode(x);//空链表的情况if (*pphead == NULL){*pphead = newnode; //newnode的data有了,next指向空指针,将pphead地址覆盖,指向新的newnode空间,完成尾插}//非空链表的情况else{//找尾SLTNode* ptail = *pphead; //不想让*pphead变动位置(变动了就需要使用二级指针了),因此又定义了一个变量ptail指针while (ptail->next){ptail = ptail->next;}//退出循环就说明已经找到尾了,此时ptail指向空指针ptail->next = newnode; //让ptail指向新结点}
}

上述代码指针部分较难理解,因此笔者将在下文进行详细解释

重难点:单链表实现时的指针详解

上述代码中,为什么使用二级指针?
在函数中,想要通过形参改变实参的值(即对其进行某些操作,且这些操作是针对该参数而言的,而不是该参数针对别的变量而言的),就必须在传参时传地址。

即使实际参数是一个指针,我们也需要通过指针的地址来改变该指针本身。(如下图所示)

所以,我们在函数传参时,传的是指针的地址,因此需要函数用二级指针来接收。

但请注意,只有二级指针类型的形式参数,在进行了一次解引用操作状态下的改变,才需要用到二级指针类型的形式参数。

此处我们可以类比一级指针和整型变量。要通过形式参数改变实际参数,那么需要形式参数是一个指向需改变变量的指针,然后才可以通过形式参数改变该变量;而想要改变一级指针变量,那么就需要通过二级变量来改变。(前者是结点里的指针域、数据域的内容的改变,后者是结点本身的改变)

所以,如果需要改变结点,就用二级指针;如果只需要改变某个结点指针域、数据域内容,那就传一级指针,此时就没有必要传二级指针了。

上述代码中,为什么有些地方需要解引用,而有些地方不需要呢?

解引用(即*操作符)可以看作是把指针降级(文章链接部分有指针其他内容复习),把二级指针变成一级指针,把一级指针变成指针指向的内容;所以上述代码中,部分地方使用了一次解引用操作,即从指向结点的指针地址变为了指向结点的指针(也可以看成是结点本身)。

为什么单链表的打印不需要二级指针,而单链表的尾插就需要呢?

因为单链表的打印并没有改变链表的首元结点本身,只是完成了打印操作,所以不需要通过形式参数来改变实际参数;而尾插操作需要改变链表的首元结点本身(从NULL变成了newnode),因此需要通过形参来改变实参。

SLTNode* 和 * 的区别是?

SLTNode* 是在告诉编译器,这个变量是个指针,指向了SLTNode这个类型的数据,而并不是在对变量进行解引用操作;*是解引用操作符,在上文已经讲解完毕;而在函数当中,对二级指针进行解引用,其本质上还是个二级指针,因为形式参数依然还是二级指针。

045a54bf69824b03a12d409368acadfe.jpg

 实参形参(前面的*是指函数中使用的解引用操作符个数)
第一个结点的内容*plist(即xxx)

ptail -> xxx

(即一般情况下的**pphead,此处只是因为是结构体指针,所以需要用结构体指针的解引用方式)

指向第一个结点的指针plist*pphead
指向第一个结点的指针的地址&plistpphead
	SLTNode* plist = NULL; //代码1SLTNode* plist1 = node1; //代码2SLTPushBack(&plist1, 1); //代码3SLTPrint(plist1); //代码4plist1->next = NULL; //代码5

代码1:结点为空

代码2:一个有效结点

代码3:传输有效结点的地址

代码4:传输有效结点

代码5:对有效结点解引用,结构体里套了一个指向结构体类型(即为其本身)的指针变量,让该变量指向空,这一操作即是让该结点的下一个结点为空

4.单链表的头插

3abd1fb12b9347c194e420fdf3028572.jpg 4f25ddd568754c5a8f5324e4e3ae0bf0.jpg

	void SLTPushFront(SLTNode** pphead, SLTDataType x){assert(pphead);SLTNode* newnode = SLTBuyNode(x); newnode->next = *pphead; //newnode指向首元结点*pphead = newnode; //将头指针指向新创建的结点}

 

5.单链表的尾删

88de588083eb4ca795eb5a760e86e716.jpg

	void SLTPopBack(SLTNode** pphead) {assert(pphead && *pphead) //指向链表结点的指针不能为空,链表也不能为空SLTNode* prev = *pphead; //为防止野指针,要让尾元结点的next指针指向空,因此需要再创建一个指针,来完成这一操作SLTNode* ptail = *pphead;while (ptail->next){prev = ptail; //prev需要指向尾元结点的前一个结点,因此需要跟着ptail变动ptail = ptail->next; //ptail指向尾元结点}free(ptail); //释放ptail所指向的空间,就能达到尾删的目的ptail = NULL; //动态内存开辟内容,下文有链接prev->next = NULL; //要让prev里的next指针从指向野指针到指向空指针}

 

4b7639d7a4f14bb59bc72213fb92393f.jpg

当只有一个结点的时候,循环直接跳过,ptail和prev指向同一个结点,在将ptail空间释放并变成空指针以后,又一次对prev(即ptail的同一结点)进行了解引用操作,这样代码会报错(对空指针解引用)

因此当链表只有一个节点时,代码如下所示:

	//链表只有一个结点if ((*pphead)->next = NULL) //加括号是因为 -> 优先级高于 *{free(*pphead);*pphead = NULL;}

尾删的全部代码:

	void SLTPopBack(SLTNode** pphead) {assert(pphead && *pphead) //链表不能为空,指向链表结点的指针也不能为空//链表只有一个结点if ((*pphead)->next = NULL) //加括号是因为 -> 优先级高于 *{free(*pphead);*pphead = NULL;}else{SLTNode* prev = *pphead; //为防止野指针,要让尾元结点的next指针指向空,因此需要再创建一个指针,来完成这一操作SLTNode* ptail = *pphead;while (ptail->next){prev = ptail; //prev需要指向尾元结点的前一个结点,因此需要跟着ptail变动ptail = ptail->next; //ptail指向尾元结点}free(ptail); //释放ptail所指向的空间,就能达到尾删的目的ptail = NULL; //动态内存开辟内容,下文有链接prev->next = NULL; //要让prev里的next指针从指向野指针到指向空指针}}

 

6.单链表的头删

dde786103c6c45bcad2d18e0b3569991.jpg cc8648526b6647a383ed9598cf4afc93.jpg

先要保存好头删前的链表第二个节点,头删以后把*pphead指针指向新的节点

void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}
//只有一个结点的情况上述代码也能够解决

小结:

上述的插入、删除代码中,因为四种代码所以必须使用二级指针:
1.free(*pphead);

2.*pphead = NULL;

3.*pphead = newnode;

4.*pphead = next;

即对一级指针类型的参数本身进行了某些操作

7.单链表的查找

在使用查找函数以后,要分为找到了和没找到两种情况,那么我们可以通过如下代码来区分(此处的3是指数据域为3的结点):

SLTNode* find = SLTFind(plist, 3);
if (find == NULL)
{printf("没有找到");
}
else
{printf("找到了");
}

上述代码中,plist即为首元结点的指针

不需要传输首元结点指针的地址,这是因为并不需要通过查找函数来改变链表的首元结点。 

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead; //不想形式参数是个二级指针,因此可以在函数中定义一个指针变量while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}

 

8.在指定位置前插入数据

该函数必须要有三个参数,分别是链表的首元结点(SLTNode** pphead)、在链表的哪个结点前插入(SLTNode* pos)、所需要插入的数据(x)。如下所示:

void SLTInsert(SLTNode** pphead,SLTNode* pos,SLTDataType x)

一个参数使用二级指针,一个参数使用一级指针的原因:

前一个二级指针类型是链表的首元结点地址,是需要通过形式参数来改变首元结点的;下一个一级指针类型是链表指定位置的结点,可以直接通过解引用,对结点的指针域、数据域进行操作。

6fac2faf81f9429c8c9982e47ca39e66.jpg 579a96948e964c49be9432242decbd8f.jpg

 

如果要在第3个结点前插入数据,就需要先创建一个结点,然后把这个结点和第3个结点相连接,然后断开第2个结点和第3个结点的连接,让第2个结点和第3个结点相连。而第2个结点就需要通过遍历获得,循环语句的退出条件是:prev->next != pos (prev为函数中定义的指针变量,遍历以前,prev == *pphead)

aa755b4a649c4be7ad1be126a1474017.jpg

如果是在链表的首元结点之前插入数据,那么prev的next就不可能会等于pos,因此我们在代码实现里要考虑到一般情况和在首元结点之前插入数据两张情况。并且在首元结点前插入数据可以通过头插的函数来实现(这种情况也是函数需要使用二级指针的原因,因为函数需要传一个二级指针)。

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(*pphead && pphead); //链表不能为空,因为为空了,就无法确定“某一位置”了assert(pos);SLTNode* newnode = SLTBuyNode(x);if (pos == *pphead) //在首元结点之前插入{SLTPushFront(pphead, x);}else //一般情况{//找到pos结点的前一个结点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next; //没有找到就找下一个}newnode->next = pos; //完成上文的操作prev->next = newnode;}
}//pos指针即为上文查找部分出现的find

 

9.在指定位置后插入数据

在指定位置后插入数据,不需要再创建一个指针,直接通过"指定位置"结点的next以及该结点next的next来插入即可

91182df3ec8647e4b229c4a88a65f201.jpg

第一种:先红色,再绿色

第二种:先绿色,再红色

以上两种方式是否相同?

//第一种方式的代码
newnode->next = pos->next;
pos->next = newnode;

//第二种方式的代码
pos->next = newnode;
newnode->next = pos->next;

9d37c24988ea4d60811acab0693d4e2d.jpg

第一种方式可以完成我们需要的操作,即先让新节点指向链表的指定节点的下一个结点(next),然后再让指定节点指向新结点

第二种方式让指定节点指向新节点,此时指定结点的下一个结点即为新节点,因此让新节点指向指定的下一个结点时,即为新节点本身,无法完成需要的操作(如上图所示)

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//pos指针即为上文查找部分出现的find

 

10.删除pos结点

删除pos结点需要我们将pos结点的前一个结点和pos的后一个结点相接,因此还需要有个prev指针去保存pos结点的前一个结点,如下图所示 

首元结点、尾元结点的删除请读者自行判断

 ea52e351378b48f48101055f5421b69a.jpg

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);//pos是首元结点if (pos == *pphead){//头删SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;}
}//pos指针即为上文查找部分出现的find
//在使用完find指针以后,需要在函数外进行free、置空操作

 

11.删除pos结点之后的结点

删除pos->next,让pos结点和pos->next->next相连即能完成操作,如下图所示: 

ad6dcc92da534b8699185df2fbbbfc13.jpg

void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next; //保存pos的下一个结点pos->next = del->next;  //让pos的下一个结点(即del)和pos的下一个结点(即del)的下一个节点相链接free(del);del = NULL;
}

 

12.删除链表

d1a63226464048a1821ef0bdcdb021d5.jpg

 ed7295ad506d4d47a02a840a64af2344.jpg

传入首元结点,然后保存好首元结点的下一个结点(next指针),然后free掉pcur以后,让next指针和pcur指针都往后走一个结点,循环退出条件为pcur为空指针。(如上图所示)

void SListDesTroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next; free(pcur);pcur = next;}*pphead = NULL; //已经释放完所有的内容了,将pphead置为空
}

 

13.可能存在的疑惑解答

**pphead作为形参出现那么多次,那么他到底对应哪个结点呢?

他既可以对应空指针(链表完全没有结点,对应了插入等的情况),同时也可以对应首元结点(头删和尾删等的情况)

为什么在部分函数里有时候free不需要二级指针?

例如free(ptail),在尾删板块出现。这是因为ptail是在函数里创建、使用的,无需通过函数来改变任何一个实参(它也没有对应的实参、因为它并不是一个形式参数)。在出了函数以后,ptail指针就没有作用了,如果在函数内没有进行free、置空操作,就会变成一个野指针。 

单链表的开始和初始化必须要像1那样写吗?
并不是,其实完全可以通过尾插、头插等等方式进行链表的初始化的。

所有函数都必须要传二级指针吗?

有些一定需要传,例如*pphead=newnode(在头插板块),因为这条代码如果不写在函数里,就完成不了头插操作;在某些情况下不一定需要传,例如free(*pphead),放到函数实现外的部分,可以通过free(plist)完成操作,不写在函数里也不会影响函数的功能实现。

如果打印函数里使用形参进行打印,不创建变量了,是否可以?
其实是可以的,因为形参是一级指针,不会影响实参。

在函数中,定义一个变量pcur = *pphead了以后,为什么影响了pcur就能影响整个链表了?

在函数当中,实际上pcur能直接看成pphead解引用了一次,即可以把所有的pcur看成是(*pphead),这就像是定义宏一样;这就像是传入了一个一级指针  int *a,然后在函数了假设变量int b = *a ,然后通过b的改变来改变传入进来的*a一样。而我们会在函数里使用到pcur = *pphead有两个原因,其一就是为了代码的可读性,其二就是为了让pphead指针一直指向首元结点。

 14.全部用于测试的代码

请注意,以下代码有些是不兼容的,就比如plist指针一会是NULL,一会是首元结点;同时有些功能,例如单链表的查找,不能链表是空的,需要有前提条件。因此请读者对函数进行一一尝试,需尝试的功能直接取消注释即可。

int main()
{//SListTest01(); //对于开始和初始化//SLTNode* plist = NULL; //从没有链表开始对于尾插//SLTPushBack(&plist, 1);//SLTPrintf(plist); //此处plist又变成了首元结点,在使用完该操作以后,plist由于//SLTPushBack(&plist, 2);//SLTPrintf(plist);//SLTPushBack(&plist, 3);//SLTPrintf(plist);//SLTPushBack(&plist, 4);//SLTPrintf(plist);对于头插(尾插、头插写一块了)//SLTPushFront(&plist, 6);//SLTPrintf(plist);//SLTPushFront(&plist, 2);//SLTPrintf(plist);//SLTPushFront(&plist, 3);//SLTPrintf(plist);//SLTPushFront(&plist, 4);//SLTPrintf(plist);尾删、头删(先决条件:不为空)//SLTPopBack(&plist);//SLTPrint(plist);//SLTFrontBack(&plist);//SLTPrint(plist);查找(先决条件:不为空)//SLTNode* find = SLTFind(plist, 3);//if (find == NULL)//{//	printf("没有找到");//}//else//{//	printf("找到了");//}指定位置的插入删除(先决条件:不为空+有具体指定位置)//SLTNode* find = SLTFind(plist, 3); //find即为具体指定位置//SLTInsert(&plist, find, 10);//SLTPrint(plist);//SLTInsertAfter(find, 10);//SLTPrint(plist);//SLTErase(&plist, find);//SLTPrintf(plist);//SLTEraseAfter(find);//SLTPrintf(plist);//销毁//SListDesTroy(&plist);//SLTPrintf(plist);
}
//以上代码是在vscode2022中进行的

 

四.文章链接

指针讲解:指针复习

动态内存开辟讲解:动态内存开辟复习

 

 

 

 

 

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

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

相关文章

GitHub工程获取第三方PR操作

GitHub工程获取第三方PR操作 1. 源由2. 获取第三方PRStep 1:安装ghStep 2:获取个人TokenStep 3:通过git协议获取代码Step 4:获取第三方PR分支 3. 总结 1. 源由 通常来说,GitHub上通常有三种场景: 工程管理…

冯喜运:6.8下周伦敦金行情怎么看?黄金原油下周操作建议

【黄金消息面分析】:黄金不是由通胀驱动的。它也不是由通货紧缩驱动的。它也不是由美元驱动的。当股市反弹时,它也不会下跌,反之亦然。黄金是由市场情绪驱动的。而且,它是黄金市场上唯一的常数,使人们能够对黄金价格趋…

c++使用_beginthreadex创建线程

记录使用_beginthreadex(),来创建线程。方便后期的使用。 创建一个线程 相关函数介绍 unsigned long _beginthreadex( void *security, // 安全属性, 为NULL时表示默认安全性 unsigned stack_size, // 线程的堆栈大小, 一般默认为0 u…

3D打印随形透气钢:技术革新引领模具制造新潮流

在模具制造领域,透气钢一直扮演着重要角色,它能够有效解决模具困气问题,提高注塑成型的效率和质量。然而,传统的透气钢制造方法受限于工艺和材料,难以满足复杂模具的需求。随着3D打印技术的飞速发展,3D打印…

Go微服务: 分布式之通过本地消息实现最终一致性

概述 我们的业务场景是可以允许我们一段时间有不一致的消息的状态的,并没有说必须特别高的这个消息的一致性比如说在TCC这个架构中,如果采用了消息的最终一致性,整体架构设计要轻松好多即便我们库存服务挂了,或者我们积分服务挂了…

【2024PythonPycharm详细安装教程】

1.打开官网 https://www.python.org/ downloads——>Windows 2.找到 Download Windows installer (64-bit) 下载 3.下载完成双击安装包 勾选Add python.exe to PATH(自动配置系统变量) 点击Install Now(默认安装) 然后看到安装成功&#xff0…

Oracle EBS AP发票创建会计科目错误:子分类帐日记帐分录未按输入币种进行平衡

系统版本 RDBMS : 12.1.0.2.0 Oracle Applications : 12.2.6 问题症状: 提交“创建会计科目”请求提示错误信息如下: 中文报错: 该子分类帐日记帐分录未按输入币种进行平衡。请检查日记帐分录行中输入的金额。 英文报错:The subledger journal entry does not balance i…

PS的stable diffusion插件安装指南

PS的stable diffusion插件安装指南 1.首先要安装stable diffusion,具体安装方法,参考https://blog.csdn.net/sheji888/article/details/139196688 stable diffusion要求要启用API功能 2.安装ps2023以上版本,低于这个版本不能使用stable diff…

17- Redis 中的 quicklist 数据结构

在 Redis 3.0 之前,List 对象的底层数据结构是双向链表或者压缩列表,然后在 Redis 3.2 的时候,List 对象的底层改由 quicklist 数据结构实现。 其实 quicklist 就是【双向链表 压缩列表】组合,因为一个 quicklist 就是一个链表&…

什么是ESG?

什么是ESG? ESG的实施和发展是企业应对全球和国内环境、社会和治理挑战的关键路径。《ESG入门一本通》详细阐述了ESG的概念、发展历程和评价体系,并结合中国的实际情况,强调了ESG的重要性和必要性。企业需重视ESG管理和信息披露,…

详细分析Mysql中的JSON_OBJECT() 基本知识(附Demo)

目录 前言1. 基本知识2. Demo 前言 对于基本的命令行以及优化推荐阅读: 数据库中增删改常用语法语句(全)Mysql优化高级篇(全)命令行登录Mysql的详细讲解 1. 基本知识 JSON_OBJECT() 是 MySQL 中用于生成 JSON 对象…

信息系统项目管理师0149:输入(9项目范围管理—9.4收集需求—9.4.1输入)

点击查看专栏目录 文章目录 9.4 收集需求9.4.1 输入9.4 收集需求 收集需求是为实现目标而确定,记录并管理干系人的需要和需求的过程。本过程的主要作用是为定义产品范围和项目范围奠定基础。本过程仅开展一次或仅在项目的预定义点开展。收集需求过程的数据流向如图 9-2 所示。…

电脑开机出现英文字母,如何解决这个常见问题?

电脑开机时出现英文字母的情况通常意味着系统在启动过程中遇到了问题。这些英文字母可能是错误信息、系统提示或BIOS设置问题。通过理解这些信息并采取适当的措施,您可以解决大多数启动问题。本文将介绍三种解决电脑开机出现英文字母问题的方法,帮助您恢…

BGP汇总+认证

一、BGP 的宣告问题 1、在 BGP 协议中每台运行 BGP 的设备上,宣告本地直连路由 2、在 BGP 协议中运行 BGP 协议的设备来宣告.通过 IGP 学习到的,未运行 BGP 协议设备产2、生的路由; 在 BGP 协议中宣告本地路由表中路由条目时,将携带本地到达这…

51单片机STC89C52RC——创建Keil项目

一,打开Keil5 菜单---project--New uVision Project... 二,新建项目文件夹 弹出选择文件夹对话框后,可以右键新建一个项目文件夹【文件夹名字可以随便取,自己看得懂就行,建议不要有特殊字符】,这样该项目…

MATLAB算法实战应用案例精讲-【数模应用】因子分析(附MATLAB和python代码实现)

目录 前言 算法原理 SPSS因子分析 操作步骤 结果分析 SPSSAU 因子分析案例 1、背景 2、理论 3、操作 4、SPSSAU输出结果 5、文字分析 6、剖析 疑难解惑 同源方差或共同方法变异偏差,Harman单因子检验? 提示出现奇异矩阵? 因子得分和综合得分? 因子分析计…

品牌策划:不只是工作,是一场创意与学习的旅程

你是否认为只有那些经验丰富、手握无数成功案例的高手才能在品牌策划界崭露头角? 今天,我要悄悄告诉你一个行业内的秘密:在品牌策划的世界里,经验虽重要,但绝非唯一。 1️、无止境的学习欲望 品牌策划,这…

rtl8723DU移植 android4.4 4418 (第二部分蓝牙部分)

使用的代码: HMI (8723bu)源码 567_RTL8723DU_WiFi_linux_v5.6.5.3_35502_COEX20181130-2e2e.20191025.zip 由于之前写的所有笔记没有保存,这里只能是部分。 0、 前置知识 1 、kernel 的移植 2、hardwire的移植 将 驱动中的 h…

系统思考—啤酒游戏沙盘

10个智商120的‮组人‬成‮团的‬队,大‮的家‬集体智‮是商‬多少? 在‮期长‬辅‮各导‬种‮业企‬的‮程过‬中,我‮经们‬常‮察观‬到,虽‮每然‬个‮门部‬都‮力努‬解决‮己自‬的问题,但‮司公‬整体的‮收应…

Rust 标记一个属性或函数为废弃

如题,演示Rust 标记一个属性或函数为废弃的基本使用方法: 示例: use serde::{Deserialize, Serialize};#[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct GrpcOptions {pub addr: String,pub max_recv_message_size: u…