C——单链表

news2024/9/28 19:16:25

一.前言

我们在前面已经了解了链表中的双向链表,而我们在介绍链表分类的时候就说过常用的链表只有两种——双向带头循环链表和单向不带头不循环链表。下来我来介绍另一种常用的链表——单向不带头不循环链表也叫做单链表。不清楚链表分类的以及不了解双向链表的可以看我之前的博客C——双向链表-CSDN博客。

二.单链表的结构

我们已经了解了单链表的全称叫做单向不带头不循环链表,我们怎么理解这链表前面的修饰词呢?其实,单链表就像是一节节连接起来的火车车厢一样。但是这节车厢只可以向后走,并且火车的车头和车尾是不能连接在一起的。

我们照着这张图再来分析单链表的结构:单向的意思就是只能沿着D->A->C->E->Z->X->W这个方向走,那不带头的意思就是没有头节点即哨兵位,我们在讲双向链表的时候提到了哨兵位,他只是一个没有有效数据的头节点。不循环的意思我们也可以对照这双向链表来看,双向链表的尾节点可以找到头节点,头节点也可以找到尾节点,这就是循环;而单链表的尾节点指向的不是头节点而是NULL,头节点也不能找到尾节点。

那么单链表的节点到底是什么样的呢?

 我们再来与双向链表的节点进行对比一下:

 单链表节点只有两个成员,分别是数据和指向下一个节点的指针,而双向链表的节点有三个成员,分别是数据,指向前一个节点的指针和指向下一个节点的指针。

综上所述:单链表是一个只能指向下一个节点的链表,而且没有头节点,并且是不循环的。 

三.实现单链表

与双向链表的实现相似,实现单链表也需要很多的函数及头文件,所以我们将所有的函数声明和头文件都放到singlelist.h中,函数的实现都放入singlelist.c中。

3.1单链表节点的结构

//链表节点
struct SingleList
{
	int val;
	struct SingleList* next;
};

我们这样写虽然可以完成链表的基本结构,但是难道我们链表只能存储整形嘛?如果我们创建了很多个整型节点,但是有一天我们想利用链表存储字符型或者浮点型数据,那么我们就得一个一个的去修改,费时费力;还有就是这个节点的名称很长,在后面的代码中会重复出现,也很浪费时间。所以我们可以利用typedef关键词,对节点重命名,以及对int重命名。

//节点数据类型
typedef int SingleListdatatype;

//链表节点
typedef struct SingleListNode
{
	SingleListdatatype val;
	struct SingleListNode* next;
}SLNode;

这样我们在多次使用该类型的时候就不需要再写那么长一串了,以后再修改储存的数据类型的时候也就不用一个一个节点的修改了,只需将定义的节点数据类型中的数据类型修改即可。

3.2单链表节点的创建

 我们在创建节点的时候实际上就是创建一个结构体变量,我们可以利用动态内存管理为我们的每一个节点动态开辟一块内存空间。

