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

单链表——C语言实现

目录

一.相关指针知识点

二.链表

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;
}

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

相关文章:

单链表——C语言实现

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

PostgreSQL插件生态全景解析:赋能数据库的无限可能

PostgreSQL以其开放的扩展生态闻名于世&#xff0c;其插件机制如同瑞士军刀般灵活&#xff0c;能够在不修改核心代码的前提下实现功能无限延伸。本文将基于多年内核开发经验&#xff0c;深度剖析PostgreSQL插件生态体系&#xff0c;为架构师与开发者提供全景式技术选型参考。 一…...

minio提供nfs服务

minio提供nfs服务 挂载minio为本地目录开机自动挂载使用supervisor实现开机自动挂载服务单元实现开机自动挂载minio为本地目录---失败 调试 挂载minio为本地目录 使用 Minio 作为后端存储&#xff0c;并通过 NFS 为客户端提供访问&#xff0c;那么你需要一个中间层来将 Minio …...

QML中的信号与槽机制

QML 中的信号与槽机制是 Qt 框架的核心特性之一&#xff0c;它提供了一种对象间通信的强大方式。与 C 中的信号槽类似&#xff0c;但语法更加简洁。 基本概念 1. 信号 (Signal) 当某个特定事件发生时由对象自动发出的通知 例如&#xff1a;按钮被点击时发出的 clicked 信号 …...

使用 Ktor 构建现代 Android 应用的后端服务

使用 Ktor 构建现代 Android 应用的后端服务 前言 在移动互联网时代&#xff0c;Android 应用对后端服务的实时性与性能要求越来越高&#xff0c;而传统的后端框架在一些场景中存在复杂度高、扩展性不足等问题。Ktor 作为 JetBrains 推出的异步 Web 框架&#xff0c;充分利用…...

leetcode_454. 四数相加 II_java

454. 四数相加 IIhttps://leetcode.cn/problems/4sum-ii/ 1、题目 给你四个整数数组 nums1、nums2、nums3 和 nums4 &#xff0c;数组长度都是 n &#xff0c;请你计算有多少个元组 (i, j, k, l) 能满足&#xff1a; 0 < i, j, k, l < nnums1[i] nums2[j] nums3[k] …...

类名与协议名相同,开发中应该避免吗?

在 Objective-C 开发中&#xff0c;协议与实现类之间的命名关系非常重要。虽然语言允许协议名和类名相同&#xff0c;但从可读性和维护性等角度出发&#xff0c;这种做法并不推荐。本文通过一个典型示例展开分析&#xff0c;并提供更合理的命名建议。 一、示例 在某项目中&…...

环信鸿蒙版 UIKit 快速上手指南

环信鸿蒙版 UIKit 是专为 HarmonyOS 开发者设计的 IM UI 组件库&#xff0c;基于环信 IM SDK 开发&#xff0c;可帮助开发者快速集成即时通讯功能。 环信UIKit 的特点 ArkUI 声明式开发范式&#xff1a;采用高效简洁的声明式开发方式状态管理 V2&#xff1a;支持深度观测和精…...

编译freecad

git clone --recurse-submodules https://github.com/FreeCAD/FreeCAD.git freecad-source 手动安装 vscode 扩展安装cmake tool cmake ../ 缺什么装什么 Third Party Libraries - FreeCAD Documentation sudo apt install qt6-base-dev sudo apt install libyaml-cpp-dev …...

安卓Kotlin接入高德定位和地图SDK

前言&#xff1a;高德的定位sdk可以获取设备当前的详细信息&#xff0c;如经纬度&#xff0c;具体地址&#xff08;省->街道&#xff09;等&#xff0c; 本文主要使用的是定位sdk和地图sdk中的poi搜索功能&#xff08;以当前位置半径多少米内的关键词搜索&#xff09; 目录…...

JavaScript浅拷贝与深拷贝

