🔥个人主页:艾莉丝努力练剑

❄专栏传送门:《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题

🍉学习方向:C/C++方向

⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平


前言:本篇文章,我们复盘顺序表和链表相关的知识点,在初阶的数据结构与算法阶段,我们把知识点分成三部分,复杂度作为第一部分,顺序表和链表、栈和队列、二叉树为第二部分,排序为第二部分,我们之前已经介绍完了第一部分:算法复杂度,本文我们继续学习第二部分中的顺序表和链表部分内容啦。

        半个多月前,博主更新了头插、尾删、头删、随机位置插入、随机位置删除、查找、修改、菜单等内容,本篇文章,我们就来复盘一下动态顺序表的内容,博主会添加很多新内容,希望对大家的顺序表学习有所帮助。


​ 


目录

正文

三、单链表

(二)实现单链表

3、增删查改

(1)尾插

(2)头插

(3)在指定位置之前插入数据

(4)在指定位置之后插入数据

(1)尾删

(2)头删

(3)删除pos节点

(4)删除pos之后的节点

(1)查找

改 

(1)修改

 销毁链表

(1)销毁链表

4、完整代码 

(1)SList.h:

(2)SList.c:

(3)test.c:

结尾


正文

提醒:为什么我们要学那么多的数据结构?这是因为没有一种数据结构能够去应对所有场景。我们在不同的场景需要选择不同的数据结构,所以数据结构没有谁好谁坏之分,而评估数据结构的好坏要针对场景,如果在一种场景下我们需要频繁地对头部进行插入删除操作,那么这个时候我们用链表;但是如果对尾部进行插入删除操作比较频繁,那我们用顺序表比较好。

        因此,不同的场景我们选择不同的数据结构。

三、单链表

(二)实现单链表

3、增删查改
(1)尾插

我们要申请新的节点(需要malloc),我们单独封装一个函数。

现在新节点就申请好了,我们要让5和4节点连起来:

这就是为什么我们明明已经有phead这个指针,还要额外再定义一个指针pcur——

这样一来pcur在不断变化,phead保持不变,phead始终保存的是第一个节点的地址。在这里我不想改变phead,phead始终指向第一个节点,方便我们后面遍历完了如果还要再从头开始遍历的时候我们能够找到第一个节点的地址。

我们定义pcur,只要pcur不为空,我们就进入循环,pcur为空我们就跳出循环。

我们这边调用test02: 

 这是SList.c尾插的代码:

//尾插
void SLTPushBack(​SLTNode* phead, SLTDatatype x)
{//申请新节点​SLTNode* newnode = SLTBuyNode(x);//链表为空——要特殊处理if (phead == NULL){phead = newnode;}​SLTNode* ptail = phead;while (ptail->next != NULL){ptail = ptail->next;}//找到了尾节点 ptail newnodeptail->next = newnode;
}

这边其实代码还是有问题的,我们先运行一下看看:

尾插: 

SList.c:

//尾插
void SLTPushBack(​SLTNode** pphead, SLTDatatype x)
{//申请新节点​SLTNode* newnode = SLTBuyNode(x);//链表为空——要特殊处理if (*pphead == NULL){*pphead = newnode;}else{​SLTNode* ptail = *pphead;while (ptail->next != NULL){ptail = ptail->next;}//找到了尾节点 ptail newnodeptail->next = newnode;}
}

test.c: 

void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);
}int main()
{/*test01();*/test02();return 0;
}

尾插的时间复杂度:O(N) 。

(2)头插

头插:

SList.c:

//头插
void SLTPushFront(​SLTNode** pphead, SLTDatatype x)
{assert(pphead);​SLTNode* newnode = SLTBuyNode(x);//newnode *ppheadnewnode->next = *pphead;*pphead = newnode;
}

test.c: 

void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPrint(plist);SLTPushFront(&plist, 2);SLTPrint(plist);SLTPushFront(&plist, 3);SLTPrint(plist);SLTPushFront(&plist, 4);SLTPrint(plist);
}int main()
{/*test01();*/test02();return 0;
}

头插的时间复杂度:O(1) 。 

(3)在指定位置之前插入数据

函数形参中同样需要用二级指针传入链表地址,还要传入指定位置的地址和需要插入的数据。

在函数中,需要先找到指定位置的前一个节点,然后把需要添加的数据插入到这两个节点之间:

SList.c:

void SLTPushBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && pos);//当pos为头节点时 相当于头插if(*pphead == pos){SLTPushFront(pphead, x);}else{SLTNode* newNode = SLTBuyNode(x);//找到pos前一个节点SLTNode* pre = *pphead;while(pre->next != pos){pre = pre->next;}//把新节点放在pre和pos之间newNode->next = pre->next;//等价于newNode->next = pospre->next = newNode;}}

test.c: 

void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);​SLTNode* pos = SLTFind(plist, 4);SLTInsert(&plist, pos, 100);SLTPrint(plist);
}int main()
{/*test01();*/test02();return 0;
}

在指定位置之前插入数据时间复杂度:O(N)。 

(4)在指定位置之后插入数据

和类似,我们找到指定位置的后一个节点,把新节点放在这两个节点之间——

4后面插入一个100: 

1后面插入一个100:

SList.c:

void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && pos);if(pos->next == NULL){//相当于尾插SLTPushBack(pphead, x);}else{SLTNode* newNode = SLTBuyNode(x);newNode->next = pos->next;pos->next = newNode;//顺序不能颠倒 否则会先修改pos->next}
}

test.c: 

void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);​SLTNode* pos = SLTFind(plist, 1);//SLTInsert(&plist, pos, 100);SLTInsertAfter(pos, 100);SLTPrint(plist);
}int main()
{/*test01();*/test02();return 0;
}

在指定位置之后插入数据时间复杂度:O(1)。  

(1)尾删

pphead和phead的关系:

本文中的pphead是形参,phead是画图时定义的一个指针。

phead是指向第一个结点的指针;

在程序里面,指向第一个节点的指针在形参里面是*pphead。

函数形参中二级指针存放表头地址。

如果链表只有一个元素需要释放头节点的空间,并把链表指针置为空;

如果有多个元素需要找到倒数第二个节点和最后一个节点,释放最后一个节点,并把倒数第二个节点的next指针置为空。

​出问题了: 

万一链表只有一个节点,我们要注意这种情况——

尾删:

SList.c:

//尾删
void SLTPopBack(​SLTNode** pphead)
{//链表为空不能删除assert(pphead && *pphead);//pphead是*pphead的地址//pphead是一个二级指针,我们对pphead解引用一次,*pphead就是指向第一个节点的地址//*pphead为空说明链表为空//链表只有一个节点的情况if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{​SLTNode* prev = NULL;​SLTNode* ptail = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}//prev  ptailprev->next = NULL;free(ptail);ptail = NULL;}
}

test.c:

void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);//SLTPushFront(&plist, 1);//SLTPrint(plist);//SLTPushFront(&plist, 2);//SLTPrint(plist);//SLTPushFront(&plist, 3);//SLTPrint(plist);//SLTPushFront(&plist, 4);//SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}int main()
{/*test01();*/test02();return 0;
}

链表为空,如果还要再删一次,程序就会assert(断言)报错:

表达式为假,因为*pphead为空了,在69行断言出现了报错。

如果初始将prev置为*pphead也要讨论:

这样两个指针都指向这个节点,让prev下一个节点置为空——本身它下一个节点就为空,现在把ptail 给 free掉,prev就变成了野指针。

如果只有一个节点,prev->next = NULL;这一行代码就可以不要了,而且ptailprev都要free。如果不止一个节点的话,那这又是一套逻辑,这种写法会更复杂一些。

博主给出的这种写法会简单一些。 

尾删的时间复杂度:O(N) 。 

(2)头删

与尾删相似,让头指针指向第二个节点,并释放头节点——

头删:

SList.c:

//头删
void SLTPopFront(​SLTNode** pphead)
{assert(pphead && *pphead);​SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}

test.c:

void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);//头删SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);
}int main()
{/*test01();*/test02();return 0;
}

再删一次就会断言报错——

头删的时间复杂度:O(1) 。

(3)删除pos节点

让pos前一个节点指向pos后一个节点——

SList.c:

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && pos && *pphead);//pos为头节点if(pos == *pphead){free(*pphead);*pphead = NULL;pos = NULL;}else{SLTNode* prev = *pphead;while(prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}

test.c:

void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);​SLTNode* pos = SLTFind(plist, 3);SLTErase(&plist, pos);SLTPrint(plist);
}int main()
{/*test01();*/test02();return 0;
}
(4)删除pos之后的节点

