基于结点的数据结构——链表(单链表双向循环链表)| 附完整源码 | C语言版

news2024/12/23 14:46:12

本章内容

1.什么是链表

2.链表常见几种形式

3.无头单向非循环链表的实现

3.1结点结构的定义

3.2函数接口的实现

3.2.1尾插

3.2.2尾删

4. 带头双向循环链表的实现

4.1结点结构的定义

4.2函数接口的实现

5.两种链表的差异

①尾插与尾删的时间复杂度

②头插与头删的时间复杂度

③函数形参为何一个是二级指针,一个是一级指针?

完整源码

无头单向非循环链表

SList.h

SList.c

test.c

带头双向循环链表

List.h

List.c

test.c


1.什么是链表

像数组一样,链表也用来表示一系列的元素。事实上,能用数组来做的事情,一般也可以用链表来做。然而,链表的实现跟数组是不一样的,在不同场景它们会有不同的性能表现。

计算机的内存就像一大堆格子,每格都可以用来保存比特形式的数据。当要创建数组时,程序会在内存中找出一组连续的空格子,给它们起个名字,以便你的应用存放数据。

我们之前说过,计算机能够直接跳到数组的某一索引上。如果代码要求它读取索引 4的值,那么计算机只需一步就可以完成任务。重申一次,之所以能够这样,是因为程序事先知道了数组开头所在的内存地址——例如地址是 1000——当它想去索引 4时,便会自动跳到 1004处。

与数组不同的是,组成链表的格子不是连续的。它们可以分布在内存的各个地方。这种不相邻的格子,就叫作结点

那么问题来了,计算机怎么知道这些分散的结点里,哪些属于这个链表,哪些属于其他链表呢?这就是链表的关键了:每个结点除了保存数据,它还保存着链表里的下一结点的内存地址。

这份用来指示下一结点的内存地址的额外数据,被称为链。链表如下图所示。 

注意:

1.从上图可以看出,链式结构在逻辑上是来连续的,但是在物理上不一定连续。

2.现实中的结点一般都是从堆上来申请的。

3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。


2.链表常见几种形式

①单向或者双向

②带头或者不带头 

 ③循环或者非循环

虽然有这么多的链表的结构,但是我们实际中最常用到的还是这两种结构:

1. 无头单向非循环链表

结构简单,一般不会用来单独进行存储数据。实际中更多是作为其它数据结构的子结构,如哈希表、图的邻接表等等。另外这种结构在笔试面试中出现比较多。

2. 带头双向循环链表

结构最复杂,一般用于单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后发现结构会带来很多优势,实现反而简单了。


 

3.无头单向非循环链表的实现

就我个人而言,我认为虽然无头单向非循环链表是这几个链表中结构最简单的,但是实现却是最复杂的。我们学会了头单向非循环链表的实现,别的链表实现应当不在话下。

3.1结点结构的定义

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

如上文所讲,一个结点包含的内容有数据data和保存下一个结点的地址的指针。

链表正式由一个个这样的结点串连起来的,所以我们只需要记住排在最前面的头结点的位置,就能访问链表里任意一个结点。

注意:无头单向非循环链表里的头结点指的是链表中的第一个结点,它本身也存储着有效数据。而后面所讲的带头双向循环链表中的头结点仅仅是作为链表起始的标志,并不会存储有效数据。

3.2函数接口的实现

//申请一个结点
SLTNode* BuySLTNode(SLTDataType data);
//创建一个链表,包含数据为0~n
SLTNode* CreateSList(int n);
//释放内存
void SLTDestroy(SLTNode** pphead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType data);
//尾删
void SLTPopBack(SLTNode** pphead);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType data);
//头删
void SLTPopFront(SLTNode** pphead);
//查找data的结点
SLTNode* SLTFind(SLTNode** pphead, SLTDataType data);
//在pos位置前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType data);
//在pos位置后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType data);
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//打印链表内容
void SLTPrint(SLTNode* phead);

由于代码量过大,为了避免影响阅读体验太差,我将完整代码置于文章末尾。此处,我们来细致的学习两个函数尾插与尾删,其它函数与其原理几乎相同。

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType data);
//尾删
void SLTPopBack(SLTNode** pphead);