目录 浅拷贝&#xff08;Shallow Copy&#xff09; 一、浅拷贝的定义 二、直接赋值 vs 浅拷贝 1. 直接赋值 2. 浅拷贝 三、数组的浅拷贝方法 1. slice() 2. concat() 3. 扩展运算符&#xff08;...&#xff09; 四、对象的浅拷贝方法 1. Object.assign() 2. 扩展运…...

智能生态之城-广东茂名

故事摘要 在中国广东茂名的未来社区&#xff0c;晨光中&#xff0c;垂直果园里发光的荔枝与智能无人机的早餐派送唤醒了城市的生活。在海底透明隧道的图书馆里&#xff0c;孩子们通过声控设备与虚拟生物互动。面对暴雨来临时&#xff0c;市民们积极参与到荔枝蜜饯制作和雨季造林…...

【Android】Android Activity 横屏设置详解及常见异常问题解决方法汇总

在 Android 开发中&#xff0c;我们经常需要控制 Activity 的屏幕方向&#xff0c;例如视频播放、游戏、VR/AR 应用等场景通常希望默认横屏显示。本文将讲解如何通过 Manifest 配置 和 Java/Kotlin 代码 设置横屏显示&#xff0c;并分析常见设置无效的原因与解决方法。 一、通过…...

Android 存储路径

​​一、内部存储路径&#xff08;Internal Storage&#xff09;​​ ​​stats.codeSize&#xff08;内部代码大小&#xff09;​​ ​​路径​​&#xff1a;/data/app/com.example.test-{随机后缀}/base.apk ​​说明​​&#xff1a;APK 安装路径&#xff0c;包含应用代码…...

【12】数据结构之基于线性表的排序算法

目录标题 插入排序直接插入排序折半插入排序希尔排序 交换排序冒泡排序快速排序 归并排序时间复杂度对比最好情况平均情况最坏情况 空间复杂度对比 插入排序 基本思想&#xff1a;将一个元素插入到一个有序序列中&#xff0c;继而得到一个有序的元素个数加一的新序列. 直接插…...

解决RecyclerView在调用smoothScrollToPosition后最后一个item底部超出屏幕的问题

要解决RecyclerView在调用smoothScrollToPosition后最后一个item底部超出屏幕的问题&#xff0c;可以使用自定义的LinearSmoothScroller&#xff0c;使其底部对齐屏幕。步骤如下&#xff1a; 创建自定义的SmoothScroller类&#xff1a; 继承LinearSmoothScroller并重写getVerti…...

数字世界的免疫系统:恶意流量检测如何守护网络安全

在2023年全球网络安全威胁报告中,某跨国电商平台每秒拦截的恶意请求峰值达到217万次,这个数字背后是无数黑客精心设计的自动化攻击脚本。恶意流量如同数字世界的埃博拉病毒,正在以指数级速度进化,传统安全防线频频失守。这场没有硝烟的战争中,恶意流量检测技术已成为守护网…...

十分钟机器学习之--------------线性回归

线性回归&#xff08;linear regression&#xff09;是一种基于数学模型的算法&#xff0c;首先假设数据集与标签之间存在线性关系&#xff0c;然后简历线性模型求解参数。在实际生活中&#xff0c;线性回归算法因为其简单容易计算&#xff0c;在统计学经济学等领域都有广泛的应…...

常用 Excel VBA 技巧,简单好学易上手

在日常办公中&#xff0c;我们常常会遇到各种繁琐的数据处理任务&#xff0c;而 Excel VBA&#xff08;Visual Basic for Applications&#xff09;作为一款强大的自动化工具&#xff0c;能够帮助我们轻松应对这些挑战。本文将介绍一些常用且简单好学的 Excel VBA 技巧&#xf…...

第7篇:Linux程序访问控制FPGA端LEDR<五>

Q&#xff1a;如何设计.c程序代码实现FPGA端外设LEDR流水灯&#xff1f; A&#xff1a;在DE1-SoC开发板上实现的流水灯效果&#xff1a;一次只点亮一个红色LED&#xff0c;初始状态为向左移动直至点亮LEDR9&#xff0c;然后改变移动的方向为向右直至点亮LEDR0&#xff0c;以此…...