找到pos之后的节点del,连接pos节点和del->next,再释放del——

SList.c:

void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{assert(pphead && pos && pos->next && *pphead);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

test.c:

void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);​SLTNode* pos = SLTFind(plist, 3);SLTErase(&plist, pos);SLTPrint(plist);
}int main()
{/*test01();*/test02();return 0;
}
​
(1)查找

来个不存在的数据测试一下—— 

查找 

SList.c:

//查找
​SLTNode* SLTFind(​SLTNode* phead, SLTDatatype x)
{​SLTNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}//未找到return NULL;
}

test.c:

void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);​SLTNode* pos = SLTFind(plist, 100);if (pos){printf("找到了!\n");}else{printf("未找到!\n");}
}int main()
{/*test01();*/test02();return 0;
}
改 
(1)修改

修改指定位置的数据:直接修改该节点data的值——

SList.c:

void SLTChangeData(SLTNode* pos, SLTDataType x)
{assert(pos);pos->data = x;
}
 销毁链表

遍历链表,释放每一个节点,由于需要修改指向头节点的指针,因此函数形参中要用二级指针——

(1)销毁链表

SList.c:

void SLTDestroy(SLTNode** pphead)
{assert(pphead );SLTNode* pcur = *pphead;while(pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

这里pos=NULL、pcur=NULL、next=NULL加上置为空是养成好习惯,这里是默认置为空。 

test.c: 

void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);​SLTNode* pos = SLTFind(plist, 3);SListDestory(&plist);
}int main()
{/*test01();*/test02();return 0;
}
4、完整代码 
(1)SList.h:
#pragma once#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//链表的结构​
typedef int SLTDatatype;
typedef struct SListNode
{SLTDatatype data;struct SListNode* next;//指向下一个节点的地址
}​SLTNode;//typedef struct SListNode SLTNode;void SLTPrint(​SLTNode* phead);//尾插
void SLTPushBack(​SLTNode** pphead, SLTDatatype x);//头插
void SLTPushFront(​SLTNode** pphead, SLTDatatype x);//尾删
void SLTPopBack(​SLTNode** pphead);//头删
void SLTPopFront(​SLTNode** pphead);//查找
​SLTNode* SLTFind(​SLTNode* phead, SLTDatatype x);//在指定位置之前插入数据
void SLTInsert(​SLTNode** pphead, ​SLTNode* pos, SLTDatatype x);//在指定位置之后插入数据
void SLTInsertAfter(​SLTNode* pos, SLTDatatype x);//删除pos节点
void SLTErase(​SLTNode** pphead, ​SLTNode* pos);//删除pos之后的节点
void SLTEraseAfter(​SLTNode* pos);//修改
void SLTChangeData(​SLTNode* pos, SLTDatatype x);//销毁链表
void SListDestory(​SLTNode** pphead);
(2)SList.c:
#define  _CRT_SECURE_NO_WARNINGS  1#include"SList.h"void SLTPrint(​SLTNode* phead)
{​SLTNode* pcur = phead;while (pcur != NULL){printf("%d -> ", pcur->data);pcur = pcur->next;}printf("NULL\n");
}//后续我们要申请新节点就直接调用SLTBuyNode方法
​SLTNode* SLTBuyNode(SLTDatatype x)
{​SLTNode* newnode = (​SLTNode*)malloc(sizeof(​SLTNode));//malloc不一定申请成功,我们判断一下if (newnode == NULL){printf("malloc fail!");exit(1);}//初始化一下newnode->data = x;newnode->next = NULL;return newnode;
}//尾插
void SLTPushBack(​SLTNode** pphead, SLTDatatype x)
{//申请新节点​SLTNode* newnode = SLTBuyNode(x);//链表为空——要特殊处理if (*pphead == NULL){*pphead = newnode;}else{​SLTNode* ptail = *pphead;while (ptail->next != NULL){ptail = ptail->next;}//找到了尾节点 ptail newnodeptail->next = newnode;}
}//头插
void SLTPushFront(​SLTNode** pphead, SLTDatatype x)
{assert(pphead);​SLTNode* newnode = SLTBuyNode(x);//newnode *ppheadnewnode->next = *pphead;*pphead = newnode;
}//尾删
void SLTPopBack(​SLTNode** pphead)
{//链表为空不能删除assert(pphead && *pphead);//pphead是*pphead的地址//pphead是一个二级指针,我们对pphead解引用一次,*pphead就是指向第一个节点的地址//*pphead为空说明链表为空//链表只有一个节点的情况if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{​SLTNode* prev = NULL;​SLTNode* ptail = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}//prev  ptailprev->next = NULL;free(ptail);ptail = NULL;}
}//头删
void SLTPopFront(​SLTNode** pphead)
{assert(pphead && *pphead);​SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}//查找
​SLTNode* SLTFind(​SLTNode* phead, SLTDatatype x)
{​SLTNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}//未找到return NULL;
}//在指定位置之前插入数据
void SLTInsert(​SLTNode** pphead, ​SLTNode* pos, SLTDatatype x)
{assert(pphead && pos);​SLTNode* newnode = SLTBuyNode(x);//pos指向头节点if (pos == *pphead){//头插SLTPushFront(pphead, x);}else{//找pos前一个节点​SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev   newnode  posprev->next = newnode;newnode->next = pos;}
}//在指定位置之后插入数据
void SLTInsertAfter(​SLTNode* pos, SLTDatatype x)
{assert(pos);​SLTNode* newnode = SLTBuyNode(x);//pos newnode pos->nextnewnode->next = pos->next;pos->next = newnode;
}//删除pos节点
void SLTErase(​SLTNode** pphead, ​SLTNode* pos)
{assert(pphead && pos);//pos刚好是头节点——头删if (pos==*pphead){SLTPopFront(pphead);}else{​SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//prev pos pos->nextprev->next = pos->next;free(pos);pos = NULL;}
}//删除pos之后的节点
void SLTEraseAfter(​SLTNode* pos)
{assert(pos);//pos  del  del->next​SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}//修改
void SLTChangeData(​SLTNode* pos, SLTDatatype x)
{assert(pos);pos->data = x;
}//销毁链表
void SListDestory(​SLTNode** pphead)
{//一个一个销毁assert(pphead);​SLTNode* pcur = *pphead;while (pcur){​SLTNode* next = pcur->next;free(pcur);pcur = next;}//*pphead是野指针,要置为空*pphead = NULL;
}
(3)test.c:
#define  _CRT_SECURE_NO_WARNINGS  1#include"SList.h"int test01()
{//创建一个链表——实际上是创建一个一个节点,再把节点连起来​SLTNode*node1=(​SLTNode*)malloc(sizeof(​SLTNode));​SLTNode*node2=(​SLTNode*)malloc(sizeof(​SLTNode));​SLTNode*node3=(​SLTNode*)malloc(sizeof(​SLTNode));​SLTNode*node4=(​SLTNode*)malloc(sizeof(​SLTNode));node1->data = 1;node2->data = 2;node3->data = 3;node4->data = 4;node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;​SLTNode* plist = node1;//打印链表SLTPrint(plist);
}void test02()
{//创建空链表​SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);////SLTPushFront(&plist, 1);//SLTPrint(plist);//SLTPushFront(&plist, 2);//SLTPrint(plist);//SLTPushFront(&plist, 3);//SLTPrint(plist);//SLTPushFront(&plist, 4);//SLTPrint(plist);////SLTPopBack(&plist);//SLTPrint(plist);//SLTPopBack(&plist);//SLTPrint(plist);//SLTPopBack(&plist);//SLTPrint(plist);//SLTPopBack(&plist);//SLTPrint(plist);//SLTPopBack(&plist);//SLTPrint(plist);////头删
//	SLTPopFront(&plist);
//	SLTPrint(plist);
//	SLTPopFront(&plist);
//	SLTPrint(plist);
//	SLTPopFront(&plist);
//	SLTPrint(plist);
//	SLTPopFront(&plist);
//	SLTPrint(plist);​SLTNode* pos = SLTFind(plist, 3);//if (pos)//{//	printf("找到了!\n");//}//else//{//	printf("未找到!\n");//}//SLTInsert(&plist, pos, 100);/*SLTInsertAfter(pos, 100);*///SLTErase(&plist, pos);//SLTPrint(plist);SListDestory(&plist);
}int main()
{/*test01();*/test02();return 0;
}

结尾

往期回顾:

【数据结构与算法】数据结构初阶:详解顺序表和链表(三)——单链表(上

本期内容需要回顾的C语言知识如下面的截图中所示(指针博主写了6篇,列出来有水字数嫌疑了,就只放指针第六篇的网址,博主在指针(六)把指针部分的前五篇的网址都放在【往期回顾】了,点击【传送门】就可以看了)。

大家如果对前面部分的知识点印象不深,可以去上一篇文章的结尾部分看看,博主把需要回顾的知识点相关的博客的链接都放在上一篇文章了,上一篇文章的链接博主放在下面了:

 【数据结构与算法】数据结构初阶:详解顺序表和链表(三)——单链表(上)

结语:本篇文章到这里就结束了,对数据结构的单链表知识感兴趣的友友们可以在评论区留言,博主创作时可能存在笔误,或者知识点不严谨的地方,大家多担待,如果大家在阅读的时候发现了行文有什么错误欢迎在评论区斧正,再次感谢友友们的关注和支持!

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

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

相关文章

Java+AI精准广告革命:实时推送系统实战指南

⚡ 广告推送的世纪难题 用户反感&#xff1a;72%用户因无关广告卸载APP 转化率低&#xff1a;传统推送转化率<0.5% 资源浪费&#xff1a;40%广告预算被无效曝光消耗 &#x1f9e0; 智能广告系统架构 &#x1f525; 核心模块实现&#xff08;Java 17&#xff09; 1. 实时…

JVM组成及运行流程 - 面试笔记

JVM整体架构 JVM&#xff08;Java Virtual Machine&#xff09;是Java程序运行的核心环境&#xff0c;主要由以下几个部分组成&#xff1a;1. 程序计数器&#xff08;Program Counter&#xff09; 特点&#xff1a;线程私有&#xff0c;每个线程都有独立的程序计数器作用&#…

JavaEE——线程池

目录前言1. 概念2. 线程池相关参数3. Executors的使用总结前言 线程是为了解决进程太重的问题&#xff0c;操作系统中进程的创建和销毁需要较多的系统资源&#xff0c;用了轻量级的线程来代替部分线程&#xff0c;但是如果线程创建和销毁的频率也开始提升到了一定程度&#xf…

3 c++提高——STL常用容器(一)

目录 1 string容器 1.1 string基本概念 1.2 string构造函数 1.3 string赋值操作 1.4 string字符串拼接 1.5 string查找和替换 1.6 string字符串比较 1.7 string字符存取 1.8 string插入和删除 1.9 string子串 2 vector容器 2.1 vector基本概念 2.2 vector构造函数…

手把手教你用【Go】语言调用DeepSeek大模型

1、首先呢&#xff0c;点击 “DeepSeek”” 这个&#xff0c; 可以充1块玩玩。 2、然后获取api-key 3、替换apiKey const (apiURL "https://api.deepseek.com/v1/chat/completions"apiKey "your api key" // 替换为你的实际 API KeymodelName &…

自动化UI测试工具TestComplete的核心功能及应用

对桌面应用稳定性与用户体验的挑战&#xff0c;手动测试效率低、覆盖有限&#xff0c;而普通自动化工具常难以应对复杂控件识别、脚本灵活性和大规模并行测试的需求。 自动化UI测试工具TestComplete凭借卓越的对象识别能力、灵活的测试创建方式以及高效的跨平台并行执行功能&a…

【C/C++】迈出编译第一步——预处理

【C/C】迈出编译第一步——预处理 在C/C编译流程中&#xff0c;预处理&#xff08;Preprocessing&#xff09;是第一个也是至关重要的阶段。它负责对源代码进行初步的文本替换与组织&#xff0c;使得编译器在后续阶段能正确地处理规范化的代码。预处理过程不仅影响编译效率&…

快捷键——VsCode

一键折叠所有的代码块 先按 ctrl K&#xff0c;再ctrl 0 快速注释一行 ctrl /

import 和require的区别

概念 import 是es6 规范&#xff0c;主要应用于浏览器和主流前端框架当中&#xff0c;export 导出&#xff0c; require 是 commonjs 规范&#xff0c;主要应用于nodejs环境中&#xff0c;module.exports 导出编译规则 import 静态导入是编译时解析&#xff0c;动态导入是执…

8、鸿蒙Harmony Next开发:相对布局 (RelativeContainer)

目录 概述 基本概念 设置依赖关系 设置参考边界 设置锚点 设置相对于锚点的对齐位置 子组件位置偏移 多种组件的对齐布局 组件尺寸 多个组件形成链 概述 RelativeContainer是一种采用相对布局的容器&#xff0c;支持容器内部的子元素设置相对位置关系&#xff0c;适…

Linux命令的命令历史

Linux下history命令可以对当前系统中执行过的所有shell命令进行显示。重复执行命令历史中的某个命令&#xff0c;使用&#xff1a;!命令编号&#xff1b;环境变量histsize的值保存历史命令记录的总行数&#xff1b;可用echo查看一下&#xff1b;需要大写&#xff1b;环境变量hi…

【C++小白逆袭】内存管理从崩溃到精通的秘籍

目录【C小白逆袭】内存管理从崩溃到精通的秘籍前言&#xff1a;为什么内存管理让我掉了N根头发&#xff1f;内存四区大揭秘&#xff1a;你的变量都住在哪里&#xff1f;&#x1f3e0;内存就像大学宿舍区 &#x1f3d8;️C语言的内存管理&#xff1a;手动搬砖时代 &#x1f9f1;…

【网络安全】利用 Cookie Sandwich 窃取 HttpOnly Cookie

未经许可,不得转载。 文章目录 引言Cookie 三明治原理解析Apache Tomcat 行为Python 框架行为窃取 HttpOnly 的 PHPSESSID Cookie第一步:识别 XSS 漏洞第二步:发现反射型 Cookie 参数第三步:通过 Cookie 降级实现信息泄露第四步:整合攻击流程修复建议引言 本文将介绍一种…

【工具】什么软件识别重复数字?

网上的数字统计工具虽多&#xff0c;但处理重复数字时总有点不尽如人意。 要么只能按指定格式输入&#xff0c;要么重时得手动一点点筛&#xff0c;遇上数据量多的情况&#xff0c;光是找出重复的数字就得另外花不少功夫。​ 于是我做了个重复数字统计器&#xff0c;不管是零…

CSS分层渲染与微前端2.0:解锁前端性能优化的新维度

CSS分层渲染与微前端2.0&#xff1a;解锁前端性能优化的新维度 当你的页面加载时间超过3秒&#xff0c;用户的跳出率可能飙升40%以上。这并非危言耸听&#xff0c;而是残酷的现实。在当前前端应用日益复杂、功能日益臃肿的“新常态”下&#xff0c;性能优化早已不是锦上添花的“…

AI Agent开发学习系列 - langchain之Chains的使用(5):Transformation

Transformation&#xff08;转换链&#xff09;是 LangChain 中用于“自定义数据处理”的链式工具&#xff0c;允许你在链路中插入任意 Python 代码&#xff0c;对输入或中间结果进行灵活处理。常用于&#xff1a; 对输入/输出做格式化、过滤、摘要、拆分等自定义操作作为 LLMC…

Druid 连接池使用详解

Druid 连接池使用详解 一、Druid 核心优势与架构 1. Druid 核心特性 特性说明价值监控统计内置 SQL 监控/防火墙实时查看 SQL 执行情况防 SQL 注入WallFilter 防御机制提升系统安全性加密支持数据库密码加密存储符合安全审计要求扩展性强Filter 链式架构自定义功能扩展高性能…

9.2 埃尔米特矩阵和酉矩阵

一、复向量的长度 本节的主要内容可概括为&#xff1a;当对一个复向量 z\pmb zz 或复矩阵 A\pmb AA 转置后&#xff0c;还要取复共轭。 不能在 zTz^TzT 或 ATA^TAT 时就停下来&#xff0c;还要对所有的虚部取相反的符号。对于一个分量为 zjajibjz_ja_jib_jzj​aj​ibj​ 的列向…

AI驱动的低代码革命:解构与重塑开发范式

引言&#xff1a;低代码平台的范式转移 当AI技术与低代码平台深度融合&#xff0c;软件开发正经历从"可视化编程"到"意图驱动开发"的根本性转变。这种变革不仅提升了开发效率&#xff0c;更重新定义了人与系统的交互方式。本文将从AI介入的解构层次、交互范…

zookeeper etcd区别

ZooKeeper与etcd的核心区别体现在设计理念、数据模型、一致性协议及适用场景等方面。‌ZooKeeper基于ZAB协议实现分布式协调&#xff0c;采用树形数据结构和临时节点特性&#xff0c;适合传统分布式系统&#xff1b;而etcd基于Raft协议&#xff0c;以高性能键值对存储为核心&am…