3.2.1尾插

void SLTPushBack(SLTNode** pphead, SLTDataType data)
{
	SLTNode* newNode = BuySLTNode(data);
	//若为第一次插入,则分情况处理
	if (*pphead==NULL)
	{
		*pphead = newNode;
		return;
	}

	//找尾
	SLTNode* tail = *pphead;
	while(tail->next)
	{
		tail = tail->next;
	}
	//找到了,进行尾插
	tail->next = newNode;
}

 首先将tail也指向头结点,通过变化tail来找到尾结点,如下图所示为找尾的过程:

此时,tail所指向的结点的next为NULL,则循环结束,进行尾插。尾插只需要将tail所指向的结点的next指针赋值为newNode即可。

3.2.2尾删

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	//分情况处理
	if (*pphead == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}

	//找尾
	SLTNode* tail = *pphead;
	while (tail->next->next)
	{
		tail = tail->next;
	}
	//找到了,进行尾删
	free(tail->next);
	tail->next = NULL;
}

同样的尾删也是先找尾,但与尾插的找尾有一点不同的是while循环的判断条件不相同。原因是我们删掉尾结点后,还有重要的一步是将尾结点的前一个结点(也就是新的尾结点)的next置为NULL。所以这里的tail指向的是尾结点的前一个结点。

此时tail->next->next 为NULL,所以循环结束,进行尾删。尾删只需要释放掉尾结点,并将新的尾结点的next置为NULL。

 


4. 带头双向循环链表的实现

带头双向循环链表看似结构复杂,其实在写代码时你会感到很轻松。其关键就在于它的头结点不一般。此处的头结点不存储有效数据。

4.1结点结构的定义

typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;//指向前一个结点
	struct ListNode* next;//指向后一个结点
}LTNode;

既然是双向循环链表,那就得要求每个结点既保存下一个结点的地址,又要保存上一个结点的地址。结构中,prev指向前一个结点,next指向后一个结点。

4.2函数接口的实现

//申请一个结点
LTNode* BuyListNode(LTDataType data);
//初始化头结点
LTNode* LTInit();
//释放申请的内存
void LTDestroy(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType data);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType data);
//头删
void LTPopFront(LTNode* phead);
//查找data
LTNode* LTFind(LTNode* phead, LTDataType data);
//在pos位置前插入
void LTInsert(LTNode* phead, LTNode* pos, LTDataType data);
//删除pos位置
void LTErase(LTNode* phead, LTNode* pos);
//判断链表是否为空
bool LTEmpty(LTNode* phead);
//统计链表中数据的个数
size_t LTSize(LTNode* phead);
//打印链表存储的数据
void LTPrint(LTNode* phead);

同样的,由于代码过长,为了避免影响阅读体验,我将完整源码置于文章末尾。带头双向循环链表的各个函数实现都比不带头的单链表简单很多。因此学会了之前单链表的实现,双向链表的实现自然轻松无比。这里就不做赘述。


5.两种链表的差异

①尾插与尾删的时间复杂度

对于单链表而言,尾插与尾删都是它的劣势。因为计算机无法直接访问到链表的尾结点,必须遍历完整个链表才能找到尾结点。所以单链表的尾插与尾删都为O(N)。

对于带头双向循环链表,找尾是极其方便的,因为尾结点就在头结点的前一个,可以一步就访问到。所以,带头双向循环链表的尾插与尾删都为O(1)。

②头插与头删的时间复杂度

两种链表头插与头删都为O(1)。对于链表这种数据结构而言,头插与头删都是其优势。上一章中提到顺序表的尾插与尾删效率极高,但是头插与头删却比较劣势。而链表正好相反,头插与头删效率很高。因此面对不同的场景,要学会使用合适的数据结构。

③函数形参为何一个是二级指针,一个是一级指针?

单链表,我们需要对phead的值做修改。那么就得利用phead的指针。而phead本身就是一个指针,所以函数的形参为pphead的二级指针。