PyTorch 深度学习实战(35):图生成模型与分子设计

在上一篇文章中&#xff0c;我们探讨了强化学习在机器人控制中的应用。本文将深入介绍图生成模型及其在分子设计领域的应用&#xff0c;这是一个结合深度学习与化学的交叉领域。我们将使用PyTorch Geometric实现基于图神经网络的分子生成模型&#xff0c;并在ZINC250k数据集上进…...

免费送源码:Java+ssm+HTML 三分糖——甜品店网站设计与实现 计算机毕业设计原创定制

录 摘要 1 1 绪论 3 1.1 研究背景 3 1.2 研究意义 3 1.3论文结构与章节安排 3 2系统分析 4 2.1 可行性分析 4 2.2 系统流程分析 4 2.2.1 登录流程 4 2.2.2数据删除流程 5 2.3 系统功能分析 5 2.3.1功能性分析 6 2.3.2 非功能性分析 7 2.4 系统用例分析 7 2.5本章…...

JVM 调试与内存优化实战详解

&#x1f31f; JVM 调试与内存优化实战详解 &#x1f31f; 前言一、JVM 内存模型概览二、常见内存问题与诊断思路三、核心调试工具与命令详解四、实战案例一&#xff1a;频繁 Full GC 深度排查与优化1. &#x1f575;️ 问题现象2. &#x1f52c; 排查流程3. ✅ 优化方案 五、实…...

Linux上通过Docker部署Zabbix6.2监控平台

文章目录 前言Zabbix概述zabbix特性 一、Zabbix架构二、主节点部署容器2.1 部署Docker 三、主节点部署数据库3.1 创建存储卷3.2 查看存储卷3.3 删除存储卷3.4 运行容器MySQL8 四、主节点部署Zabbix4.1 安装zabbix-java-gateway4.2 运行zabbix-server4.3 安装zabbix-web 五、配置…...

Grok3 API 已经免费开放了,附上免费使用 Grok3 API的教程

Grok-3是什么 Grok-3 是 xAI&#xff08;由 Elon Musk 创立的 AI 公司&#xff09;开发的最新大语言模型&#xff08;LLM&#xff09;&#xff0c;属于 Grok 系列模型的第三代&#xff0c;旨在与 OpenAI 的 GPT-4、Anthropic 的 Claude 3 和 Google 的 Gemini 1.5 等顶尖 AI 竞…...

2025年4月9日-华为暑期实习-第三题-300分

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 03. 矩阵螺旋排序 问题描述 卢小姐是一家艺术展览馆的策展人,她正在为一个数字艺术展览设计一个特殊的展示方案。展览厅的墙面是一个 N N N \times N...

【Web API系列】WebSocketStream API 深度实践:构建高吞吐量实时应用的流式通信方案

前言 在当今的 Web 开发领域&#xff0c;实时通信已成为许多应用的核心需求。无论是即时聊天、实时数据仪表盘&#xff0c;还是在线游戏和金融交易系统&#xff0c;都需要高效的双向数据传输能力。传统的 WebSocket API 为此提供了基础支持&#xff0c;但在处理大规模数据流、…...

WEB攻防-Java安全JNDIRMILDAP五大不安全组件RCE执行不出网不回显

目录 1. RCE执行-5大类函数调用 1.1 Runtime方式 1.2 Groovy执行命令 1.3 脚本引擎代码注入 1.4 ProcessImpl 1.5 ProcessBuilder 2. JNDI注入(RCE)-RMI&LDAP&高版本 2.1 RMI服务中的JNDI注入场景 2.2 LDAP服务中的JNDI注入场景 攻击路径示例&#…...

在android实现Google的web登录

前言 由于业务上的需要,想要在android端实现Google登录。但是android的原生方法受到限制,实现起来比较麻烦。于是想到了一个曲线救国的方法,在android端使用Google的web登录。 实现逻辑 在andorid端拉起外部浏览器,用户登录Google账号,登录成功后,再将登录信息返回到a…...

opencv常用边缘检测算子示例

