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

【C语言】最详细的单链表(两遍包会!)

🦄个人主页:小米里的大麦-CSDN博客

🎏所属专栏:C语言数据结构_小米里的大麦的博客-CSDN博客

🎁代码托管:黄灿灿/数据结构 (gitee.com)

⚙️操作环境:Visual Studio 2022

目录

一、前言

二、单链表的概念

1. 单链表的特点

2. 单链表的基本操作

3. 画图思想

三、单链表分步详解

1. 函数声明

2. 打印函数 

3. 尾插函数(很重要!) 

4. 头插函数

5. 尾删函数

6. 头删函数

7. 查找函数

8. 在指定节点 pos 之前插入一个新节点

9. 删除指定节点 pos

10. 在指定节点 pos 之后插入一个新节点

11. 删除指定节点 pos 之后的节点

12. 链表的销毁

13. 测试用例

四、源文件展示(链表 · 07c4d36 · 黄灿灿/数据结构 - Gitee.com)

头文件:SList.h

函数部分源文件:SList.c

测试部分文件(同上):text.c

总结

希望这篇文章能帮助读者你更好地理解单链表的概念和操作。如果您有任何疑问或建议,请随时留言交流。

共勉


一、前言

在计算机科学中,链表是一种常见的数据结构,它通过一系列的节点来存储数据元素,其中每个节点包含一个数据字段和一个指向链表中下一个节点的引用。单链表是最简单的链表形式,本文将深入探讨单链表的基本概念、实现步骤及其易错点!帮助大家以最快速度学会单链表!

本文从0到尾,基本涵盖了所有经典问题,所有的资料全都有 (单词注释、链接直达),全部都准备好了!不用您再东找找、四看看了!接下来,我们会先分步讲单独的函数,不想细看的,可以直接跳转至文尾的 “源文件” 处!

一点要求如下:

  • 先要对单链表有概念常识,清楚单链表的链表形式。
  • 仔细思考示例中的实现方法,尤其是能够理解错误示例的错误点!(这是第一遍)
  • 凭借画图和逻辑思考(尽量不要有所参考),自行尝试手撕代码进行单链表的实现(这是第二遍)

二、单链表的概念

单链表由一系列节点组成,每个节点包含两个部分:

  1. 数据域:用于存储实际的数据。
  2. 指针域:存储指向链表中下一个节点的地址。

链表的第一个节点通常被称为“头节点”或“首节点”,而最后一个节点的指针域通常为空(NULL),表明链表的结束。

如果你不理解,可以点击链接跳转至 “单链表动画” (五分钟左右),清楚理解其链表形式后再回来!(【C语言】发明链表的人真是太有才啦_哔哩哔哩_bilibili 或者【动画演示】链表详解及其底层机制 C语言_哔哩哔哩_bilibili)

1. 单链表的特点

  • 动态分配:与数组不同,链表的大小可以在运行时改变,因此可以有效地管理内存资源。
  • 插入和删除操作简便:不需要像数组那样移动大量元素,只需修改相关节点的指针即可完成插入或删除操作。
  • 顺序访问:由于链表中的元素不是连续存储的,因此只能通过遍历的方式访问元素,不能像数组那样随机访问。

2. 单链表的基本操作

单链表的基本操作包括:

  • 初始化
  • 取值
  • 按值查找
  • 插入
  • 删除

3. 画图思想

数据结构最核心的思想是画图!!!这里给出几个样例,可自行导入画图板进行演示:

一个个人演示示例(画图板很实用!):

好了,到此,你应该对单链表有了一定的认识,难的是如何实现,下面我们开始分步讲解 (注意,这里的示例为8种链表中经典的无头单项非循环链表)

选择原因:无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

三、单链表分步详解

1. 函数声明

要实现单链表,首先就要有节点,我们先创建节点,构建我们的数据域指针域,这里的数据域用最简单的整形代替,学会进阶可增加/丰富数据域的内容,使其可以存储更多更丰富发内容!

//Single-linked lists:单链表(SList)
//list:列表#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//node:节点
//next:下一个
//data:数据
//Singly linked list nodes:单链表节点(SLLN)typedef struct SListnode
{int data;struct SListnode* next;
}SLLN;
//等价于:
//struct SListnode
//{
//	int data;
//	struct SListnode* next;
//};
//typedef struct SListnode SLLN;

此处顺便将所有函数声明给出,可看可不看,

  • 看:要注意函数命名,函数较多,切记不要混淆(说实话,这里的函数名不那么恰当,可自行更改)
  • 不看:只要记得有此处的函数声明即可
//找节点
SLLN* lookup(int x);// 动态申请一个节点:Dynamically apply for a node
//SLLN* Da_node(int x);// 单链表打印
void SList_print(SLLN* phead);// 单链表尾插	Tail plugging:尾插
//void SList_tplug(SLLN* phead, int x);//错!
void SList_tplug(SLLN** pphead, int x);//对!// 单链表的头插		Header:头插
void SList_header(SLLN** pphead, int x);// 单链表的尾删		Tail deletion:尾删
void SList_tdel(SLLN** pphead);// 单链表头删		Header deleted:头删
void SList_hdel(SLLN** pphead);// 单链表查找		Find:查找
SLLN* SList_find(SLLN* phead, int x);
//void SList_print_matching(SLLN* phead, int x);//在指定节点 pos 之前插入一个新节点:Inserts a new node before specifying it
void SList_before(SLLN** pphead, SLLN* pos, int x);//删除指定节点 pos :Deletes the specified node		
void SList_del(SLLN** pphead, SLLN* pos);//后:after
//在指定节点 pos 之后插入一个新节点:Inserts a new node after the specified node
void SList_after(SLLN* pos, int x);//删除指定节点 pos 之后的节点:Deletes a node after the specified node
void SList_del2(SLLN* pos);//链表销毁,Destroyed:销毁
void Destroyed(SLLN* phead);
//void Destroyed(SLLN** pphead);或者

函数部分注意引用部分:

#define _CRT_SECURE_NO_WARNINGS 1
//Single-linked lists:单链表
//Singly linked list nodes:单链表节点(SLLN)
//list:列表
//head:头
//tail:尾,尾巴
//temp:临时的#include "SList.h"

2. 打印函数 

好了,下面开始我们的第一个函数——打印函数:

提醒一下:空链表可以打印,空链表是指没有数据节点的链表,也就是说,除了可能存在的头节点外,链表中没有任何数据节点。在空链表中,通常会有一个头节点(有时也称为哨兵节点),这个节点不包含任何数据,它的作用主要是方便对链表进行操作,例如插入和删除等。

  • 空链表本身没有数据节点。
  • 是否包含头节点取决于具体的实现方式。
  • 空链表的特征是链表的最后一个节点(如果有的话)的next指针指向NULL
