当前位置: 首页 > news >正文

【数据结构 · 初阶】- 单链表

目录

一.相关指针知识点

二.链表

1.为什么学了顺序表还要学链表

2.优点

三.实现

1.链表的打印 —— 理解链表结构

(2) 物理结构图

2.链表的尾插 —— 入门

错误写法:tail != NULL

总结:

正确代码物理图解:

(2) 尾插整体代码 (思考对吗?)

Bug

3.头插

4.尾删

Bug

5.头删

6.查找

7. Find 查找的功能

(1)pos 之前插入

(2)pos 位置删除

(3)pos 之后插入

(4)pos位置后面删除

四.思维提升

五.总结

1.传什么?

2.要不要断言?

(1)打印、查找

(2)pphead

(3)*pphead

六.整体代码

SList.h

SList.c


一.相关指针知识点

调用一个函数,就会建立一个空间,这个空间叫栈帧。局部变量是存放在栈帧里的(除了static修饰的局部变量)。函数结束,栈帧空间就销毁,局部变量也销毁

函数传参,不管是传值,还是传地址,其实都是拷贝。就看拷贝值还是地址。

代码1:y 的改变,不会改变 x 的值

void Func(int y)
{y = 1;
}
int main()
{int x = 0;Func(x);return 0;
}

这是两个栈帧,Func 里面是 y,main 里面是 x。x 传给 y 是拷贝给 y,y 的改变不会影响 x,并且 Func 会销毁

代码2:解决上面问题,传地址。改变的是 int ,使用的是 int 的指针

void Func(int* p)
{*p = 1;
}
int main()
{int x = 0;Func(&x);return 0;
}

这里的 p 是 x 地址的拷贝在传参里面,我们要改变什么,就要用它的指针。然后 * 解引用可以改变

代码3

void Func(int* ptr)
{ptr = (int*)malloc(sizeof(int));
}
int main()
{int* px = NULL;Func(px);free(px); // 加上也没用return 0;
}

这也是拷贝值,把 px 的值拷贝给 ptr,ptr 是空。但是我 malloc 了一块空间,让 ptr 指向这块空间。
px 拷贝给 ptr,ptr 的改变不会影响 px 。并且出了作用域 Func 销毁,malloc 的内存块还找不到了(内存泄漏),就算 free 也 free 不到

这里我们要改变的是 int* ,不是 int 。传 int* 不起作用。应该传 int**(二级指针)

代码4:改变 int* ,使用 int* 的地址,int**(二级指针)

void Func(int** pptr)
{*pptr = (int*)malloc(sizeof(int));
}
int main()
{int* px = NULL;Func(&px);free(px);return 0;
}

这里把 px 的地址传过去,pptr 指向 px 。malloc了一块空间,是让 *pptr 即 px 指向这块空间
Func 结束,栈帧销毁。但 px 还指向这块空间,free 可以 free 到。这里内存释放,值也拿回来了

二.链表

1.为什么学了顺序表还要学链表

顺序表是有很多缺陷的:
1)中间,头部 插入,删除数据,需要挪动数据,效率低下。你也不可能说在中间插入一块空间,没有这种概念,这本来就是一块连续的空间。
(2)空间不够需要扩容,拷贝数据,释放旧空间。会有不小的消耗
扩容有一定的效率消耗。原地扩还好,异地扩呢?
还可能会有一定的空间浪费。一次扩太少,会频繁扩;一次扩太多,浪费

能不能说,我用一点给一点呢?存一块数据,开一块空间
可以,但怎么管理呢?
顺序表里,开了整片空间,由于存放的数据是连续的,只需要记录这块空间最开始的地址。
现在要一块空间,去 malloc 。多次 malloc ,他们之间的地址不能保证相邻。

这时候,链表会用一个指针指向第一个内存块(节点 Node)。
为了通过第一个能找到第二个怎么办?上一个会存下一个的地址,上一个指向下一个。
什么时候结束?顺序表是 size 。链表里最后一个节点的指针存 NULL 即可

2.优点

不需要扩容。存一块,开一块。
可以从中间插入,不需要挪动数据。

顺序表,链表是互补,相辅相成的。很多情况是配合起来使用的

三.实现

上面的理解,链表是一个个的内存块,再由指针链接起来
先来定义它的结构:从语言的角度来说,凡是有多个数据,都要存到结构体里面
为方便替换成其他类型的数据,我们将类型统一重命名为 SLTDataType

1.链表的打印 —— 理解链表结构

SList.h