opencv常用边缘检测算子示例 1. Canny算子2. Sobel算子3. Scharr算子4. Laplacian算子5. 对比 1. Canny算子 从不同视觉对象中提取有用的结构信息并大大减少要处理的数据量的一种技术&#xff0c;检测算法可以分为以下5个步骤&#xff1a; 噪声过滤&#xff08;高斯滤波&…...

安装了VM Tools,仍无法复制拖动-解决方案

今天在安装ubuntu时遇到了困扰许久的问题&#xff0c;安装了VM Tools&#xff0c;仍无法拖动主机文件到虚拟机&#xff0c;主要有两种原因并对应解决办法。 1.相关虚拟机设置选项卡中-客户机隔离-两个功能没有勾选 解决方案&#xff1a;勾选重启虚拟机即可 2.&#xff08;这个…...

一文读懂WPF布局

WPF布局 布局WPF 布局的核心机制常用布局控件详解Grid&#xff08;网格布局&#xff09;StackPanel&#xff08;堆叠布局&#xff09;DockPanel&#xff08;停靠布局&#xff09;WrapPanel&#xff08;自动换行布局&#xff09;Canvas&#xff08;绝对定位布局&#xff09;Unif…...

【Docker基础-网络】--查阅笔记4

目录 Docker 网络网络类型none 网络host 网络bridge 网络自定义网络 容器间通信IP 通信Docker DNS Serverjoined 容器 容器与外部通信容器访问外部外部访问容器 Docker 网络 学习Docker提供的几种原生网络如何创建自定义网络容器间通信&#xff0c;容器于外界交互 Docker 安装…...

FacialExpressionDetection的conda虚拟环境搭建Window

安装conda的URL&#xff1a;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 清华源下载conda版本&#xff0c;哪个最新用哪个&#xff0c;这里下载Anaconda3-5.3.1-Windows-x86_64.exe版本 安装conda虚拟环境 conda create --name py1…...

IPD推进中关键角色与岗位(七)LTDT确保技术开发的实用性与可靠性,满足市场需求

IPD 体系作为一种先进的研发管理模式&#xff0c;其核心在于打破传统职能部门的壁垒&#xff0c;通过跨部门团队的协同合作&#xff0c;实现产品开发的全流程优化。在 IPD 体系的架构中&#xff0c;存在着多个关键团队&#xff0c;它们各司其职又紧密配合&#xff0c;共同推动产…...

c++自学笔记——字符串与指针

字符串与指针 1. 字符串基础 字符串的定义&#xff1a;字符的序列&#xff0c;通常用来表示文本。 字符串字面量&#xff08;String Literal&#xff09;是在程序中直接用双引号括起来的文本序列。字符串字面量的类型是 const char*&#xff0c;即指向字符常量的指针。字符串…...

Jmeter分布式测试启动

代理客户端配置 打开jmeter.properties文件&#xff0c;取消注释并设置端口&#xff08;如server_port1099&#xff09;&#xff0c; 并添加server.rmi.ssl.disabletrue禁用SSL加密。 &#xff08;Linux系统&#xff09;修改jmeter-server文件中的RMI_HOST_DEF为代理机实际IP。…...

.DS_Store文件泄露、.git目录泄露、.svn目录泄露漏洞利用工具

&#x1f409;工具介绍 一款图形化的 .DS_Store文件泄露、.git目录泄露、.svn目录泄露漏洞利用工具。 &#x1f3af;使用 本工具使用Python3 PyQt5开发&#xff0c;在开始使用前&#xff0c;请确保已经安装了相关模块&#xff1a; pip3 install -r requirements.txt -i ht…...

Coze+大模型智能体环境搭建

1 前言 Coze作为字节跳动推出的新一代AI Bot开发平台&#xff0c;为开发者提供了快速构建、部署智能对话代理的一站式解决方案。Coze平台通过提供可视化工作流编排、知识库集成、插件扩展等核心功能&#xff0c;结合一些大模型&#xff0c;例如豆包、deepseek、通义千问等大模型…...

计算机视觉与深度学习 | 视觉SLAM学习思路总结与视觉SLAM发展历程(1986年至2025年)