双向循环链表,并不需要对phead做修改,而只是需要访问它prev和next所指向的结点,所以只用一级指针即可。


完整源码

无头单向非循环链表

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;

//申请一个结点
SLTNode* BuySLTNode(SLTDataType data);
//创建一个链表,包含数据为0~n
SLTNode* CreateSList(int n);
//释放内存
void SLTDestroy(SLTNode** pphead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType data);
//尾删
void SLTPopBack(SLTNode** pphead);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType data);
//头删
void SLTPopFront(SLTNode** pphead);
//查找data的结点
SLTNode* SLTFind(SLTNode** pphead, SLTDataType data);
//在pos位置前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType data);
//在pos位置后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType data);
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//打印链表内容
void SLTPrint(SLTNode* phead);

SList.c

#define _CRT_SECURE_NO_DEPRECATE 1
#include"SList.h"

SLTNode* BuySLTNode(SLTDataType data)
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	//检查是否申请成功
	if (newNode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	//对newNode进行初始化
	newNode->data = data;
	newNode->next = NULL;

	//返回申请成功的结点
	return newNode;
}

SLTNode* CreateSList(int n)
{
	SLTNode* phead = NULL;
	SLTNode* ptail = NULL;

	for (int i = 0; i < n; i++)
	{
		SLTNode* newNode = BuySLTNode(i);
		//若为第一个插入,则分情况处理
		if (phead == NULL)
		{
			phead = ptail = newNode;
		}
		else
		{
			ptail->next = newNode;
			ptail = newNode;
		}
	}
	return phead;
}

void SLTDestroy(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	//cur判断何时结束
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

void SLTPushBack(SLTNode** pphead, SLTDataType data)
{
	SLTNode* newNode = BuySLTNode(data);
	//若为第一次插入,则分情况处理
	if (*pphead==NULL)
	{
		*pphead = newNode;
		return;
	}

	//找尾
	SLTNode* tail = *pphead;
	while(tail->next)
	{
		tail = tail->next;
	}
	//找到了,进行尾插
	tail->next = newNode;
}
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	//分情况处理
	if (*pphead == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}

	//找尾
	SLTNode* tail = *pphead;
	while (tail->next->next)
	{
		tail = tail->next;
	}
	//找到了,进行尾删
	free(tail->next);
	tail->next = NULL;
}

void SLTPushFront(SLTNode** pphead, SLTDataType data)
{
	SLTNode* newNode = BuySLTNode(data);
	//进行头插
	newNode->next = *pphead;
	*pphead = newNode;
}

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

SLTNode* SLTFind(SLTNode** pphead, SLTDataType data)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		//找到了,返回cur
		if (cur->data == data)
		{
			return cur;
		}
		cur = cur->next;
	}
	//没找到,返回NULL
	return NULL;
}

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType data)
{
	assert(pos);
	SLTNode* newNode = BuySLTNode(data);
	//若pos恰好是phead,相当于进行头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead, data);
		return;
	}
	//找pos前一个指针
	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	//找到了,进行插入
	prev->next = newNode;
	newNode->next = pos;
}

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

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	//若pos恰好就是phead,则相当于头删
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
		return;
	}
	//找pos前一个结点
	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	//找到了,进行删除
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

void SLTPrint(SLTNode* phead)
{
	assert(phead);
	SLTNode* cur = phead;
	//cur控制何时结束
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

test.c

#define _CRT_SECURE_NO_DEPRECATE 1
#include"SList.h"

void test()
{
	SLTNode* phead = CreateSList(10);
	SLTPushBack(&phead, 0);
	SLTPushBack(&phead, 0);
	SLTPushBack(&phead, 0);
	SLTPushBack(&phead, 0);
	SLTPushBack(&phead, 0);
	SLTPrint(phead);

	SLTPushFront(&phead, 1);
	SLTPushFront(&phead, 1);
	SLTPushFront(&phead, 1);
	SLTPushFront(&phead, 1);
	SLTPushFront(&phead, 1);
	SLTPrint(phead);

	SLTNode* pos = SLTFind(&phead, 3);
	SLTInsert(&phead, pos, 300);
	SLTInsertAfter(pos, 30);
	SLTPrint(phead);
	SLTPopBack(&phead);
	SLTPopBack(&phead);
	SLTPopBack(&phead);
	SLTPopBack(&phead);
	SLTPopBack(&phead);

	SLTPopFront(&phead);
	SLTPopFront(&phead);
	SLTPopFront(&phead);
	SLTPopFront(&phead);
	SLTPopFront(&phead);
	SLTPrint(phead);
	SLTDestroy(&phead);
}
int main()
{
	test();
	return 0;
}

带头双向循环链表

List.h

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

typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;//指向前一个结点
	struct ListNode* next;//指向后一个结点
}LTNode;