上一个节点要存下一个节点的地址,每个节点都是结构体类型,所以存结构体指针 next
链表要有个头指针 phead 指向第一个节点,判断结束只需要走到空 NULL 即可。
不能断言 phead 为空,空链表也可以打印

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印链表
void SLTPrint(SLTNode* phead);

SList.c

void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;//while (cur->next != NULL) 错误写法!!!//while(cur != NULL)while (cur){printf("%d->", cur->data);cur = cur->next; // 指向下一个位置// 不能写成 ++cur;}printf("NULL\n");
}

问:为什么不能写成 ++cur ?
答:链表地址不连续,++cur 不能保证它指向下一个位置。如果强行把地址弄成连续,不就成顺序表了吗?


怎么理解 cur = cur->next;
cur 是结构体指针,cur-> 就是访问结构体成员。next 是结构体成员,是下一个节点的地址
赋值操作是把下一个节点的地址给 cur 


为什么循环判断条件 cur->next != NULL 为错?
cur->next 是下一节点地址。走到尾就结束了,没有打印最后的数据

(2) 物理结构图

上面画的是逻辑结构图,是为方便理解,形象画出来的
物理结构图:实实在在数据在内存中的变化

2.链表的尾插 —— 入门

依然不能断言 phead 为空。为空(没有数据)依然可以尾插

顺序表尾插,先要判断空间够不够,不够扩容。      链表不用,永远有空间

第一步:搞个节点,并初始化。后面多次用到,分装成函数
第二步:找尾。  尾的特征:tail->next == NULL

// 搞节点,并初始化
SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return NULL;}// 初始化newnode->data = x;newnode->next = NULL;return newnode;
}void SLTPushBack(SLTNode* phead, SLTDataType x) // 思考这里对吗?
{SLTNode* newnode = BuySLTNode(x);// 找尾SLTNode* tail = phead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;
}

错误写法:tail != NULL

// 找尾
SLTNode* tail = phead;
while (tail != NULL)
{tail = tail->next;
}
tail = newnode;

从逻辑结构图角度,看似正确:

从物理结构图理解:




tail ,newnode 都是局部变量,出了作用域销毁

上一个节点没有存下一个节点的地址,链接失败



总结:

tail 是个局部变量。不应该赋值给 tail 。应该赋值给 tail 指向的结构体(存放下一个节点地址的)成员

不为空链表尾插的本质:原尾节点中要存新的尾节点的地址



正确代码物理图解:

// 找尾
SLTNode* tail = phead;
while (tail->next != NULL)
{tail = tail->next;
}
tail->next = newnode;




tail ,newnode 都是局部变量,出了作用域销毁

上一个节点存储下一个节点的地址,链接成功

(2) 空链表尾插

phead == NULL      tail = phead        让 phead 指向新节点即可

(2) 尾插整体代码 (思考对吗?)

SList.h

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印链表
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode* phead, SLTDataType x); // 思考这里对不对

SList.c 

void SLTPushBack(SLTNode* phead, SLTDataType x) // 对吗?
{SLTNode* newnode = BuySLTNode(x);if (phead == NULL){phead = newnode;}else{// 找尾SLTNode* tail = phead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

Test.c

void TestSList1()
{SLTNode* plist = NULL;SLTPushBack(plist, 1);SLTPushBack(plist, 2);SLTPushBack(plist, 3);SLTPushBack(plist, 4);SLTPrint(plist);
}

Bug

我们运行上面的代码:

看下图,phead 和 newnode 都是结构体指针类型的指针变量
phead = newnode 是赋值行为,其真正含义是让 phead 也指向 newnode 指向的新节点

函数结束,栈帧空间销毁。我们的目标是让 plist 指向新节点,但最后没有,造成了内存泄漏

改Bug

我们要改变 SLNode* plist ,传参里要传 SLNode* plist 的地址 ,用 SLNode** 接收

SList.h

typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印链表
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

SList.c

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);if (*pphead == NULL){*pphead = newnode;}else{// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}

pphead 存的是 plist 的指针。*pphead 就是 plist 。
函数结束,栈帧空间销毁。plist 指向了新节点

链表运行结果:

3.头插

盲猜头插要用二级指针,因为一定有一个情况是为空,为空肯定要用

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}

如果传的是 phead ,改变的就是 phead ,无法改变外边的 plist

这段代码同样可以解决空的情况

4.尾删

SList.c

void SLTPopBack(SLTNode** pphead) // 这么写对吗?
{assert(pphead);assert(*pphead);// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}free(tail);tail = NULL;
}

Test.c

void TestSList1()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}

Bug

     碰上这种情况多半是野指针,调试看看