视觉SLAM(Simultaneous Localization and Mapping,同时定位与建图)是计算机视觉和机器人领域的重要研究方向,涉及数学、几何、优化、传感器融合等多学科知识。以下是学习视觉SLAM的系统化思路总结,适合从入门到进阶的学习路径:视觉SLAM学习思路总结 一、基础准备 数学基…...

Ansible(8)——循环与条件任务

目录 一、循环迭代任务&#xff1a; 1、简单循环&#xff1a; 2、循环字典列表&#xff1a; 3、Ansible 2.5 之前的循环关键字&#xff1a; 4、在循环中使用 register 变量&#xff1a; 二、条件任务&#xff1a; 1、使用条件句的常见场景&#xff1a; 2、条件任务语法…...

搭建Trae+Vue3的AI开发环境

从2024年2025年&#xff0c;不断的有各种AI工具会在自媒体中火起来&#xff0c;号称各种效率王炸&#xff0c;而在AI是否会替代打工人的话题中&#xff0c;程序员又首当其冲。 作为一个后端开发&#xff0c;这篇文章基于Trae工具&#xff0c;来创建和运行一个简单的Vue前端应用…...

【免费公测】可遇AI直播/无人直播/矩阵直播/AI场控

前言 经过了一个多月的内测打磨&#xff0c;实现了非常稳定的无人直播效果&#xff0c;AI直播语音及其真实&#xff0c;软件交互方便&#xff0c;可以快速的构建AI直播间。 免费公测&#xff0c;内置无限激活卡密一张&#xff0c;打开即用。 亮点 高仿真语音模型&#xff0c…...

大数据Hadoop(MapReduce)

MapReduce概述 MapReduce定义 MapReduce是一个分布式运算程序的编程框架&#xff0c;是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在一个Hadoop集群上…...

使用apache-jmeter时,控制台打不开应用的解决方法

运行jmeter.bat的时候&#xff0c;提示&#xff1a; findstr 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 Not able to find Java executable or version. Please check your Java installation. errorlevel2这个错误信息表明系统无法识别 findstr 命令…...

leetcode 74. 搜索二维矩阵

class Solution {public boolean searchMatrix(int[][] matrix, int target) {int m matrix.length;int n matrix[0].length;int l 0;int r n * m - 1; // 记得减一while (l < r) {int mid (l r) / 2;if(matrix[mid / n][mid % n] > target) {r mid - 1;} else if…...

函数式编程在 Java:Function、BiFunction、UnaryOperator 你真的会用?

大家好&#xff0c;我是你们的Java技术博主&#xff01;今天我们要深入探讨Java函数式编程中的几个核心接口&#xff1a;Function、BiFunction和UnaryOperator。很多同学虽然知道它们的存在&#xff0c;但真正用起来却总是不得要领。这篇文章将带你彻底掌握它们&#xff01;&am…...

SpringMVC基础一(SpringMVC运行原理)

先了解MVC&#xff0c;在JavaWeb基础五中。 回忆servlet&#xff0c;在javaweb基础二中。 创建一个web项目&#xff1a; 1、新建maven项目&#xff0c;导入依赖。&#xff08;junit、springmvc、spring-webmvc、servlet-api、jsp-api、jstl&#xff09; <groupId>org…...

libva之ffavdemo分析

ffavdemo 代码库实现了一个基于FFmpeg和VAAPI的硬件加速视频解码与渲染框架&#xff0c;主要用于演示视频解码与渲染的完整硬件加速流程。支持多种渲染后端&#xff08;X11、DRM、EGL&#xff09;&#xff0c;适应不同显示环境。包含视频处理过滤器&#xff0c;可进行格式转换和…...

从零开始写android 的智能指针

Android中定义了两种智能指针类型&#xff0c;一种是强指针sp&#xff08;strong pointer&#xff09;&#xff0c;源码中的位置在system/core/include/utils/StrongPointer.h。另外一种是弱指针&#xff08;weak pointer&#xff09;。其实称之为强引用和弱引用更合适一些。强…...