// 单链表打印
//可以使用二级指针,但没必要,要改变实参用二级,不改变用一级!可以加 const,不建议,会带来一些不必要的麻烦!
void SList_print(SLLN* phead)
{//此处切不可断言(assert),空链表可以打印,此处使用断言会终止程序!//(单链表为空,此处指针(phead)为空。顺序表此处需断言(assert),顺序表是一个结构体,结构体里有一个指针指向一块数组空间,结构体指针为空,程序就走不了了)SLLN* temp = phead;//注意:切不可写成 while (temp->next != NULL),最后一个数据无法打印!//正确写法://while (temp != NULL)while (temp){printf("%d->", temp->data);temp = temp->next;//temp++/++temp,注意:切不可加加!节点地址不能保证连续!}printf("NULL\n");
}

3. 尾插函数(很重要!) 

下面是最为复杂的尾插函数,注意深刻体会注释部分,明白注释中错误部分非常非常重要!!!对应的行数部分注释只有在 “源文件” 处才对应得上!!

//提取一个公共函数:找一个新节点,lookup:查找
//这个函数全部内容来自void SList_tplug(SLLN** pphead, int x)函数的 122-135 行 
SLLN* lookup(int x)
{SLLN* newnode = (SLLN*)malloc(sizeof(SLLN)); // newnode: 新节点if (newnode == NULL) // 检查{perror("malloc fail");return NULL; // 返回 NULL 表示失败}// 新节点初始化为空newnode->data = x;newnode->next = NULL;return newnode;
}虽然是错,但要从中吸取教训!!!总结结论!!!(着重看和理解注释部分!!!)单链表尾插	Tail plugging:尾插
//void SList_tplug(SLLN* phead, int x)
//{
//	//此处切不可断言(assert),链表为空,指针(phead)为空,此处使用断言会终止程序!
//
//	SLLN* newnode = (SLLN*)malloc(sizeof(SLLN));//newnode:新节点
//	if (newnode == NULL)//检查
//	{
//		perror("malloc fail");
//		return;
//	}
//
//	//新节点初始化为空
//	newnode->data = x;
//	newnode->next = NULL;
//
//	if (phead == NULL)
//	{
//		phead = newnode;
//	}
//	else
//	{
//		//找尾,尾插的本质:原尾节点中要存储新的尾节点地址
//		//"正确"写法(相对于此情况,但此情况(函数部分)有错):
//		SLLN* tail = phead;//tail:尾
//		while (tail->next != NULL)
//		{
//			tail = tail->next;
//		}
//		tail->next = newnode;//精华所在!
//
//		//错误写法:
//		//SLLN* tail = phead;//tail:尾
//		//while (tail != NULL)
//		//{
//		//	tail = tail->next;
//		//}
//		//tail = newnode;
//		//原因:函数栈帧知识:tail是局部变量,之后会销毁!
//	}
//}//整个函数错误原因:
请对比:void temp(int *p)						void temp(int *ptr)
{										{*p = 1;									ptr = (int*)malloc(sizeof(int));
}										}
int main()				    和			int main()
{							和			{int x = 0;				和				int* px=NULL;temp(&x);								temp(px);return 0;								return 0;
}										}改变的是int,使用的是int的指针;改变int*要使用int*的地址,int**指针!!!
所以要改变*ptr,使用的是*ptr的指针
即:
int main()
{int* px = NULL;Func(&px);free(px);return 0;
}
正确的函数写法:
void SList_tplug(SLLN** pphead, int x)
{//为了使用 35 行的 lookup 函数将此 122 到 135 行注释掉!改成 137 行的 SLLN* newnode = lookup(x);此处切不可断言(assert),链表为空,指针(phead)为空,此处使用断言会终止程序!//SLLN* newnode = (SLLN*)malloc(sizeof(SLLN));//newnode:新节点//if (newnode == NULL)//检查//{//	perror("malloc fail");//	return;//}新节点初始化为空//newnode->data = x;//newnode->next = NULL;SLLN* newnode = lookup(x);if (*pphead == NULL){*pphead = newnode;}else{//找尾,尾插的本质:原尾节点中要存储新的尾节点地址SLLN* tail = *pphead;//tail:尾while (tail->next != NULL){tail = tail->next;}tail->next = newnode;//精华所在!!!(改变指针,改变结构体成员,用一级指针,函数栈帧的知识要深挖!!!)}
}

4. 头插函数

比较简单,注意调用关系,层次关系,要理解二级指针!动画演示:链表的头插法,数据结构与算法完整代码动画,考研408 期末考试数据结构_哔哩哔哩_bilibili

// 单链表的头插		Header:头插
void SList_header(SLLN** pphead, int x)
{assert(pphead);SLLN* newnode = lookup(x);newnode->next = *pphead;*pphead = newnode;
}

5. 尾删函数

注意链表本身的顺序和节点规律,这里唯一重要的是要清晰当前节点和旁边的两个节点关系(后面很多地方也是一样!!),动画演示:链表的尾插法 完整代码动画版,数据结构与算法。附在线数据结构交互式工具_哔哩哔哩_bilibili

// 单链表的尾删		Tail deletion:尾删
void SList_tdel(SLLN** pphead)
{错误写法://SLLN* tail = *pphead;//while (tail->next != NULL)//{//	tail = tail->next;//}//free(tail);//tail = NULL;//错误原因:此行tail是局部变量,没有把前一个next节点置空,前一个节点是一个结构体,要将前一个结构体节点置空,需要一个结构体的指针!//检查二选一//暴力的检查assert(pphead);assert(*pphead);//或者assert(*pphead!=NULL);//温柔的检查//if (*pphead == NULL)//{//	return;//}if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//正确写法1:SLLN* temp = NULL;SLLN* tail = *pphead;while (tail->next != NULL){temp = tail;tail = tail->next;}free(tail);tail = NULL;temp->next = NULL;//正确写法2://SLLN* tail = *pphead;//while (tail->next->next != NULL)//{//tail = tail->next;//}//free(tail->next);//tail->next = NULL;}
}

6. 头删函数

比较简单,不进行过多赘述。

// 单链表头删		Header deleted:头删
void SList_hdel(SLLN** pphead)
{//检查二选一//暴力的检查assert(pphead);assert(*pphead);//或者assert(*pphead!=NULL);//温柔的检查//if (*pphead == NULL)//{//	return;//}SLLN* first = *pphead;//first:第一*pphead = first->next;free(first);first = NULL;
}

7. 查找函数

这里本身的查找函数,人是不好观察的,所以又提供了注释部分的查找打印函数,这里需要理解的是更新/迭代的逻辑!