尾就是1这个节点,2这个节点存着他的地址

直接把 tail 指向的尾节点 free 了,前一个节点的 next 就是野指针了。指向已经被释放的空间的指针是野指针

这里把 tail 置空,不会把前一个节点的 next 置空
前一个节点是结构体,想改变结构体的内容要用结构体指针


修改1

void SLTPopBack(SLTNode** pphead)
{assert(pphead);assert(*pphead);SLTNode* prev = NULL;// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;
}

修改2:找的是倒数第2个

void SLTPopBack(SLTNode** pphead)
{assert(pphead);assert(*pphead);// 找尾SLTNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;
}

如果链表删到只剩1个元素,还删。
如果链表本身为空

void TestSList1()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);
}

              

下面用红圈圈起来的是两组代码在只剩1个的情况下,分别有误的地方

修改:只有1个节点,直接 free,plist 置空。不用找尾节点
所以尾删如果用一级指针接收,phead 是 plist 的拷贝,对 phead 置空的改变不影响 plist,达不到置空 plist 的目的,plist 会变成野指针

void SLTPopBack(SLTNode** pphead)
{//暴力检查assert(pphead);assert(*pphead);//温柔检查/*if (*pphead == NULL)return;*/if ((*pphead)->next == NULL) // 只有1个节点{free(*pphead);*pphead = NULL;}else // 多个节点{/*SLTNode* prev = NULL;// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;*/// 找尾SLTNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}

5.头删

不需要单独处理只有1个节点的情况

void SLTPopFront(SLTNode** pphead)
{assert(pphead);assert(*pphead);SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}

6.查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

返回的是对应节点的指针,可以用 Find 实现修改

void TestSList2()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);// 值为2的节点 *2SLTNode* ret = SLTFind(plist, 2);ret->data *= 2;SLTPrint(plist);
}

Find 主要是与下面的功能相配合

7. Find 查找的功能

我们这里不传下标,传结构体指针,与 C++ 贴合

(1)pos 之前插入

为啥不是在 pos 位置插入? 是把 pos 及以后的数据往后移,所以逻辑上说是之前插入

单链表不适合 pos 之前插入,只适合在后面插入因为要找到 pos 前一个节点的地址,只能从头找

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{if (pos == *pphead){SLTPushFront(pphead, x);}else{// 找到 pos 的前一个位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySLTNode(x);newnode->next = pos;prev->next = newnode;}
}

如果 pos 不是链表里的指针,while 循环停不下来,最终出现空指针
这种情况怎么办 (甚至 pos 就是 NULL)?   
说明传错了,断言,起码可以排除 NULL

(2)pos 位置删除

这里 *pphead 可以不断言,pos 间接断言了
pos 不为空,有节点,一定不为空链表

pos 位删除,要找到前一个位置。pos 是头,就是头删,先处理这个特殊情况

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);assert(*pphead);if (*pphead == pos){SLTPopFront(pphead);}else{// 找到 pos 的前一个位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);// pos = NULL;}
}

pos = NULL 没用,形参的修改不改变实参。要不要传二级指针呢?不。
为保持和其他的一致性,通常由用的人考虑置空

void TestSList4()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPrint(plist);SLTNode* ret = SLTFind(plist, 2);SLTErase(&plist, ret);ret = NULL;SLTPrint(plist);
}

(3)pos 之后插入

错误写法:会造成死循环

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);pos->next = newnode;newnode->next = pos->next;
}

正确写法:先改后面

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}

(4)pos位置后面删除

法1:pos->next = pos->next->next;  这里从右往左赋值     橙圈的内容丢了,所以要引入del

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

法2:好理解

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

四.思维提升

单链表给了 pos 没给头指针

(1)插入

(2)删除

没有前一个位置,就删后一个。先换值,后删。但是不能删尾

五.总结

1.传什么?

我们刚开始拿到链表,plist 是 NULL 。要插入新节点,要让 plist 指向新节点,会改变 plist ,所以要传指针的地址。
删除时,总会删到空,这时要将 plist 置为 NULL ,也改变 plist ,所以也传指针的地址

如果不需要修改头指针的链接,就传一级指针

2.要不要断言?

断言可以排出明显的错误,避免调试耗时。一定不能为空,就断言

(1)打印、查找

问:是否要 assert 指针 phead 为空? (一级指针)
答:不要。空的 (没有数据) 的链表,顺序表都可以打印、查找。链表为空时,phead == NULL,断言直接终止程序不合适。

顺序表,链表结构不一样,不能一概而论。
phead 是指向第一个存有数据的节点,链表为空时,phead == NULL