//申请一个结点
LTNode* BuyListNode(LTDataType data);
//初始化头结点
LTNode* LTInit();
//释放申请的内存
void LTDestroy(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType data);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType data);
//头删
void LTPopFront(LTNode* phead);
//查找data
LTNode* LTFind(LTNode* phead, LTDataType data);
//在pos位置前插入
void LTInsert(LTNode* phead, LTNode* pos, LTDataType data);
//删除pos位置
void LTErase(LTNode* phead, LTNode* pos);
//判断链表是否为空
bool LTEmpty(LTNode* phead);
//统计链表中数据的个数
size_t LTSize(LTNode* phead);
//打印链表存储的数据
void LTPrint(LTNode* phead);

List.c

#define _CRT_SECURE_NO_DEPRECATE 1
#include"List.h"

LTNode* BuyListNode(LTDataType data)
{
	LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
	if (newNode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newNode->data = data;
	newNode->prev = NULL;
	newNode->next = NULL;

	return newNode;
}

LTNode* LTInit()
{
	LTNode* phead = BuyListNode(-1);
	phead->prev = phead;
	phead->next = phead;
	return phead;
}

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);
}

void LTPushBack(LTNode* phead, LTDataType data)
{
	assert(phead);
	LTNode* newNode = BuyListNode(data);
	LTNode* tail = phead->prev;

	newNode->next = phead;
	newNode->prev = tail;
	tail->next = newNode;
	phead->prev = newNode;
}

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* tail = phead->prev;
	tail->prev->next = phead;
	phead->prev = tail->prev;

	free(tail);
}

void LTPushFront(LTNode* phead, LTDataType data)
{
	assert(phead);
	LTNode* newNode = BuyListNode(data);
	
	newNode->next = phead->next;
	phead->next->prev = newNode;

	phead->next = newNode;
	newNode->prev = phead;
}

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* cur = phead->next;
	phead->next = cur->next;
	cur->next->prev = phead;

	free(cur);
}

LTNode* LTFind(LTNode* phead, LTDataType data)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data==data)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void LTInsert(LTNode* phead, LTNode* pos, LTDataType data)
{
	assert(phead);
	LTNode* newNode = BuyListNode(data);
	pos->prev->next = newNode;
	newNode->prev = pos->prev;

	newNode->next = pos;
	pos->prev = newNode;
}

void LTErase(LTNode* phead,LTNode* pos)
{
	assert(phead);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;

	free(pos);
}

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	if (phead->next == phead)
	{
		return true;
	}
	return false;
}