//节点的创建
SLNode* BuyNode(SingleListdatatype x)
{
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	if (node == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	node->val = x;
	node->next = NULL;
	return node;
}

 我们来测试一下节点创建函数是否正确:

我们利用此测试代码来调试发现,我们创建了4个节点,通过调试也确实观察到了这四个节点,并且它们储蓄的数据就是我们传的数据,指向下一个节点的指针也是NULL。与我们想要实现的结果相同。 

那我们现在将这四个节点连接起来:

void test1()
{
	SLNode* plist = NULL;
	SLNode* plist1 = BuyNode(1);
	SLNode* plist2 = BuyNode(2);
	SLNode* plist3 = BuyNode(3);
	SLNode* plist4 = BuyNode(4);

	plist1->next = plist2;
	plist2->next = plist3;
	plist3->next = plist4;
}

int main()
{
	test1();
	return 0;
}

我们再来调试一下: 

我们看到将所有节点连接起来之后,我们就可以利用第一个节点找到后面的节点。 

3.3链表的打印

//链表的打印
void SLNodePrint(SLNode* plist)
{
	while (plist)//plist != NULL
	{
		printf("%d->", plist->val);
		plist = plist->next;
	}
	printf("NULL\n");
}

 我们再来测试一下该函数:

也是没有出现问题,说明我们链表的打印已经完成了。 

3.4尾插

我们现在创建的节点需要我们手动将其连接起来,我们可以利用尾插方法,将新创建的节点直接连接到前一个节点上。 

//尾插
void SLNodepushback(SLNode** plist, SingleListdatatype x)
{
	assert(plist);

	SLNode* newnode = BuyNode(x);//新节点

	if (*plist == NULL)
	{
		//空链表
		*plist = newnode;
	}
	else
	{
		//非空链表
		//遍历原链表,找到尾节点
		SLNode* pcur = *plist;
		while (pcur->next)//(*plist)->next != NULL
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}

我们测试下尾插方法:

我们尾插了四次,通过打印方法,将链表打印出来,说明我们的尾插方法也没有问题。但是有些人可能会有疑问:为什么这里传的是二级指针 而不传一级指针?

因为我们是通过一个结构体类型的指针来维护我们的链表,而我们传一级指针的话相当于值传递,而对于值传递来说,形参改变是不影响实参的。简单来说,就说你在函数内部将形参节点的next指针改变了,但是却不影响实参的值。达不到我们的目的。所以要想形参的改变影响实参的话,我们就得地址传递,就得传一级指针的地址,也就是二级指针

3.5头插 

头插从名字就可以听出来是把一个新节点插到链表的头部,我们利用图来分析如何进行头插:

//头插
void SLpushfront(SLNode** plist, SingleListdatatype x)
{
	assert(plist);

	SLNode* newnode = BuyNode(x);
	newnode->next = *plist;
	*plist = newnode;
}

我们测试头插方法:

根据打印结果来看,头插的方法也没有任何问题。 

3.6尾删

尾删就是删除链表中的尾节点,那么前提就是该链表不能为空,如果都是空链表了,怎么执行删除操作呢?我们画图来分析一下:

//尾删
void SLpopback(SLNode** plist)
{
	assert(plist && *plist);//*plist != NULL防止链表为空

	SLNode* pcur = *plist;//记录倒数第二个节点
	SLNode* del = *plist;//记录尾节点
	while (del->next)
	{
		pcur = del;
		del = del->next;
	}
	pcur->next = NULL;
	free(del);
	del = NULL;
}

我们测试之后发现出了错误,这是为什么呢?

我们进行四次尾删,最后一次删除会将1删除掉,此时应该只打印NULL,这里打印了随机值。这是为什么呢? 

//尾删
void SLpopback(SLNode** plist)
{
	assert(plist && *plist);//*plist != NULL防止链表为空

	if (!((*plist)->next))//(*plist)->next == NULL
	{
		//链表只有一个节点
		free(*plist);
		*plist = NULL;
	}
	else
	{
		//链表有多个节点
		SLNode* pcur = *plist;//记录倒数第二个节点
		SLNode* del = *plist;//记录尾节点
		while (del->next)
		{
			pcur = del;
			del = del->next;
		}
		pcur->next = NULL;
		free(del);
		del = NULL;
	}
}

我们现在再来测试一下修改后的尾删代码: 

我们看到,现在就没有问题了。

3.7头删

我们实现了尾删,现在来实现头删。头删的前提与尾删的前提相同:链表不能为空。头删实际上就是删除链表中的第一个节点,我们画图分析如何实现头删:

//头删
void SLpopfront(SLNode** plist)
{
	assert(plist && *plist);//链表不能为空

	SLNode* next = (*plist)->next;
	free(*plist);
	*plist = next;
}

我们来测试头删方法:

没有问题~ 

那如果我们多删一次呢?

我们看到,第五次删除的时候这时候已经是空链表了,我们再看提示的错误信息,说是断言出错了,这就说明了链表为空了。 

3.8查找数据

查找数据非常简单,我们只需要遍历链表,将要查找的数据与链表中的数据进行对比,如果找到了则返回该数据对应节点的地址;如果没找到则返回NULL。

//查找
SLNode* SLFind(SLNode* plist, SingleListdatatype x)
{
	assert(plist);

	SLNode* pcur = plist;
	while (pcur)
	{
		if (pcur->val == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

测试查找:

结果没毛病,老铁OK了! 

3.9在指定位置之前插入数据

我们要在指定位置的前面插入数据,所以我们必须得找到该指定位置,这就需要调用我们的查找方法了。我们来画图分析一下:

//在指定位置之前插入数据
void SLposfront(SLNode** plist, SLNode* pos, SingleListdatatype x)
{
	assert(plist && *plist);//链表不能为空,否则找不到指定位置

	if (pos == *plist)
	{
		//pos节点就是第一个节点
		//调用头插方法
		SLpushfront(plist,x);
	}
	else
	{
		//pos节点不是第一个节点
		SLNode* newnode = BuyNode(x);
		SLNode* pcur = *plist;
		SLNode* prev = *plist;
		while (pcur)
		{
			if (pcur->val == pos->val)
			{
				break;
			}
			prev = pcur;
			pcur = pcur->next;
		}
		// prev newnode pcur/pos
		prev->next = newnode;
		newnode->next = pcur;
	}
}

 我们测试该代码:

我们测试了三种位置的插入,无论是第一个节点之前还是中间节点之前或者是尾节点之前都没有问题。

3.10在指定位置之后插入数据

在指定位置之后插入数据与在指定位置之前插入数据有相同点但也有不同点,我们继续来画图分析:

//在指定位置之后插入数据
void SLposback(SLNode* pos, SingleListdatatype x)
{
	assert(pos);
	SLNode* newnode = BuyNode(x);
	//pos newnode pos->next
	newnode->next = pos->next;
	pos->next = newnode;
}

我们对其进行测试来看: 

3.11删除pos节点

我们现在来删除指定节点,前提肯定是链表不能为空。我们接着来画图分析:

//删除pos节点
void slpoppos(SLNode** plist, SLNode* pos)
{
	assert(plist && *plist);
	assert(pos);

	if (*plist == pos)
	{
		//pos是头节点,调用头删方法
		SLpopfront(plist);
	}
	else
	{
		SLNode* prev = *plist;
		while (prev->next != pos)
		{
			//找到pos的前一个节点
			prev = prev->next;
		}
		//prev pos pos->next
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

测试该代码: 老铁没毛病!

3.12删除pos节点之后的节点

删除pos节点之后的节点我们要改变指向的指针就只有一个了,那就是pos->next指针。要让它指向pos->next->next。我们画图分析:

//删除pos节点后一个节点
void slpopposback(SLNode* pos)
{
	assert(pos && pos->next);//pos->next != NULL避免pos是尾节点

	SLNode* Next = pos->next;
	pos->next = pos->next->next;
	free(Next);
	Next = NULL;
}

 测试代码:

那如果我们的pos节点就是尾节点程序会发生什么呢? 

我们看到,上面提醒我们断言出错,说明此时pos节点为尾节点。 

3.13销毁链表

//销毁链表
void DestorySL(SLNode** plist)
{
	assert(plist && *plist);
	SLNode* Next = (*plist)->next;
	while (Next)
	{
		free(*plist);
		*plist = Next;
		Next = Next->next;
	}
	free(*plist);
	*plist = NULL;
}

 我们通过调试来判断我们的销毁方法:没有问题!

到这里,我们单链表的所有方法都已经完成了。下面附上完整代码:

四.完整代码

1.singlelist.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//节点数据类型
typedef int SingleListdatatype;

//链表节点
typedef struct SingleListNode
{
	SingleListdatatype val;
	struct SingleListNode* next;
}SLNode;

//节点的创建
SLNode* BuyNode(SingleListdatatype x);

//链表的打印
void SLNodePrint(SLNode* plist);

//尾插
void SLNodepushback(SLNode** plist, SingleListdatatype x);

//头插
void SLpushfront(SLNode** plist, SingleListdatatype x);

//尾删
void SLpopback(SLNode** plist);

//头删
void SLpopfront(SLNode** plist);

//查找
SLNode* SLFind(SLNode* plist, SingleListdatatype x);

//在指定位置之前插入数据
void SLposfront(SLNode** plist, SLNode* pos,SingleListdatatype x);

//在指定位置之后插入数据
void SLposback(SLNode* pos, SingleListdatatype x);

//删除pos节点
void slpoppos(SLNode** plist, SLNode* pos);

//删除pos节点后一个节点
void slpopposback(SLNode* pos);

//销毁链表
void DestorySL(SLNode** plist);

2.singlelist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SingleList.h"

//节点的创建
SLNode* BuyNode(SingleListdatatype x)
{
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	if (node == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	node->val = x;
	node->next = NULL;
	return node;
}

//链表的打印
void SLNodePrint(SLNode* plist)
{
	while (plist)//plist != NULL
	{
		printf("%d->", plist->val);
		plist = plist->next;
	}
	printf("NULL\n");
}

//尾插
void SLNodepushback(SLNode** plist, SingleListdatatype x)
{
	assert(plist);

	SLNode* newnode = BuyNode(x);//新节点

	if (*plist == NULL)
	{
		//空链表
		*plist = newnode;
	}
	else
	{
		//非空链表
		//遍历原链表,找到尾节点
		SLNode* pcur = *plist;
		while (pcur->next)//(*plist)->next != NULL
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}

//头插
void SLpushfront(SLNode** plist, SingleListdatatype x)
{
	assert(plist);

	SLNode* newnode = BuyNode(x);
	newnode->next = *plist;
	*plist = newnode;
}

//尾删
void SLpopback(SLNode** plist)
{
	assert(plist && *plist);//*plist != NULL防止链表为空

	if (!((*plist)->next))//(*plist)->next == NULL
	{
		//链表只有一个节点
		free(*plist);
		*plist = NULL;
	}
	else
	{
		//链表有多个节点
		SLNode* pcur = *plist;//记录倒数第二个节点
		SLNode* del = *plist;//记录尾节点
		while (del->next)
		{
			pcur = del;
			del = del->next;
		}
		pcur->next = NULL;
		free(del);
		del = NULL;
	}
}

//头删
void SLpopfront(SLNode** plist)
{
	assert(plist && *plist);//链表不能为空

	SLNode* next = (*plist)->next;
	free(*plist);
	*plist = next;
}

//查找
SLNode* SLFind(SLNode* plist, SingleListdatatype x)
{
	assert(plist);

	SLNode* pcur = plist;
	while (pcur)
	{
		if (pcur->val == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//在指定位置之前插入数据
void SLposfront(SLNode** plist, SLNode* pos, SingleListdatatype x)
{
	assert(plist && *plist);//链表不能为空,否则找不到指定位置
	assert(pos);

	if (pos == *plist)
	{
		//pos节点就是第一个节点
		//调用头插方法
		SLpushfront(plist,x);
	}
	else
	{
		//pos节点不是第一个节点
		SLNode* newnode = BuyNode(x);
		SLNode* pcur = *plist;
		SLNode* prev = *plist;
		while (pcur)
		{
			if (pcur->val == pos->val)
			{
				break;
			}
			prev = pcur;
			pcur = pcur->next;
		}
		// prev newnode pcur/pos
		prev->next = newnode;
		newnode->next = pcur;
	}
}

//在指定位置之后插入数据
void SLposback(SLNode* pos, SingleListdatatype x)
{
	assert(pos);
	SLNode* newnode = BuyNode(x);
	//pos newnode pos->next
	newnode->next = pos->next;
	pos->next = newnode;
}

//删除pos节点
void slpoppos(SLNode** plist, SLNode* pos)
{
	assert(plist && *plist);
	assert(pos);

	if (*plist == pos)
	{
		//pos是头节点,调用头删方法
		SLpopfront(plist);
	}
	else
	{
		SLNode* prev = *plist;
		while (prev->next != pos)
		{
			//找到pos的前一个节点
			prev = prev->next;
		}
		//prev pos pos->next
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

//删除pos节点后一个节点
void slpopposback(SLNode* pos)
{
	assert(pos && pos->next);//pos->next != NULL避免pos是尾节点

	SLNode* Next = pos->next;
	pos->next = pos->next->next;
	free(Next);
	Next = NULL;
}

//销毁链表
void DestorySL(SLNode** plist)
{
	assert(plist && *plist);
	SLNode* Next = (*plist)->next;
	while (Next)
	{
		free(*plist);
		*plist = Next;
		Next = Next->next;
	}
	free(*plist);
	*plist = NULL;
}

3.test.c 

#define _CRT_SECURE_NO_WARNINGS 1
#include "SingleList.h"

//void test1()
//{
//	SLNode* plist1 = BuyNode(1);
//	SLNode* plist2 = BuyNode(2);
//	SLNode* plist3 = BuyNode(3);
//	SLNode* plist4 = BuyNode(4);
//
//	plist1->next = plist2;
//	plist2->next = plist3;
//	plist3->next = plist4;
//
//	SLNodePrint(plist1);
//}


void test2()
{
	SLNode* plist = NULL;
	//测试尾插
	SLNodepushback(&plist, 1);
	SLNodepushback(&plist, 2);
	SLNodepushback(&plist, 3);
	SLNodepushback(&plist, 4);
	SLNodePrint(plist);

	//测试销毁链表
	DestorySL(&plist);

	//测试删除pos节点的下一个节点
	//SLNode* find = SLFind(plist, 4);
	//slpopposback(find);
	//SLNodePrint(plist);

	//测试删除pos节点
	//SLNode* find = SLFind(plist, 4);
	//slpoppos(&plist,find);
	//SLNodePrint(plist);


	//测试在指定位置之后插入数据
	//SLNode* find = SLFind(plist, 1);
	//SLposback(find,66);
	//SLNodePrint(plist);

	//测试在指定位置之前插入数据
	//SLNode* find = SLFind(plist, 4);
	//SLposfront(&plist,find,100);
	//SLNodePrint(plist);

	//测试查找
	//SLNode* find = SLFind(plist,99);
	//if (find == NULL)
	//{
	//	printf("找不到!");
	//}
	//else
	//{
	//	printf("找到了!");
	//}

	//测试头删
	//SLpopfront(&plist);
	//SLNodePrint(plist);//2 3 4
	//SLpopfront(&plist);
	//SLNodePrint(plist);//3 4
	//SLpopfront(&plist);
	//SLNodePrint(plist);//4
	//SLpopfront(&plist);
	//SLNodePrint(plist);//NULL
	//SLpopfront(&plist);
	//SLNodePrint(plist);


	//测试尾删
	//SLpopback(&plist);
	//SLNodePrint(plist);//1 2 3
	//SLpopback(&plist);
	//SLNodePrint(plist);//1 2
	//SLpopback(&plist);
	//SLNodePrint(plist);//1
	//SLpopback(&plist);
	//SLNodePrint(plist);//NULL


	//测试头插
	//SLpushfront(&plist,100);
	//SLNodePrint(plist);
	//SLpushfront(&plist,99);
	//SLNodePrint(plist);
}

int main()
{
	//test1();
	test2();
	return 0;
}

完!

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

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

相关文章

数仓开发流程规范

一、目的 数据研发规范化旨在为数据开发提供规范化的研发流程指导方法&#xff0c;目的是简化、规范化日常工作流程&#xff0c;提高工作效率&#xff0c;较少无效与冗余工作&#xff0c;赋能企业更强大的数据掌控力来应对海量增长的业务数据&#xff0c;从而释放更多的人力与…

keil的jlink重新选择芯片识别

keil选择jlink要选择对应芯片&#xff0c;一旦选择成功会出现以下文件 如果选择错了芯片类型&#xff0c;就需要删除这两个文件&#xff0c;然后重新进入选择&#xff0c;就可以了

神经网络与空间变换关系

神经网络的隐藏层实际上就是在进行一次空间变换&#xff0c;隐藏层中神经元的个数就是变换后空间的维度&#xff0c;代表可以升维也可以降维。 不同是 神经网络的一层运算不只有矩阵乘法&#xff0c;还会有一个加法。以及 进行完线性计算后&#xff0c;还要经过非线性的激活函…

泰迪智能科技企业数据挖掘流程分析及特色服务优势

企业发展会沉淀大量的数据&#xff0c;数据中囊括了企业业务各种维度指标&#xff0c;通过数据挖掘和数据分析 &#xff0c;让企业业务了解过去、现在和未来将要发生什么&#xff0c;从而更好的调整企业发展方向。泰迪智能科技企业数据挖掘平台是面向企业级用户快速处理数据构建…

微信小程序之简单的发送弹幕操作

大家看视频的时候是不是时不时会有弹幕飘过~ 在我们微信小程序当中&#xff0c;我们可以十分简单的实现&#xff0c;接下来为大家介绍一下吧&#xff01; 我们使用微信官方给我们的一个视频链接 "http://wxsnsdy.tc.qq.com/105/20210/snsdyvideodownload?filekey302802…

语言基础 /CC++ 可变参函数设计与实践,va_ 系列实战详解(强制参数和变参数的参数类型陷阱)

文章目录 概述va_ 系列定义va_list 类型va_start 宏从变参函数的强制参数谈起宏 va_start 对 char 和 short 类型编译告警宏 va_start 源码分析猜测 __va_start 函数实现 va_arg 宏宏 va_arg 无法接受 char 和 short为啥va_arg可解析int却不能解析float类型&#xff1f;宏 va_a…

Linux 第二十七章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

答辩PPT不会做?试试这些AI工具,一键生成

在我原本的认知里面&#xff0c;答辩PPT是要包含论文各个章节的&#xff0c;在答辩时需要方方面面都讲到的&#xff0c;什么摘要、文献综述、实证分析、研究结果样样不落。但是&#xff0c;这大错特错&#xff01; 答辩PPT环节时长一般不超过5分钟&#xff0c;老师想要的答辩P…

【JavaSE】/*初识Java*/

目录 一、了解 Java 语言 二、Java 语言的重要性 2.1 使用程度 2.2 工作领域 三、Java 语言的特性 四、Java 的基础语法 五、可能遇到的错误 六、第一个 java 程序代码解析 七、Java 注释 八、Java 标识符 九、Java 关键字 一、了解 Java 语言 Java 是由 Sun Micr…

2023年建筑特种作业人员安全生产知识试题

100分题库提供安全员考试试题、建筑安全员考试预测题、建筑安全员ABC考试真题、安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 判断题&#xff08;1-20&#xff09; 1.《建筑工程安全生产管理条例》是我国第一部关于…

mac苹果电脑卡顿反应慢如何解决?2024最新免费方法教程

苹果电脑以其稳定的性能、出色的设计和高效的操作系统&#xff0c;赢得了广大用户的喜爱。然而&#xff0c;随着时间的推移&#xff0c;一些用户会发现自己的苹果电脑开始出现卡顿、反应慢等问题。这不仅影响使用体验&#xff0c;还会影响工作效率。那么&#xff0c;面对这些问…

2024年旅游行业薪酬报告

来源&#xff1a;薪智 近期历史回顾&#xff1a; 2024年中国健康家电消费洞察及趋势研究报告.pdf 2024巴菲特股东大会5万字完整版.pdf 2024年全国大学生新媒体直播大赛.pdf 2024北京市高级别自动驾驶示范区数据安全治理白皮书.pdf 2024年第一季度开发者健康调查报告.pdf 2024年…

计算机毕业设计 | vue+springboot线上考试 在线测试系统(附源码)

1&#xff0c;项目介绍 项目背景 在线考试借助于网络来进行&#xff0c;传统考试所必备的考场和监考对于在线考试来说并不是必要项目&#xff0c;因此可以有效减少组织考试做需要的成本以及设施。同时&#xff0c;由于在线考试系统本身具有智能阅卷的功能&#xff0c;也大大减…

记录文件上传exists方法遇到的坑

1、问题 判断文件是否存在使用exist方法&#xff0c;官方的注释是这样的 百度翻译结果&#xff1a;true&#xff0c;当且仅当由该抽象路径名表示的文件或目录存在时&#xff1b;否则为false 2、实际返回 注意&#xff1a;实际上exsits方法的返回值与其官方注释的返回结果是相…

NSSCTF中的web学习(md5())

目录 MD5的学习 [BJDCTF 2020]easy_md5 [LitCTF 2023]Follow me and hack me [LitCTF 2023]Ping [SWPUCTF 2021 新生赛]easyupload3.0 [NSSCTF 2022 Spring Recruit]babyphp MD5的学习 md5()函数&#xff1a; md5($a)&#xff1a;返回a字符串的散列值 md5($a,TRUE)&…

一套全新的PACS医学存档影像系统源码 RIS和PACS系统分别在哪些方面发挥作用

RIS和PACS系统分别在哪些方面发挥作用 RIS系统的作用 放射信息系统&#xff08;RIS&#xff09;主要用于管理和调度患者的放射检查流程。它的主要功能包括患者管理、检查预约、报告生成等。RIS系统通常作为独立系统运行&#xff0c;侧重于临床流程管理&#xff0c;并优化放射…

关于docker network网络

首先,我们来看看Docker默认的网络模式,即docker0网桥。 每当你安装Docker时,它会创建一个名为docker0的虚拟网桥,并设置一个IP地址范围供它进行端口映射等工作。所有Docker容器在创建时,都会自动连接到这个docker0网桥,并分配一个虚拟IP地址。这样,容器与主机之间,以及容器与容…

3d里如何做螺旋状模型?---模大狮模型网

螺旋状模型在3D设计中常常被运用&#xff0c;不仅可以用于创造独特的装饰品和艺术品&#xff0c;还可以用于建筑设计、工程模拟等领域。然而&#xff0c;对于初学者而言&#xff0c;如何在3D软件中创建螺旋状模型可能是一个挑战。在本文中&#xff0c;我们将分享几种简单而有效…

Qt——信号 和 槽

目录 概述 信号和槽的使用 自定义信号和槽 带参数的信号和槽 概述 在Linux系统中&#xff0c;我们也介绍了信号的产生、信号的检测以及信号的处理机制&#xff0c;它就是系统内部的通知机制&#xff0c;也可以是一种进程间通信的方式。在系统中有很多信号&#xff0c;我们可…

镜舟科技亮相2024中国移动算力网络大会、Qcon、DTC等多项活动

在刚刚过去的 4 月份&#xff0c;镜舟科技受邀参与一系列技术交流活动&#xff0c;与移动云、金科创新社、infoQ、墨天轮、开科唯识等媒体及合作伙伴展开积极交流&#xff0c;并分享其在数据技术、金融等垂直行业领域的创新实践&#xff0c;从产业侧、业务侧、技术侧洞察需求、…