顺序表的打印


void SLPrint(SL* ps)
{assert(ps);for (int i = 0; i < ps->size; ++i){printf("%d ", ps->a[i]);}printf("\n");
}

指针 ps 指向结构体 SL ,顺序表的数据不是存储在结构体上。而是存储在结构体里的一个指针 a 指向的空间。即使顺序表里没有数据,ps 指向的结构体也是必须要有的。ps->a 是否为空也不重要,到底有没有数据,取决于 ps->size 是否为 0
所以对顺序表而言,指针就不能为空

总结:不要看到指针上来就断言

(2)pphead

要,pphead 不能为空。为什么?
pphead 是 plist 的地址。plist 是指针变量,值有可能是空,地址一定不为空

(3)*pphead

*pphead 就是 plist ,是看是否为空 (二级指针)

要不要断言 *pphead 取决于函数是否包容空链表的情况

先 assert ( pphead )   后 assert ( *pphead )  如果反了,先 * ,再检查,有啥用?

空链表能插入,不断言;不能删,要断言。

六.整体代码

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;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); // pos之前插入
void SLTErase(SLTNode** pphead, SLTNode* pos); // pos位置删除void SLTInsertAfter(SLTNode* pos, SLTDataType x); // pos之后插入
void SLTEraseAfter(SLTNode* pos); // pos位置后面删除

SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;//while (cur->next != NULL) 错误写法!!!//while(cur != NULL)while (cur){printf("%d->", cur->data);cur = cur->next;//cur++; 错误写法!!!}printf("NULL\n");
}// 搞新节点,并初始化
SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return NULL;}// 初始化newnode->data = x;newnode->next = NULL;return newnode;
}void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);if (*pphead == NULL){*pphead = newnode;}else{// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}
}void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPopBack(SLTNode** pphead)
{//暴力检查assert(pphead);assert(*pphead);//温柔检查/*if (*pphead == NULL)return;*/if ((*pphead)->next == NULL) // 只有1个节点{free(*pphead);*pphead = NULL;}else // 多个节点{/*SLTNode* prev = NULL;// 找尾SLTNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;*/// 找尾SLTNode* tail = *pphead;while (tail->next->next != NULL){tail = tail->next;}free(tail->next);tail->next = NULL;}
}void SLTPopFront(SLTNode** pphead)
{assert(pphead);assert(*pphead);SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPushFront(pphead, x);}else{// 找到 pos 的前一个位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySLTNode(x);newnode->next = pos;prev->next = newnode;}
}void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);assert(*pphead);if (*pphead == pos){SLTPopFront(pphead);}else{// 找到 pos 的前一个位置SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);// pos = NULL;}
}void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);//SLTNode* del = pos->next;//pos->next = pos->next->next;//free(del);//del = NULL;SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章

相关文章:

【数据结构 · 初阶】- 单链表

目录 一.相关指针知识点 二.链表 1.为什么学了顺序表还要学链表 2.优点 三.实现 1.链表的打印 —— 理解链表结构 (2) 物理结构图 2.链表的尾插 —— 入门 错误写法&#xff1a;tail ! NULL 总结&#xff1a; 正确代码物理图解&#xff1a; (2) 尾插整体代码 (思考…...

【前端】【React】useCallback的作用与使用场景总结

一、useCallback 的作用与使用场景总结 useCallback 是 React 提供的一个 Hook&#xff0c;用于缓存函数的引用&#xff0c;避免因为组件重新渲染而导致函数地址发生变化。它返回一个记忆&#xff08;memoized&#xff09;后的回调函数&#xff0c;只有当依赖项发生变化时才会…...

什么是 React Router?如何使用?

React Router 详解 1. 引言 在现代 web 开发中&#xff0c;单页面应用&#xff08;SPA&#xff09;越来越流行&#xff0c;React 是构建 SPA 的热门库之一。React Router 是一个标准的路由库&#xff0c;专为 React 应用设计&#xff0c;允许开发者在应用中实现动态路由和 UR…...

jQuery 插件

在现代Web开发中&#xff0c;jQuery以其简洁的语法和强大的功能成为了前端开发者们喜爱的工具之一。为了进一步扩展jQuery的功能&#xff0c;社区贡献了大量的插件&#xff0c;使得开发者能够更加高效地实现各种复杂的交互效果和功能。本文将介绍什么是jQuery插件、如何编写自己…...

未来杭州:科技与茶香交织的生态诗篇