size_t LTSize(LTNode* phead)
{
	assert(phead);
	size_t size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

test.c

#define _CRT_SECURE_NO_DEPRECATE 1
#include"List.h"

void test()
{
	LTNode* phead = LTInit();
	LTPushBack(phead, 2);
	LTPushBack(phead, 1);
	LTPushBack(phead, 1);
	LTPushBack(phead, 1);
	LTPushBack(phead, 1);
	LTPushBack(phead, 1);
	LTPushFront(phead, 0);
	LTPushFront(phead, 0);
	LTPushFront(phead, 0);
	LTPushFront(phead, 0);
	LTPushFront(phead, 0);
	LTPrint(phead);
	LTPopFront(phead);
	LTPopFront(phead);
	LTPopFront(phead);
	LTPrint(phead);
	LTPopBack(phead);
	LTPopBack(phead);
	LTPopBack(phead);
	LTPrint(phead);
	LTNode* pos = LTFind(phead, 2);
	LTInsert(phead, pos, 200);
	LTInsert(phead, pos, 200);
	LTInsert(phead, pos, 200);
	LTPrint(phead);
	LTErase(phead, pos);
	LTPrint(phead);
	LTDestroy(phead);
}

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

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

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

相关文章

Ai 作图 stable-diffusion-webui prompt

文章参考了 prompt指导手册 &#xff1a; https://strikingloo.github.io/stable-diffusion-vs-dalle-2 https://prompthero.com/stable-diffusion-prompt-guide 一般来说&#xff0c;最好的稳定扩散提示会有这样的形式&#xff1a; “ [主要主题]的[图片类型] &#xff0…

C语言-文件操作(13.1)

目录 思维导图&#xff1a; 1. 为什么使用文件 2. 什么是文件 2.1 程序文件 2.2 数据文件 2.3 文件名 3. 文件的打开和关闭 3.1 文件指针 3.2 文件的打开和关闭 4. 文件的顺序读写 4.1 对比一组函数 5. 文件的随机读写 5.1 fseek 5.2 ftell 5.3 rewind 6. 文本…

FeignClient调用源码解析

文章目录一、FeignClient二、整体流程1.使用FeignClient2.FeignClient整体调用流程三、源码解析1. 注解EnableFeignClients2. FeignClientsRegistrar3. Feign其他配置4. FactoryBean5. 方法调用一、FeignClient FeignClient作为SrpingCloud进行http请求的一个基础组件&#xf…

IP地址和MAC地址是什么?Dhcp和arp又是什么?

本期武汉海翎光电的小编和大家聊聊 计算机是如何通信的&#xff1f;IP地址和MAC地址是什么&#xff1f;Dhcp和arp又是什么&#xff1f;在我们的家庭网络中&#xff0c;有许多的网络设备&#xff0c;比如我们可以有两台计算机A和B, 一台手机一台电视机&#xff0c;他们都连接到了…

【尚硅谷】Java数据结构与算法笔记09 - 哈希表

文章目录一、哈希表引入二、基本介绍三、Google公司的一个上机题3.1 题目描述3.2 代码实现一、哈希表引入 1&#xff09;看一个实际需求, google 公司的一个上机题: 2&#xff09;有一个公司, 当有新的员工来报道时, 要求将该员工的信息加入(id,性别,年龄, 住址…), 当输入该员…

【Linux】理解文件系统——软硬链接

我们之前讨论的都是进程和被打开文件的关系&#xff0c;而如果一个文件是没有被打开呢&#xff1f;没有被打开的文件操作系统如何管理&#xff1f; 没有被打开的文件在磁盘上&#xff0c;所以磁盘上有大量的文件&#xff0c;这些文件要被静态管理起来&#xff0c;方便我们随时…

1588_AURIX_TC275_PMU简介

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) PMU是编程存储单元的缩写&#xff0c;但是落实到了具体的硬件模块上其实是一个Flash模块。在TC275中&#xff0c;只有一个PMU模块。在所有的AURIX芯片中&#xff0c;只有PMU0支持BootROM的…

React--》React组件的三大核心属性

目录 state 事件绑定 props 函数式组件使用props refs state state是组件对象最重要的属性&#xff0c;值是对象(可以包含多个 key-value的组合)&#xff1b;组件被称为“状态机”&#xff0c;通过更新组件来对应页面显示(重新渲染组件)&#xff0c;也就是有状态组件&…

ASP.NET Core 3.1系列(29)——System.Text.Json实现JSON的序列化和反序列化

1、前言 在Web开发中&#xff0c;JSON数据可以说是无处不在。由于具有轻量、易读等优点&#xff0c;JSON已经成为当前主流的数据传输格式。在ASP.NET Core 3.0之前&#xff0c;大多数项目都会使用Newtonsoft.Json组件来实现JSON的序列化和反序列化操作&#xff0c;而从ASP.NET…

《王道》操作系统整理