// 单链表查找		Find:查找
SLLN* SList_find(SLLN* phead, int x)
{SLLN* temp = phead;while (temp){if (temp->data == x){return temp;}temp = temp->next;//更新temp}return NULL;
}
//或者使用查找打印函数:
//void SList_print_matching(SLLN* phead, int x)
//{
//	SLLN* temp = phead;
//	int found = 0; // 标记是否找到了匹配的节点
//
//	while (temp)
//	{
//		if (temp->data == x)
//		{
//			printf("%d ", temp->data); // 打印匹配的节点数据
//			found = 1; // 标记找到了匹配的节点
//		}
//		temp = temp->next;
//	}
//
//	if (!found)
//	{
//		printf("No matching elements found.\n"); // 如果没有找到匹配的节点
//	}
//	else
//	{
//		printf("\n"); // 打印换行符,使输出更加清晰
//	}
//}

8. 在指定节点 pos 之前插入一个新节点

注意分情况,因为有一种情况是头插,可以 “偷懒” 。

//在指定节点 pos 之前插入一个新节点:Inserts a new node before specifying it
void SList_before(SLLN** pphead, SLLN* pos, int x)
{assert(pos);assert(pphead);if (pos == *pphead){SList_header(pphead, x);}else{//找pos的前一个位置SLLN* temp = *pphead;while (temp->next != pos){temp = temp->next;}SLLN* newnode = lookup(x);temp->next = newnode;newnode->next = pos;}
}

9. 删除指定节点 pos

注意情况分析!

//删除指定节点 pos :Deletes the specified node		
void SList_del(SLLN** pphead, SLLN* pos)
{assert(pphead);assert(pos);//assert(*pphead);可要可不要if (*pphead == pos){SList_hdel(pphead);}else{//找到pos的前一个位置SLLN* temp = *pphead;while (temp->next != pos){temp = temp->next;}temp->next = pos->next;free(pos);//pos=NULL;没用,因为形参不改变实参(对应text6)}
}

10. 在指定节点 pos 之后插入一个新节点

迭代问题,注意保存下一个节点地址。

//后:after
//在指定节点 pos 之后插入一个新节点:Inserts a new node after the specified node
void SList_after(SLLN* pos, int x)
{//经典错误!!!//assert(pos);//SLLN* newnode = lookup(x);//pos->next = newnode;//newnode->next = pos->next;//在将 pos 的 next 指针指向新节点 newnode 之前,必须先保存 pos 节点的下一个节点的地址,否则这部分链表将丢失。//正确写法:assert(pos);SLLN* newnode = lookup(x);newnode->next = pos->next;//保存 pos 节点的下一个节点的地址pos->next = newnode;//将 pos 的 next 指针指向新节点 newnode
}

11. 删除指定节点 pos 之后的节点

还是注意更新迭代的问题。

//删除指定节点 pos 之后的节点:Deletes a node after the specified node
void SList_del2(SLLN* pos)
{assert(pos);assert(pos->next);//错误写法(不可单独出现)://pos->next = pos->next->next;//正确写法1:SLLN* temp = pos->next;pos->next = pos->next->next;free(temp);temp = NULL;//正确写法2://SLLN* temp = pos->next;//pos->next = temp->next;//free(temp);//temp = NULL;
}

12. 链表的销毁

注意销毁的顺序迭代问题,这里要着重注意,稍有疏忽,很容易造成内存泄漏!

//链表销毁,Destroyed:销毁
void Destroyed(SLLN* phead)
{//经典错误://错误写法1://SLLN* temp = phead;//while (temp)//{//	free(temp);//	temp = temp->next;//}//错误写法2://SLLN* temp = phead;//while (temp)//{//	SLLN* tmp = temp;//	free(temp);//	temp = tmp->next;//}//正确写法:SLLN* temp = phead;while (temp){SLLN* tmp = temp->next;free(temp);temp = tmp;}//phead=NULL;
}
//或者:
//void Destroyed(SLLN** pphead)
//{
//	assert(pphead);
//	SLLN* temp = *pphead;
//	while (temp)
//	{
//		SLLN* tmp = temp->next;
//		free(temp);
//		temp = tmp;
//	}
//
//	*pphead = NULL;
//}

13. 测试用例

 下面是我的个人测试用例,大家可自行采纳,欢迎大家去实践、去测试、去加入自己的想法!

#define _CRT_SECURE_NO_WARNINGS 1
//list:列表
//text:测试#include "SList.h"//第一次测试:
void text1()
{//此处错误对应 SList.c 文件 51 到 119 行,两处错误需同时取消注释,并将正确部分注释掉,可查看错误效果!/*SLLN* plist = NULL;SList_tplug(plist, 1);SList_tplug(plist, 2);SList_tplug(plist, 3);SList_tplug(plist, 4);SList_tplug(plist, 5);*///不改变实参直接传,要改变传地址!//正确写法(要传地址!):SLLN* plist = NULL;SList_tplug(&plist, 1);SList_tplug(&plist, 2);SList_tplug(&plist, 3);SList_tplug(&plist, 4);SList_tplug(&plist, 5);SList_print(plist);//不改变实参直接传,要改变传地址!}//第二次测试:
void text2()
{SLLN* plist = NULL;SList_header(&plist, 1);SList_header(&plist, 2);SList_header(&plist, 3);SList_header(&plist, 4);SList_header(&plist, 5);SList_print(plist);SList_tdel(&plist);SList_print(plist);SList_tdel(&plist);SList_print(plist);SList_tdel(&plist);SList_print(plist);
}//第三次测试:
void text3()
{SLLN* plist = NULL;SList_tplug(&plist, 1);SList_tplug(&plist, 2);SList_tplug(&plist, 3);SList_tplug(&plist, 4);SList_tplug(&plist, 5);SList_print(plist);SList_hdel(&plist);SList_print(plist);SList_hdel(&plist);SList_print(plist);SList_hdel(&plist);SList_print(plist);SList_hdel(&plist);SList_print(plist);
}//第四次测试:
void text4()
{SLLN* plist = NULL;SList_tplug(&plist, 1);SList_tplug(&plist, 2);SList_tplug(&plist, 3);SList_tplug(&plist, 4);SList_tplug(&plist, 5);SList_print(plist);//值为2的那个节点 *2SLLN* temp = SList_find(plist, 2);temp->data *= 2;SList_print(plist);}//第五次测试:
void text5()
{SLLN* plist = NULL;SList_tplug(&plist, 1);SList_tplug(&plist, 1);SList_tplug(&plist, 2);SList_tplug(&plist, 3);SList_tplug(&plist, 4);SList_tplug(&plist, 5);SList_print(plist);// 值为2那个节点  *2SLLN* temp = SList_find(plist, 2);SList_before(&plist, temp, 20);SList_print(plist);}//第六次测试:
void text6()
{SLLN* plist = NULL;SList_tplug(&plist, 1);SList_tplug(&plist, 1);SList_tplug(&plist, 2);SList_tplug(&plist, 3);SList_tplug(&plist, 4);SList_tplug(&plist, 5);SList_print(plist);// 值为2那个节点  *2SLLN* temp = SList_find(plist, 2);SList_del(&plist, temp);temp = NULL;//此处置空,或者别用即可SList_print(plist);Destroyed(plist);plist = NULL;//或者( 用于void Destroyed(SLLN** pphead); 生效时 !!)://Destroyed(&plist);//plist = NULL;
}int main()
{//每次开一个测试,也可自行写测试内容!//text1();//text2();text3();//text4();//text5();//text6();return 0;
}

四、源文件展示(链表 · 07c4d36 · 黄灿灿/数据结构 - Gitee.com

 在这里,注释对应的行号才准确!

头文件:SList.h

#pragma once
//Single-linked lists:单链表(SList)
//list:列表#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//node:节点
//next:下一个
//data:数据
//Singly linked list nodes:单链表节点(SLLN)typedef struct SListnode
{int data;struct SListnode* next;
}SLLN;
//等价于:
//struct SListnode
//{
//	int data;
//	struct SListnode* next;
//};
//typedef struct SListnode SLLN;//找节点
SLLN* lookup(int x);// 动态申请一个节点:Dynamically apply for a node
//SLLN* Da_node(int x);// 单链表打印
void SList_print(SLLN* phead);// 单链表尾插	Tail plugging:尾插
//void SList_tplug(SLLN* phead, int x);//错!
void SList_tplug(SLLN** pphead, int x);//对!// 单链表的头插		Header:头插
void SList_header(SLLN** pphead, int x);// 单链表的尾删		Tail deletion:尾删
void SList_tdel(SLLN** pphead);// 单链表头删		Header deleted:头删
void SList_hdel(SLLN** pphead);// 单链表查找		Find:查找
SLLN* SList_find(SLLN* phead, int x);
//void SList_print_matching(SLLN* phead, int x);//在指定节点 pos 之前插入一个新节点:Inserts a new node before specifying it
void SList_before(SLLN** pphead, SLLN* pos, int x);//删除指定节点 pos :Deletes the specified node		
void SList_del(SLLN** pphead, SLLN* pos);//后:after
//在指定节点 pos 之后插入一个新节点:Inserts a new node after the specified node
void SList_after(SLLN* pos, int x);//删除指定节点 pos 之后的节点:Deletes a node after the specified node
void SList_del2(SLLN* pos);//链表销毁,Destroyed:销毁
void Destroyed(SLLN* phead);
//void Destroyed(SLLN** pphead);或者

函数部分源文件:SList.c

#define _CRT_SECURE_NO_WARNINGS 1
//Single-linked lists:单链表
//Singly linked list nodes:单链表节点(SLLN)
//list:列表
//head:头
//tail:尾,尾巴
//temp:临时的#include "SList.h"// 单链表打印
//可以使用二级指针,但没必要,要改变实参用二级,不改变用一级!可以加 const,不建议,会带来一些不必要的麻烦!
void SList_print(SLLN* phead)
{//此处切不可断言(assert),空链表可以打印,此处使用断言会终止程序!//(单链表为空,此处指针(phead)为空。顺序表此处需断言(assert),顺序表是一个结构体,结构体里有一个指针指向一块数组空间,结构体指针为空,程序就走不了了)SLLN* temp = phead;//注意:切不可写成 while (temp->next != NULL),最后一个数据无法打印!//正确写法://while (temp != NULL)while (temp){printf("%d->", temp->data);temp = temp->next;//temp++/++temp,注意:切不可加加!节点地址不能保证连续!}printf("NULL\n");
}//提取一个公共函数:找一个新节点,lookup:查找
//这个函数全部内容来自void SList_tplug(SLLN** pphead, int x)函数的 122-135 行 
SLLN* lookup(int x)
{SLLN* newnode = (SLLN*)malloc(sizeof(SLLN)); // newnode: 新节点if (newnode == NULL) // 检查{perror("malloc fail");return NULL; // 返回 NULL 表示失败}// 新节点初始化为空newnode->data = x;newnode->next = NULL;return newnode;
}虽然是错,但要从中吸取教训!!!总结结论!!!(着重看和理解注释部分!!!)单链表尾插	Tail plugging:尾插
//void SList_tplug(SLLN* phead, int x)
//{
//	//此处切不可断言(assert),链表为空,指针(phead)为空,此处使用断言会终止程序!
//
//	SLLN* newnode = (SLLN*)malloc(sizeof(SLLN));//newnode:新节点
//	if (newnode == NULL)//检查
//	{
//		perror("malloc fail");
//		return;
//	}
//
//	//新节点初始化为空
//	newnode->data = x;
//	newnode->next = NULL;
//
//	if (phead == NULL)
//	{
//		phead = newnode;
//	}
//	else
//	{
//		//找尾,尾插的本质:原尾节点中要存储新的尾节点地址
//		//"正确"写法(相对于此情况,但此情况(函数部分)有错):
//		SLLN* tail = phead;//tail:尾
//		while (tail->next != NULL)
//		{
//			tail = tail->next;
//		}
//		tail->next = newnode;//精华所在!
//
//		//错误写法:
//		//SLLN* tail = phead;//tail:尾
//		//while (tail != NULL)
//		//{
//		//	tail = tail->next;
//		//}
//		//tail = newnode;
//		//原因:函数栈帧知识:tail是局部变量,之后会销毁!
//	}
//}//整个函数错误原因:
请对比:void temp(int *p)						void temp(int *ptr)
{										{*p = 1;									ptr = (int*)malloc(sizeof(int));
}										}
int main()				    和			int main()
{							和			{int x = 0;				和				int* px=NULL;temp(&x);								temp(px);return 0;								return 0;
}										}改变的是int,使用的是int的指针;改变int*要使用int*的地址,int**指针!!!
所以要改变*ptr,使用的是*ptr的指针
即:
int main()
{int* px = NULL;Func(&px);free(px);return 0;
}
正确的函数写法:
void SList_tplug(SLLN** pphead, int x)
{//为了使用 35 行的 lookup 函数将此 122 到 135 行注释掉!改成 137 行的 SLLN* newnode = lookup(x);此处切不可断言(assert),链表为空,指针(phead)为空,此处使用断言会终止程序!//SLLN* newnode = (SLLN*)malloc(sizeof(SLLN));//newnode:新节点//if (newnode == NULL)//检查//{//	perror("malloc fail");//	return;//}新节点初始化为空//newnode->data = x;//newnode->next = NULL;SLLN* newnode = lookup(x);if (*pphead == NULL){*pphead = newnode;}else{//找尾,尾插的本质:原尾节点中要存储新的尾节点地址SLLN* tail = *pphead;//tail:尾while (tail->next != NULL){tail = tail->next;}tail->next = newnode;//精华所在!!!(改变指针,改变结构体成员,用一级指针,函数栈帧的知识要深挖!!!)}
}// 单链表的头插		Header:头插
void SList_header(SLLN** pphead, int x)
{assert(pphead);SLLN* newnode = lookup(x);newnode->next = *pphead;*pphead = newnode;
}// 单链表的尾删		Tail deletion:尾删
void SList_tdel(SLLN** pphead)
{错误写法://SLLN* tail = *pphead;//while (tail->next != NULL)//{//	tail = tail->next;//}//free(tail);//tail = NULL;//错误原因:此行tail是局部变量,没有把前一个next节点置空,前一个节点是一个结构体,要将前一个结构体节点置空,需要一个结构体的指针!//检查二选一//暴力的检查assert(pphead);assert(*pphead);//或者assert(*pphead!=NULL);//温柔的检查//if (*pphead == NULL)//{//	return;//}if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//正确写法1:SLLN* temp = NULL;SLLN* tail = *pphead;while (tail->next != NULL){temp = tail;tail = tail->next;}free(tail);tail = NULL;temp->next = NULL;//正确写法2://SLLN* tail = *pphead;//while (tail->next->next != NULL)//{//tail = tail->next;//}//free(tail->next);//tail->next = NULL;}
}// 单链表头删		Header deleted:头删
void SList_hdel(SLLN** pphead)
{//检查二选一//暴力的检查assert(pphead);assert(*pphead);//或者assert(*pphead!=NULL);//温柔的检查//if (*pphead == NULL)//{//	return;//}SLLN* first = *pphead;//first:第一*pphead = first->next;free(first);first = NULL;
}// 单链表查找		Find:查找
SLLN* SList_find(SLLN* phead, int x)
{SLLN* temp = phead;while (temp){if (temp->data == x){return temp;}temp = temp->next;//更新temp}return NULL;
}
//或者使用查找打印函数:
//void SList_print_matching(SLLN* phead, int x)
//{
//	SLLN* temp = phead;
//	int found = 0; // 标记是否找到了匹配的节点
//
//	while (temp)
//	{
//		if (temp->data == x)
//		{
//			printf("%d ", temp->data); // 打印匹配的节点数据
//			found = 1; // 标记找到了匹配的节点
//		}
//		temp = temp->next;
//	}
//
//	if (!found)
//	{
//		printf("No matching elements found.\n"); // 如果没有找到匹配的节点
//	}
//	else
//	{
//		printf("\n"); // 打印换行符,使输出更加清晰
//	}
//}//在指定节点 pos 之前插入一个新节点:Inserts a new node before specifying it
void SList_before(SLLN** pphead, SLLN* pos, int x)
{assert(pos);assert(pphead);if (pos == *pphead){SList_header(pphead, x);}else{//找pos的前一个位置SLLN* temp = *pphead;while (temp->next != pos){temp = temp->next;}SLLN* newnode = lookup(x);temp->next = newnode;newnode->next = pos;}
}//删除指定节点 pos :Deletes the specified node		
void SList_del(SLLN** pphead, SLLN* pos)
{assert(pphead);assert(pos);//assert(*pphead);可要可不要if (*pphead == pos){SList_hdel(pphead);}else{//找到pos的前一个位置SLLN* temp = *pphead;while (temp->next != pos){temp = temp->next;}temp->next = pos->next;free(pos);//pos=NULL;没用,因为形参不改变实参(对应text6)}
}//后:after
//在指定节点 pos 之后插入一个新节点:Inserts a new node after the specified node
void SList_after(SLLN* pos, int x)
{//经典错误!!!//assert(pos);//SLLN* newnode = lookup(x);//pos->next = newnode;//newnode->next = pos->next;//在将 pos 的 next 指针指向新节点 newnode 之前,必须先保存 pos 节点的下一个节点的地址,否则这部分链表将丢失。//正确写法:assert(pos);SLLN* newnode = lookup(x);newnode->next = pos->next;//保存 pos 节点的下一个节点的地址pos->next = newnode;//将 pos 的 next 指针指向新节点 newnode
}//删除指定节点 pos 之后的节点:Deletes a node after the specified node
void SList_del2(SLLN* pos)
{assert(pos);assert(pos->next);//错误写法(不可单独出现)://pos->next = pos->next->next;//正确写法1:SLLN* temp = pos->next;pos->next = pos->next->next;free(temp);temp = NULL;//正确写法2://SLLN* temp = pos->next;//pos->next = temp->next;//free(temp);//temp = NULL;
}//链表销毁,Destroyed:销毁
void Destroyed(SLLN* phead)
{//经典错误://错误写法1://SLLN* temp = phead;//while (temp)//{//	free(temp);//	temp = temp->next;//}//错误写法2://SLLN* temp = phead;//while (temp)//{//	SLLN* tmp = temp;//	free(temp);//	temp = tmp->next;//}//正确写法:SLLN* temp = phead;while (temp){SLLN* tmp = temp->next;free(temp);temp = tmp;}//phead=NULL;
}
//或者:
//void Destroyed(SLLN** pphead)
//{
//	assert(pphead);
//	SLLN* temp = *pphead;
//	while (temp)
//	{
//		SLLN* tmp = temp->next;
//		free(temp);
//		temp = tmp;
//	}
//
//	*pphead = NULL;
//}

测试部分文件(同上):text.c

#define _CRT_SECURE_NO_WARNINGS 1
//list:列表
//text:测试#include "SList.h"//第一次测试:
void text1()
{//此处错误对应 SList.c 文件 51 到 119 行,两处错误需同时取消注释,并将正确部分注释掉,可查看错误效果!/*SLLN* plist = NULL;SList_tplug(plist, 1);SList_tplug(plist, 2);SList_tplug(plist, 3);SList_tplug(plist, 4);SList_tplug(plist, 5);*///不改变实参直接传,要改变传地址!//正确写法(要传地址!):SLLN* plist = NULL;SList_tplug(&plist, 1);SList_tplug(&plist, 2);SList_tplug(&plist, 3);SList_tplug(&plist, 4);SList_tplug(&plist, 5);SList_print(plist);//不改变实参直接传,要改变传地址!}//第二次测试:
void text2()
{SLLN* plist = NULL;SList_header(&plist, 1);SList_header(&plist, 2);SList_header(&plist, 3);SList_header(&plist, 4);SList_header(&plist, 5);SList_print(plist);SList_tdel(&plist);SList_print(plist);SList_tdel(&plist);SList_print(plist);SList_tdel(&plist);SList_print(plist);
}//第三次测试:
void text3()
{SLLN* plist = NULL;SList_tplug(&plist, 1);SList_tplug(&plist, 2);SList_tplug(&plist, 3);SList_tplug(&plist, 4);SList_tplug(&plist, 5);SList_print(plist);SList_hdel(&plist);SList_print(plist);SList_hdel(&plist);SList_print(plist);SList_hdel(&plist);SList_print(plist);SList_hdel(&plist);SList_print(plist);
}//第四次测试:
void text4()
{SLLN* plist = NULL;SList_tplug(&plist, 1);SList_tplug(&plist, 2);SList_tplug(&plist, 3);SList_tplug(&plist, 4);SList_tplug(&plist, 5);SList_print(plist);//值为2的那个节点 *2SLLN* temp = SList_find(plist, 2);temp->data *= 2;SList_print(plist);}//第五次测试:
void text5()
{SLLN* plist = NULL;SList_tplug(&plist, 1);SList_tplug(&plist, 1);SList_tplug(&plist, 2);SList_tplug(&plist, 3);SList_tplug(&plist, 4);SList_tplug(&plist, 5);SList_print(plist);// 值为2那个节点  *2SLLN* temp = SList_find(plist, 2);SList_before(&plist, temp, 20);SList_print(plist);}//第六次测试:
void text6()
{SLLN* plist = NULL;SList_tplug(&plist, 1);SList_tplug(&plist, 1);SList_tplug(&plist, 2);SList_tplug(&plist, 3);SList_tplug(&plist, 4);SList_tplug(&plist, 5);SList_print(plist);// 值为2那个节点  *2SLLN* temp = SList_find(plist, 2);SList_del(&plist, temp);temp = NULL;//此处置空,或者别用即可SList_print(plist);Destroyed(plist);plist = NULL;//或者( 用于void Destroyed(SLLN** pphead); 生效时 !!)://Destroyed(&plist);//plist = NULL;
}int main()
{//每次开一个测试,也可自行写测试内容!//text1();//text2();text3();//text4();//text5();//text6();return 0;
}

总结

单链表是一种灵活的数据结构,适用于多种应用场景。理解和掌握了单链表的基本操作后,可以更高效地使用它们来解决实际问题。在后续的文章中,我们还将继续探索单链表的其他高级特性和操作。

希望这篇文章能帮助读者你更好地理解单链表的概念和操作。如果您有任何疑问或建议,请随时留言交流。

共勉

相关文章:

【C语言】最详细的单链表(两遍包会!)

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C语言数据结构_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:黄灿灿/数据结构 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、前言 二、单链表的概念 1. 单链表的特点 2. 单链表的基本…...

QT:VS2019 CMake编译CEF

CEF介绍 CEF作为一个基于Chromium的开源Web浏览器控件&#xff0c;为第三方应用提供了强大的嵌入浏览器支持。其多平台支持、HTML5特性、自定义能力以及多进程架构等特性&#xff0c;使得CEF在浏览器开发、桌面应用、开发工具以及自动化测试等领域得到了广泛应用。 多平台支持…...

day31(8/19)——静态文件共享、playbook

目录 一、ansible模块 script模块 copy模块 使用command模块下载 nfs-utils rpcbind 在被控制的主机上添加static目录&#xff0c;并创建test文件 command模块 service模块 二、playbook 三、playbook编排vsftpd 1、安装 2、卸载 3、启动服务 4、修改配置文件设置不…...

白骑士的C#教学实战项目篇 4.4 游戏开发

系列目录 上一篇&#xff1a;白骑士的C#教学实战项目篇 4.3 Web开发 在这一部分&#xff0c;我们将探索如何使用 Unity 和 C# 开发游戏。游戏开发结合了编程、图形设计和创意&#xff0c;既充满挑战又充满乐趣。通过这一节的学习&#xff0c;您将了解游戏引擎的基础知识&#…...

在Vue工程中开发页面时,发现页面垂直方向出现两个滚动条的处理

在Vue工程中开发页面时&#xff0c;发现页面垂直方向出现两个滚动条 最近在开发页面时&#xff0c;发现页面多了两个滚动条&#xff0c;如图&#xff1a; 原因&#xff1a; 当一个页面的内容高度大于屏幕的高度时就会出现滚动条。一般情况下当一个页面高度大于屏幕高度时&a…...

【C++初阶】:C++入门篇(一)

文章目录 前言一、C命名空间1.1 命名空间的定义1.2 命名空间的使用 二、C的输入和输出2.1 cin和cout的使用 三、缺省参数3.1 缺省参数的分类 四、函数重载4.1 函数重载概念及其条件4.2 C支持函数重载原理 -- 名字修饰 前言 C是在C语言的基础之上&#xff0c;增加了一些面向对象…...

【JAVA CORE_API】Day14 Collection、Iterator、增强for、泛型、List、Set

Collection接口及常用方法 Collection<Object> collection new ArrayList();&#xff1a;实例化ArrayList集合对象&#xff1b; collectionName.add(Object obj);&#xff1a;在集合中增加元素&#xff1b; int sizeName collectionName.size();&#xff1a;获取集合…...

Go更换国内源配置环境变量

背景 要在中国境内下载和使用Go编程语言的包&#xff0c;可以使用国内的Go模块代理来加速下载速度。以下是一些常见的国内Go模块代理源以及如何切换到这些源的方法&#xff1a; 常见国内Go模块代理源 七牛云&#xff08;Qiniu&#xff09; https://goproxy.cn 阿里云&#xff0…...

澎湃认证显实力,浪潮信息存储兼容新篇章

浪潮信息在存储技术兼容性领域取得新突破&#xff0c;其集中式存储HF/AS系列与长擎安全操作系统24强强联合&#xff0c;成功完成澎湃技术认证。此次合作不仅验证了双方产品的无缝对接能力&#xff0c;更体现了浪潮信息在推动全产业链共建共享方面的坚定决心。 浪潮信息澎湃技术…...

Leetcode 3255. Find the Power of K-Size Subarrays II

Leetcode 3255. Find the Power of K-Size Subarrays II 1. 解题思路2. 代码实现 题目链接&#xff1a;3255. Find the Power of K-Size Subarrays II 1. 解题思路 这一题是题目3254的进阶版&#xff0c;其实主要就是增加了算法复杂度。 整体上来说的话思路还是一个分段的思…...

Kotlin学习02-变量、常量、整数、浮点数、操作符、元组、包、导入

变量、常量、整数、浮点数、操作符、元组、包、导入 Book.kt package com.wujialiang.packclass Book {var title: String "Hello" }val PI 3.14; val E 2.178;Main.kt //引入包 //import com.wujialiang.pack.Book; import com.wujialiang.pack.*; //重命名导…...

C++的模板简介

文章目录 一、前言二、函数模板&#xff08;Function Template&#xff09;三、类模板&#xff08;Class Template&#xff09;四、变参模板&#xff08;Variadic Template&#xff09;五、模板的递归与元编程六、模板的局限与陷阱七、常用模板的实例八、C20 的概念&#xff08…...

树莓派5 笔记25:第一次启动与配置树莓派5_8G

今日继续学习树莓派5 8G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 与 python 版本如下&#xff1a; 今日购得了树莓派5_8G版本&#xff0c;性能是同运…...

Melittin 蜂毒肽;GIGAVLKVLT TGLPALISWI KRKRQQ

【Melittin 蜂毒肽 简介】 蜂毒肽&#xff08;Melittin&#xff09;是蜜蜂毒液中的主要活性成分&#xff0c;由26个氨基酸组成&#xff0c;具有强碱性&#xff0c;易溶于水&#xff0c;是已知抗炎性最强的物质之一。蜂毒肽具有多种生物学、药理学和毒理学作用&#xff0c;包括…...

day32

更新源 cd /etc/apt/ sudo cp sources.list sources.list.save 将原镜像备份 sudo vim sources.list 将原镜像修改成阿里源/清华源&#xff0c;如所述 阿里源 deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiver…...

【clickhouse】 使用 SQLAlchemy 连接 ClickHouse 数据库的完整指南

我听见有人猜 你是敌人潜伏的内线 和你相知多年 我确信对你的了解 你舍命救我画面 一一在眼前浮现 司空见惯了鲜血 你忘记你本是娇娆的红颜 感觉你我彼此都那么依恋 &#x1f3b5; 许嵩《内线》 ClickHouse 是一款非常高效的开源列式数据库&#xff0c;因…...

按键收集单击,双击和长按

按键收集单击,双击和长按 引言 在我们生活中, 按键是必不可少的, 不同的电器, 有不同的按键, 但是按键总有不够用的时候, 那么给与一个按键赋予不同的功能,就必不可少了. 一个按键可以通过按下的时间长短和频次, 来定义其类型。 一次按键收集&#xff0c; 都是在一个按键收集周…...

进程的异常终止

进程的异常终止 进程收到了某些信号&#xff0c;他杀 进程自己调用abort函数&#xff0c;产生了SIGABRT(6)信号&#xff0c;自杀 进程的最后一个线程收到了"取消"操作&#xff0c;并且做出响应 如果进程是异常结束的&#xff0c;atexit\on_exit它们事先注册的遗言…...

并发编程 | Future是如何优化程序性能

在初识Future一文中介绍了Future的核心方法。本文中接着介绍如何用Future优化我们的程序性能。 在此之前&#xff0c;有必要介绍Future接口的一个实现类FutureTask。 FutureTask介绍 FutureTask继承结构 首先我们看一下FutureTask的继承结构&#xff1a; public class Futur…...

Oracle笔记

一、 如何解决 sqlplus 无法使用退格键和方向键 .bashrc 中添加如下内容&#xff0c;解决 退格键 stty erase ^h 安装 rlwap 后&#xff0c;执行如下命令可解决 方向键 rlwap sqlplus 二、 都有哪些备份数据到工具 三、 谈谈 你对 oracle 中实例和数据库的理解 数据库是一…...

LVS+Keepalived 双机热备

LVSKeepalived 双机热备 Keepalived案例分析Keepalived工具介绍Keepalived工具介绍一、功能特点 一、理解Keepalived实现原理实验报告资源列表一、安装keepalived以及ipvsadm Keepalived案例分析 企业应用中&#xff0c;单台服务器承担应用存在单点故障的危险单点故障一旦发生…...

Web Image scr图片从后端API获取基本实现

因系统开发中需求&#xff0c;会有页面显示图片直接从后端获取后显示&#xff0c;代码如下&#xff1a; 后端&#xff1a; /*** 获取图片流* param response* param fileName*/RequestMapping(value"getImgStream",method RequestMethod.GET)public void getImgStr…...

2024音频剪辑指南:探索四大高效工具!

音频剪辑不仅仅是技术活&#xff0c;更是一种艺术创作&#xff0c;它能够让声音更加生动、更具感染力。今天&#xff0c;我们就来探索几款优秀的音频剪辑工具。 福昕音频剪辑 链接&#xff1a;www.pdf365.cn/foxit-clip/ 福昕音频剪辑是一款界面简洁、操作直观的音频编辑软件…...

“CSS”第一步——WEB开发系列13

CSS (Cascading Style Sheets&#xff0c;层叠样式表&#xff09;&#xff0c;是一种用来为结构化文档&#xff08;如 HTML 文档或 XML 应用&#xff09;添加样式&#xff08;字体、间距和颜色等&#xff09;的计算机语言&#xff0c;CSS 文件扩展名为 .css。 一、什么是 CSS&a…...

IEEE802网络协议和标准

IEEE802网络协议和标准 802委员会IEEE 802介绍现有标准 IEEE 802.3介绍物理媒介类型MAC子层与LLC子层主要内容通讯标准POE供电标准802.3af、802.3at、802.3btIEEE802.3af的工作过程&#xff1a;IEEE802.3af主要供电参数&#xff1a;IEEE802.3af的分级参数&#xff1a;为什么会有…...

vulnhub靶机 DC-9(渗透测试详解)

一、靶机信息收集 1、靶机下载 https://download.vulnhub.com/dc/DC-9.zip 2、靶机IP扫描 3、探测靶机主机、端口、服务版本信息 4、靶机目录扫描 二、web渗透测试 1、访问靶机IP 查看页面功能点&#xff0c;发现一个搜索框和登录框 2、测试一下是否存在sql注入 查看当前数…...

javaweb的新能源充电系统pf

TOC springboot339javaweb的新能源充电系统pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域…...

如何在桌面同时展示多个窗口

一、实现2分屏显示 win箭头 二、实现3分屏显示 1. 在实现2分屏显示的基础上&#xff0c;再次点击箭头图标&#xff0c;这次选择屏幕的上方或下方。 2. 点击后&#xff0c;第三个窗口将会出现在你选择的区域。现在&#xff0c;你可以在三个窗口之间自由切换&#xff0c;提高工…...

The Sandbox 游戏制作教程(第 5 部分):创建基于分类的系统

欢迎回到我们的系列&#xff0c;我们将记录 The Sandbox Game Maker 的 “On-Equip”&#xff08;装备&#xff09;功能的多种用途。 如果你刚加入 The Sandbox&#xff0c;装备功能是 “可收集组件”&#xff08;Collectable Component&#xff09;中的一个多功能工具&#x…...

HTML浏览器缓存(Browser Cache)

介绍&#xff1a; 浏览器缓存是Web缓存中最直接、最常见的一种形式。当浏览器首次请求某个资源时&#xff0c;如果服务器响应中包含了缓存控制指令&#xff08;如Cache-Control、Expires等&#xff09;&#xff0c;浏览器就会将这些资源存储在本地缓存中。后续请求相同资源时&a…...

短剧APP系统,推动短剧市场发展

近年来&#xff0c;短剧作为一直火爆的新兴行业&#xff0c;凭借着剧情进奏、爽、时长短等优势&#xff0c;深受大众欢迎&#xff0c;成为了大众碎片化时间的解压神器。 目前&#xff0c;随着短剧市场的快速发展&#xff0c;各个类型的短剧层出不穷&#xff0c;也推动了短剧AP…...

嵌入式 | 嵌入式 Linux 系统使用摄像头

点击上方"蓝字"关注我们 01、引言 >>> 在嵌入式 Linux 系统使用摄像头 俗话说“眼见为实”,这或许是为什么近年来摄像头在嵌入式系统上快速增长的原因。它们被用于不同的场景,如: 远程监控:典型的例子是闭路电视,监控人员在监视环境(或许你所在的大楼…...

C 开源库之cJSON

cJSON简介 CJSON库是一个用于解析和生成JSON数据的C语言库。 它提供了一组函数&#xff0c;使得在C语言中操作JSON数据变得简单而高效。 您可以使用CJSON库来解析从服务器返回的JSON数据&#xff0c;或者将C语言数据结构转换为JSON格式以进行传输。 cJSON 使用 官网地址&…...

ROW_NUMBER(), RANK(), DENSE_RANK() SQL排序函数图文详解

ROW_NUMBER(), RANK(), DENSE_RANK() ROW_NUMBER(): 为结果集中的每一行分配唯一的连续编号。即使有重复的值&#xff0c;ROW_NUMBER() 也会为它们分配不同的序号。 SELECT column_name, ROW_NUMBER() OVER (ORDER BY column_name) AS row_num FROM table_name;2. RANK(): 对结…...

Spring IoCDI(下)—DI的尾声

我们之前学习了控制反转IoC&#xff0c;接下来就开始学习依赖注入DI的细节。 依赖注入是一个过程&#xff0c;是指IoC容器在创建Bean时&#xff0c;去提供运行时所依赖的资源&#xff0c;而资源指的就是对象。我们使用 Autowired 注解&#xff0c;完成依赖注入的操作。简单来说…...

仕考网:考外省公务员可以调回本地吗?

一般情况下&#xff0c;公务员岗位是固定不可随意更换的&#xff0c;因为每个职位都对应特定的职责和要求。一旦考到外地的岗位&#xff0c;想要调回本地几乎是不可能的。因为这样的操作可能导致职位空缺&#xff0c;进而需要通过公共招聘流程来填补&#xff0c;而不是简单地从…...

《工厂模式在软件开发中的深度剖析与应用》

工厂模式 在软件开发的领域中&#xff0c;设计模式充当着解决常见问题的高效且可复用的策略角色。其中&#xff0c;工厂模式作为创建对象的重要设计模式&#xff0c;具有不可小觑的应用价值。接下来&#xff0c;我们将深入探讨简单工厂模式、工厂方法模式和抽象工厂模式。 一…...

双向通信之Websocket

介绍 Websocket是一种在单个TCP连接上进行全双工通信的协议。与传统的HTTP协议不同&#xff0c;websocket允许客户端与服务器之间的双向通信&#xff0c;可以在同一条连接上进行多次消息的快速传递。我之前在做一个线上刷题网站的时候&#xff0c;需要设计一个社区讨论模块&am…...

git学习使用碰到的问题1

本来在B站上看到的关于stash的使用时视频末尾讲到git stash drop 编号 会删除暂存记录 确实也是这么回事&#xff0c;但是末尾说到git stash pop 编号时up主说在恢复工作进度的时候我们可以直接删除掉这个工作记录可以直接使用 git stash pop stash{0} 使用完以后却出现了如上图…...

JavaScript初级——Math

一、Math 和其他的对象不同&#xff0c;它不是一个构造函数。它属于一个工具类&#xff0c;不用创建对象&#xff0c;里边封装了数学运算相关的属性和方法。 比如&#xff1a; Math.PI 表示圆周率。 二、Math.abs&#xff08;&#xff09; —— 可以用来计算一个数的绝对值。 三…...

ffmpeg的基础命令

文章目录 ffmpeg/ffplay/ffprobe区别ffmpeg 的作用ffplay的作用ffprobe的作用 ffmpeg使用概述功能概述转码过程简单使用FFMPEG -i常用的 -i例子 ff***工具之间共享的选项ffmpeg主要选项ffmpeg提取音视频数据ffmpeg命令修改原有的视频格式ffmpeg命令裁剪和合并视频拼接视频的方式…...

二建机电工程实务试题内附答案

1.下列有色金属材料中&#xff0c;不属于铜合金的是()。 A.紫铜 B.青铜 C.黄铜 D.白铜【答案】A 2.用于完成介质间热量交换的换热设备是()。 A.分离器 B.反应器 C.冷凝器 D.分解锅【答案】C 3.工程测量的核心是()。 A.测量精度 B.设计要求 C.减少误差累积 D.检核【答案】D 4.吊…...

Redis的热key以及Big(大)key是什么?如何解决Redis的热key以及Big(大)key问题?

一、先讲讲什么是redis的热key问题 在Redis中&#xff0c;我们把访问频率高的Key&#xff0c;称为热Key。比如突然有几十万的请求去访问redis中某个特定的Key&#xff0c;那么这样会造成redis服务器短时间流量过于集中&#xff0c;很可能导致redis的服务器宕机。那么接下来对这…...

django学习入门系列之第九点《MySQL命令介绍一》

文章目录 MySQL命令数据库的管理&#xff08;文件夹&#xff09;查看现在已有的数据库&#xff08;文件夹&#xff09;创建数据库&#xff08;文件夹&#xff09;删除数据库&#xff08;文件夹&#xff09;进入数据库&#xff08;文件夹&#xff09;查看文件夹下所有的数据表&a…...

Mysql面试一

目录 一、事务的四大特性&#xff08;ACID&#xff09;&#xff1a; 脏读 不可重复读 幻读 隔离性与隔离级别 数据库的三大范式 第一范式。确保数据表中的每个字段都是不可分割的最小单位&#xff0c;即原子性。这意味着表中的每一列都应代表一个独立的数据单元&#xff…...

模型优化之剪枝

文章目录 什么是神经网络剪枝剪枝的好处不同粒度的剪枝剪枝的分类非结构化剪枝结构化剪枝 哪些层的参数更容易被剪掉剪枝效果 什么是神经网络剪枝 神经网络剪枝 在训练期间删除连接密集张量将变得稀疏&#xff08;用零填充&#xff09;可以通过结构化块&#xff08; n m nm nm&…...

JVM的组成

JVM 运行在操作系统之上 java二进制字节码文件的运行环境 JVM的组成部分 java代码在编写完成后编译成字节码文件通过类加载器 来到运行数据区,主要作用是加载字节码到内存 包含 方法区/元空间 堆 程序计数器,虚拟机栈,本地方法栈等等 随后来到执行引擎,主要作用是翻译字…...

快速上手 iOS Protocol Buffer

快速上手 iOS Protocol Buffer | 来自缤纷多彩的灰 本文主要介绍在 iOS 开发中如何快速上手使用 Protobuf。更多关于 Protobuf 的介绍和相关的功能 api&#xff0c;读者可自行查阅官网。 Protocol Buffer&#xff08;简称 Protobuf&#xff09;是一种由Google开发的语言中立、…...

每天一个数据分析题(四百八十)- 线性回归建模

关于线性回归建模&#xff0c;线性回归模型假设说法不正确的是&#xff1f; A. 因变量和自变量要有因果关系 B. 残差均值为0 C. 残差服从正态分布 D. 自变量不存在共线性 数据分析认证考试介绍&#xff1a;点击进入 题目来源于CDA模拟题库 点击此处获取答案 数据分析专…...

电动汽车和混动汽车DC-DC转换器的创新设计与测试方法

汽车 DC-DC 转换器市场规模将达到187亿美元&#xff0c;年复合增长率为10%。 DC-DC 转换器是汽车的重要组成部分&#xff0c;它可以通过电压转换为各种车载系统供电&#xff0c;例如日益复杂的车载信息娱乐系统、使用驾驶辅助系统&#xff08;ADAS&#xff09;实现的增强安全功…...