故事背景 故事发生在中国浙江杭州&#xff0c;融合智能科技与传统茶文化&#xff0c;描绘未来城市中人与自然共生的诗意画卷。通过六个充满未来感的生态场景&#xff0c;展现科技如何重塑龙井茶园、古运河、西湖等经典地标&#xff0c;编织出一曲科技与人文共鸣的生态交响诗。 …...

微服务多模块构建feign项目过程与一些报错(2025详细版)

目录 1.eureka-server的注意事项 2.eureka-feign的注意事项 3.多模块构建feign项目过程 3.1创建父项目 3.2创建子项目eureka-server 3.3创建子项目eureka-provider 3.4创建子项目eureka-feign 3.5运行 给个点赞谢谢 1.eureka-server的注意事项 eureka-server的yml文件…...

AWS云安全实践:基于CISA关键措施的检测与实施指南

1. 引言 随着越来越多的组织将其基础设施迁移到云端,确保AWS环境的安全变得至关重要。美国网络安全与基础设施安全局(CISA)提出的关键措施为我们提供了一个可靠的框架。本文将探讨如何在AWS环境中实施这些措施,特别关注检测类别和可实施的关键措施。 2. AWS云安全中的CISA关…...

超越肉眼所见:一种利用视网膜光学相干断层扫描血管成像(OCTA)图像进行早期痴呆检测的关联模型|文献速递-深度学习医疗AI最新文献

Title 题目 Beyond the eye: A relational model for early dementia detection using retinal OCTA images 超越肉眼所见&#xff1a;一种利用视网膜光学相干断层扫描血管成像&#xff08;OCTA&#xff09;图像进行早期痴呆检测的关联模型 01 文献速递介绍 阿尔茨海默病&…...

终端使用python出现segmentation fault (core dumped)的一种解法

有时候在终端输入python&#xff0c;希望交互式运行命令或者通过pdb调试&#xff0c;但是出现如下错误&#xff1a; [1] 7476 segmentation fault (core dumped) python 但是单独运行python xxx.py 或者 python -c "xxx" 又是可以的&#xff01; 经过与AI大模…...

分布式id生成算法(雪花算法 VS 步长id生成)

分布式ID生成方案详解:雪花算法 vs 步长ID 一、核心需求 全局唯一性:集群中绝不重复有序性:有利于数据库索引性能高可用:每秒至少生成数万ID低延迟:生成耗时<1ms二、雪花算法(Snowflake) 1. 数据结构(64位) 0 | 0000000000 0000000000 0000000000 0000000000 0 |…...

h265为什么没有大范围应用

H.265&#xff08;也称为 HEVC&#xff0c;High Efficiency Video Coding&#xff09;是一种视频压缩标准&#xff0c;旨在提供比 H.264&#xff08;AVC&#xff09;更高的压缩效率。然而&#xff0c;尽管 H.265 在技术上具有许多优势&#xff0c;但其大范围应用受到了以下几个…...

Linux图形化界面

一、Linux图形化界面 桌面对于Linux系统来说&#xff0c;只是一个应用程序&#xff0c;所以是可以移植的。 Linaro公司针对于半导体厂商推出的芯片&#xff0c;开发了ARM开发工具、Linux内核以及Linux发行版&#xff08;包括Android及Ubuntu&#xff09;。 所以无需自己移植&am…...

LangChain-检索系统 (Retrieval)

检索系统 (Retrieval) 检索系统是LangChain的核心组件之一&#xff0c;它提供了从各种数据源获取相关信息的能力&#xff0c;是构建知识增强型应用的基础。本文档详细介绍LangChain检索系统的组件、工作原理和最佳实践。 概述 检索系统解决了大型语言模型知识有限和过时的问…...

【Linux】进程概念

目录 一、进程概念 &#xff08;一&#xff09;什么是进程 &#xff08;二&#xff09;描述进程-PCB 二、fork创建进程 &#xff08;一&#xff09;bash概念 &#xff08;二&#xff09;如何创建子进程 一、进程概念 &#xff08;一&#xff09;什么是进程 结论&#x…...

【JavaScript】十七、事件委托(冒泡阶段的利用)

文章目录 1、事件委托2、tab栏切换案例&#xff1a;使用事件委托优化3、阻止元素默认行为 1、事件委托 以送快递为例&#xff0c;某班有20名同学&#xff0c;每人有一个快递&#xff0c;快递员可以一个个送&#xff0c;需要送40次&#xff0c;很繁琐&#xff0c;换个方式&…...

Android InstalldNativeService::getAppSize源码分析