操作系统第1章 OS概述第1节 OS基本概念第2节 OS发展与分类第3节 OS运行机制和体系结构1.3.1 操作系统的运行机制1. 时钟管理2. 中断机制3. 原语4. 系统资源管理或系统控制的数据结构及处理1.3.2 中断和异常1.3.3 系统调用第2章 进程管理第3章 内存管理第4章 文件管理第5章 IO管…

【8】SCI易中期刊推荐——计算机 | 人工智能(中科院4区)

🚀🚀🚀NEW!!!SCI易中期刊推荐栏目来啦 ~ 📚🍀 SCI即《科学引文索引》(Science Citation Index, SCI),是1961年由美国科学信息研究所(Institute for Scientific Information, ISI)创办的文献检索工具,创始人是美国著名情报专家尤金加菲尔德(Eugene Garfield…

【SpringCloud11】Hystrix断路器

Hystrix断路器1.概述1.1分布式系统面临的问题1.2Hystrix 是什么1.3Hystrix 的作用1.4官网资料1.5Hystrix官宣停更进维2.Hystrix重要概念2.1服务降级&#xff08;fallback&#xff09;2.2服务熔断&#xff08;break&#xff09;2.3服务限流&#xff08;flowlimit&#xff09;3.H…

手把手教你使用Python实现推箱子小游戏(附完整源码)

文章目录项目介绍项目规则项目接口文档项目实现过程前置方法编写move核心方法编写项目收尾项目完善项目整体源码项目缺陷分析项目收获与反思项目介绍 我们这个项目是一个基于Python实现的推箱子小游戏&#xff0c;名叫Sokoban&#xff1a; 这个游戏的目的是让玩家&#xff0…

jfow-代码分析

jfow-代码分析目录概述需求&#xff1a;设计思路实现思路分析1.代码&#xff1a;2.代码2&#xff1a;3.CashFrmTemplate4.chartType5.DataColumnData:参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xf…

Vue实战第1章:学习和使用vue-router

学习和使用vue-router 前言 本篇在讲什么 简单讲解关于vue-router的使用 仅介绍简单的应用&#xff0c;仅供参考 本篇适合什么 适合初学Vue的小白 适合想要自己搭建网站的新手 适合没有接触过vue-router的前端程序 本篇需要什么 对Html和css语法有简单认知 对Vue有…

2023/1/14 js基础学习

1 js基础学习-基本数据类型基本语法 请参考 https://blog.csdn.net/m0_48964052?typeblog https://gitee.com/hongjilin/hongs-study-notes/blob/master/%E7%BC%96%E7%A8%8B_%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/HTMLCSSJS%E5%9F%BA%E…

Arthas 入门到实战(二)在线热更新

1. 结合 jad/mc 命令在线修改使用 jad 命令: 将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码&#xff0c;便于你理解业务逻辑&#xff1b; mc命令&#xff1a;Memory Compiler/内存编译器&#xff0c;编译.java文件生成.class。 redefine命令&#xff1a;加载…

unix进程控制及进程环境--自APUE

文章目录概述1、孤儿进程和僵尸进程进程终止进程的编译和启动进程终止的步骤进程8种终止方式进程退出函数1&#xff1a;exit进程退出函数2&#xff1a;_exit进程退出函数3&#xff1a;_Exit注册终止处理程序&#xff1a;atexit环境变量通过main函数传参全局的环境变量表&#x…

uni-app跨端自定义指令实现按钮权限

前言 初看这个标题可能很迷&#xff0c;uni-app明明不支持自定义指令&#xff0c;这文章是在搞笑吗&#xff0c;本文对于uni-app自定义指令实现按钮权限的方式也有可能是多余&#xff0c;但为了给业务部门更友好的开发体验&#xff0c;还是做了一些可能没意义的操作&#xff0…

回顾2022,展望 2023

个人相关&#xff1a; PMP 因为疫情多次延期的PMP终于搞定&#xff0c;光环的PMP就是妥妥。基本只要认真做题和思考都会过。但是考试不仅仅是考试&#xff0c;有时候更多的是对项目发展和项目管理的思考&#xff1a;风险&#xff0c;里程碑&#xff0c;相关方&#xff0c;敏捷&…