InstalldNativeService::getAppSize 是 Android 系统中用于计算应用程序存储空间的核心方法&#xff0c;其逻辑可分为以下几个关键模块&#xff08;结合代码和上下文分析&#xff09;&#xff1a; 一、基础校验与初始化 ​​1. 权限校验​​ 通过 ENFORCE_UID(AID_SYSTEM) 确…...

微信小程序跳

/** * 画布文本换行绘制 * canvasContext 画布实例 * text 要写入的文本 * x 初始x轴位置 * y 初始y轴位置 * ySpacing 换行后&#xff0c;每行直接的间隔 * maxWidth 此文本写入画布的最大宽度&#xff0c;超过此宽度就换行 * color 文本颜色 * size 文本字体大小 * align 文本…...

openlayers入门01 -- 环境配置和初始化地图

openlayers入门 openlayers开发环境配置 1.下载VSCode 官网地址&#xff1a;https://code.visualstudio.com/ 点击Download for Windows 2.安装汉化插件和openlayers插件 搜索chinese&#xff0c;下载Chinese (Simplified) (简体中文) Language Pack 更改语言并重启 搜…...

window实现多jdk共存、便捷切换

背景 如今大模型技术流行&#xff0c;想要跟上发展就也得学一学&#xff0c;比如Spring-AI等框架&#xff0c;但这些AI相关的框架对jdk版本都有要求&#xff0c;一般都要不低于17。 而在企业开发中&#xff0c;很多时候还使用着jdk8&#xff0c;如何重新安装17&#xff0c;则需…...

常见的 set 选项与空变量检查

在编写 Bash 脚本时&#xff0c;使用 set 命令中的一些选项可以帮助我们在脚本执行过程中及时捕获错误和潜在问题&#xff0c;避免脚本在出错时继续执行&#xff0c;提高脚本的可靠性和健壮性。 set -e&#xff1a;遇到错误就停 set -e 的作用是&#xff1a;一旦脚本中的某个…...

JS 创建对象方法

创建对象的三种方法 3 通过构造函数 自定义构造函数 构造函数 快速定义多个对象 自定义构造函数...

IMX6ULL2025年最新部署方案:最新的UBootLinux和Rootfs部署正点原子Alpha开发板指南

正点原子Alpha IMX6ULL开发板2025年最新部署方案&#xff1a;基于Ubuntu24.04平台开发&#xff0c;部署最新的UBoot/Linux和BusyBox Rootfs部署指南 前言 ​ 笔者实在绷不住比较旧的方案了&#xff0c;广义流行的方案是使用2016年发布的Uboot来引导Linux4.1.15&#xff0c;配…...

threeJs实现裸眼3D小狗

一、实现效果 使用threeJs实现裸眼3D小狗&#xff0c;效果如下&#xff0c;其实如果将小狗换成建模小狗&#xff0c;效果更好&#xff0c;这个是模拟了一只小狗。 二、实现代码 代码如下&#xff1a; <!DOCTYPE html> <html> <head><title>星空小狗…...

ZYNQ笔记(二):MIO 、EMIO

版本&#xff1a;Vivado2020.2&#xff08;Vitis&#xff09; 任务&#xff1a;使用GPIO MIO 和 EMIO 实现按键 KEY 控制 LED&#xff08; 两个PL端LED、两个PS端KEY&#xff09; 目录 一、MIO 、EMIO 介绍 二、硬件设计 三、软件设计 四、效果 一、MIO 、EMIO 介绍 …...

Cygwin编译安装Acise

本文记录Windows下使用Cygwin编译安装Acise的流程。 零、环境 操作系统Windows11Visual Studio CodeVisual Studio Code 1.92.0Cygwin 一、工具及依赖 1.1 Visual Studio Code 下载并安装Visual Studio Code, 同时安装以下插件&#xff0c; Task Explorer Output Colorizer …...

ubuntu22.04 安装-ODBC驱动-SQLserver

1.ISO下载 中科大 Index of /ubuntu-releases/http://mirrors.ustc.edu.cn/ubuntu-releases/ 2.VMware Workstation创建虚拟机 3.更换清华源或者中科大的源 Ubuntu 24.04更换清华源_哔哩哔哩_bilibili 中科大镜像源&#xff1a; Ubuntu - USTC Mirror Help 清华源 ubuntu | 镜…...

[数据结构]Trie字典树

GPT的介绍 &#x1f9e0; 一句话总结&#xff1a; 字典树是一种专门用来存很多字符串的“超级前缀树”&#xff0c;查找某个字符串或前缀的时候&#xff0c;特别快&#xff01; ✍️ 举个生活例子&#xff08;类比&#xff09;&#xff1a; 你想做一个词典&#xff08;Dictio…...

【网络安全】Linux 命令大全

未经许可,不得转载。 文章目录 前言正文文件管理文档编辑文件传输磁盘管理磁盘维护网络通讯系统管理系统设置备份压缩设备管理其它命令前言 在网络安全工作中,熟练掌握 Linux 系统中的常用命令对于日常运维、日志分析和安全排查等任务至关重要。 以下是常用命令的整理汇总,…...

Xcode警报“Ignoring duplicate libraries: ‘-lc++’” 警报

目录 前言 一、问题出现的原因 二、解决步骤 1. 检查构建设置中的链接器标志 2. 检查依赖中的重复库 3. 清除派生数据&#xff1a; 4. 检查冲突的 C 库 5. 更新 CocoaPods&#xff1a; 前言 有时候我们会看到Xcode警报&#xff1a;Ignoring duplicate libraries: ‘-lc…...

WebStorm中使用live-server插件

文章目录 1. 前提条件1.1 已安装Node1.1.1 淘宝的镜像1.2 安装live-server1.3 安装WebStorm2. Windows配置hosts3. WebStorm配置live-server3.1 WebStorm创建3.2 启动 live-server1. 前提条件 1.1 已安装Node Windows PowerShell 版权所有(C) Microsoft Corporation。保留所…...

Python 中使用单例模式

有这么一种场景&#xff0c;Web服务中有一个全局资源池&#xff0c;在需要使用的地方就自然而言引用该全局资源池即可&#xff0c;此时可以将该资源池以单例模式实现。随后&#xff0c;需要为某一特殊业务场景专门准备一个全局资源池&#xff0c;于是额外复制一份代码新建了一个…...

【基于LangChain的千问大模型工具调用】 Function CallingTool Calling简易示例

目录 示例代码 运行结果 功能&#xff1a;根据用户输入自动选择数学计算/天气查询工具处理 ​​"用户输入 → 意图分析 → 工具调用 → 结果返回"​​ 库名解析&#xff1a;【langchain库名解析】-CSDN博客 示例代码 # 模块1&#xff1a;依赖导入 from langc…...

银河麒麟服务器操作系统 V10 SP1 中玩转firewalld防火墙

Hey小伙伴们~&#x1f44b; 今天来聊聊怎么在银河麒麟服务器操作系统 V10 SP1 中玩转firewalld防火墙&#xff01;&#x1f6e1;️ 这个强大的工具能帮你动态管理防火墙规则&#xff0c;还支持区域概念&#xff0c;让你的网络流量控制更灵活&#xff01;&#x1f389; &#x…...

信创服务器网络更稳了!链路聚合Bonding实战

原文链接&#xff1a;信创服务器网络更稳了&#xff01;链路聚合Bonding实战 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇信创服务器操作系统上链路聚合&#xff08;Bonding&#xff09;配置实战的文章。链路聚合可以让两块网卡协同工作&#xff0c;提高服务器网…...

沐渥科技详解氮气柜操作指南

氮气柜是一种通过持续注入高纯度氮气&#xff0c;维持柜内惰性气体环境的设备&#xff0c;用于存储半导体晶圆或其他敏感元件&#xff0c;防止氧化、吸湿和污染。氮气柜操作指南是怎样的&#xff1f;下面沐渥小编给大家介绍一下。 一、操作前准备 &#xff08;1&#xff09;安全…...

Netty之ChannelOutboundBuffer详解与实战

深入理解Netty的高低水位线机制及其应用实践 在高性能网络编程中&#xff0c;Netty作为一个广泛使用的异步事件驱动的Java框架&#xff0c;其高效的流量控制机制对于系统的稳定性和性能至关重要。本文将深入探讨Netty中的高低水位线&#xff08;High/Low Water Mark&#xff0…...

学习Python的优势体现在哪些方面?

文章目录 前言易于学习和使用应用领域广泛丰富的开源库和社区支持跨平台兼容性职业发展前景好 前言 学习 Python 具有多方面的优势&#xff0c;这使得它成为当今最受欢迎的编程语言之一&#xff0c;以下为你详细介绍。 易于学习和使用 语法简洁易懂&#xff1a;Python 的语法…...

每天五分钟玩转深度学习PyTorch:搭建LSTM算法模型完成词性标注

本文重点 本文通过LSTM算法模型来解决一个实际问题,也就是如何使用LSTM完成词性预测,下面我们搭建两个模型,一个是单词级别的,另外一个是字母级别的,大概的步骤就是,字母级别的LSTM将每个单词的字母作为输入,然后取最后一个时刻作为整个单词的表示,然后将文本中所有的…...

ANP协议深度解析:智能体网络协议的演进与革新

引言 随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;多智能体系统&#xff08;Multi-Agent Systems, MAS&#xff09;逐渐成为解决复杂问题的核心范式。然而&#xff0c;智能体之间的高效协作一直面临通信标准不统一、资源分配冲突、动态环境适应能力不足等…...

如何降低论文的AIGC检测率,减少“AI味”

要降低论文的AIGC&#xff08;AI生成内容&#xff09;检测率&#xff0c;减少“AI味”&#xff0c;关键在于让论文更自然、个性化&#xff0c;并符合学术规范。以下是具体方法&#xff1a; 1. 避免直接复制AI生成内容 ❌ 错误做法&#xff1a;直接使用DeepSeek等工具生成的整段…...

安装npm install element-plus --save报错

今天安装npm install element-plus --save报的错&#xff0c;豆包提示说是项目里使用的 eslint 版本是 7.32.0&#xff0c;而 vue/eslint-config-standard9.0.1 要求的 eslint 版本是 ^9.10.0。总之就是说版本不兼容 报错&#xff1a; While resolving: vue3_project0.1.0 np…...

Python代码缩进统一规范

一、Python缩进的重要性:逻辑与可读性的桥梁 1. 语法规则的核心 Python与其他编程语言显著不同之处在于,它使用缩进来表示代码块的层次结构。不像C、Java等语言依靠大括号{}来明确函数体、循环体和条件语句的范围,Python完全依赖缩进来界定这些逻辑单元。例如,在一个if条…...

蓝桥杯基础数论入门

一.试除法 首先我们要了解&#xff0c;所有大于1的自然数都能进行质因数分解。试除法作用如下&#xff1a; ​质数判断 试除法通过验证一个数是否能被小于它的数&#xff08;一般是用2到用根号x&#xff09;整除来判断其是否为质数。根据定义&#xff0c;质数只能被1和自身整除…...

一个插件,免费使用所有顶级大模型(Deepseek,Gpt,Grok,Gemini)

DeepSider是一款集成于浏览器侧边栏的AI对话工具&#xff0c;可免费使用所有顶级大模型 包括GPT-4o&#xff0c;Grok3,Claude 3.5 Sonnet,Claude 3.7,Gemini 2.0&#xff0c;Deepseek R1满血版等 以极简交互与超快的响应速度&#xff0c;完成AI搜索、实时问答、内容创作、翻译、…...

nginx入门,部署静态资源,反向代理,负载均衡使用

Nginx在linux上部署静态资源 概念介绍 Nginx可以作为静态web服务器来部署静态资源。这里所说的静态资源是指在服务端真实存在&#xff0c;并且能够直接展示的一些文件&#xff0c;比如常见的html页面、css文件、js文件、图片、视频等资源。 相对于Tomcat&#xff0c;Nginx处理…...

微信小程序中使用ECharts 并且动态设置数据

项目下载地址 GitHub 地址 https://github.com/ecomfe/echarts-for-weixin 将当前文件夹里的内容拷贝到项目中 目录&#xff1a; json: {"usingComponents": {"ec-canvas": "../components/ec-canvas/ec-canvas"} }wxml&#xff1a; <ec…...

【愚公系列】《高效使用DeepSeek》064-跨文化谈判

🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...

云原生环境Jvm内存占用持续高位问题排查

生产内存占用过高问题排查 1、问题描述 一个tomcat应用&#xff0c;部署在云原生集群上&#xff0c;云原生监控显示内存占用率持续偏高&#xff0c;最大占用率、平均占用率都在90%多&#xff0c;但是倒没有OOM什么的。 2、问题复现 凭借对此应用的了解&#xff0c;很快就在测…...

linux下io操作详细解析

在 Linux 系统下&#xff0c;IO&#xff08;输入/输出&#xff09;操作是程序与外部设备&#xff08;如文件、网络等&#xff09;交互的重要方式。Linux 提供了丰富的系统调用和库函数来支持各种 IO 操作。以下是对 Linux 下 IO 操作的详细解析&#xff0c;包括文件 IO、网络 I…...

【在校课堂笔记】Python 第 9 节课 总结

- 第 91 篇 - Date: 2025 - 04 - 10 Author: 郑龙浩/仟墨 【Python 在校课堂笔记】 文章目录 南山-第 9 节课字符串的各种操作一 string二 string 的索引1 索引介绍2 示例 - 索引使用 三 切片1 **结构**2 示例 四 回文数 – 切片的应用五 对手机号进行脱敏处理六 注意七 读取字…...