通讯录的实现(单链表版本)

我们首先要知道通讯录的实现是基于单链表的基础上的,所以我们首先要搞懂单链表。(注意:今天的代码量较多),但这不是阻挡我们前进的脚步,冲冲冲!!!

单链表的简要概述

我们知道单链表是和顺序表有一点区别的:顺序表在物理存储结构上是连续的,在逻辑上也是连续的,而单链表只是在逻辑上是连续的,物理上并不连续。单链表是由一个一个的节点(每个节点由一个结构体组成)连接而成的,它们的地址并不连续,只是它每个节点里面存着下一个节点的地址,我们可以由某一个节点来找到它下一个节点,相当于每个节点依次这样链接起来,这就叫做逻辑上是连续的,而物理上不连续。像这样:

那么我们来实现一下这样的单链表。

简单定义和说明

我们将实现链表的增删查改写在SList.c的源文件里面,将各种函数声明写在SList.h中,将各种函数的测试写在test_1.c的源文件里面。我们来看看我们的头文件里面要实现的函数以及要使用到的头文件。

#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);
//在指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//链表的销毁
void SListDestroy(SLTNode** pphead);

注意:这里节点结构体重命名的原因是节点在后续中要多次用到,简化一下代码,而int重命名的原因是我们可以在此插入字符型数据,结构体数据类型,不止于int类型数据。

函数的实现

尾插函数的实现

我们要知道的是刚开始的时候链表是空的,我们要在一个空链表里面一个一个插入数据,而第一个插入的节点则被当作第一个节点,那么这个链表的起始位置要被改变。所以在形参中我们要传二级指针(要改变实参的地址)。而我们要知道的是再插入节点之前,我们要先申请节点空间才能插入进去。

//申请节点
SLTNode* SLTBuyNode(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 = SLTBuyNode(x);//尾插前先申请空间然后才能插入进去
	//分为空链表和非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾节点
		SLTNode* ptail = *pphead;
		while (ptail->next)//如果这里是空链表的话,会报错,不能对空指针进行解引用
		{
			ptail = ptail->next;
		}
		//找到了尾节点
		ptail->next = newnode;
	}
}

头插函数的实现

和尾插一样,插入之前要先申请一块节点空间再去插入。

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;//不管是空链表还是非空链表都可以插入,不像尾插要分情况
	*pphead = newnode;
}

尾删函数的实现

只有一个节点的链表和有多个节点的链表的情况有点不同,所以我们要分两种情况去讨论一下,当然在删除之前,链表不能为空。

//尾删
void SLTPopBack(SLTNode** pphead)
{
	//链表不能为空
	assert(pphead && *pphead);
	//判断链表节点有多个还是一个,如果没有分两种情况,程序出问题(结果出问题,但没报错)
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}

头删函数的实现

和尾删一样,在删除之前链表不能为空。但不需要分情况。

//头删
void SLTPopFront(SLTNode** pphead)
{
	//前提是链表不为空
	assert(pphead && *pphead);
	//不管链表是只有一个还是多个,都行通
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

查找函数的实现

我们需要遍历链表,查找数据,查到了就返回节点位置,没有查到返回空。

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pur = phead;
	while (pur)
	{
		if (pur->data == x)
		{
			return pur;
		}
		pur = pur->next;
	}
	//遍历了一遍,没有找到
	return NULL;
}

在指定位置之前插入节点函数实现

要实现在指定的节点位置插入节点,我们需要利用上一个查找节点的函数来找到指定位置,然后进行下一步的实现。

//在指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	//不能为空链表
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//如果在头节点之前插入,将找不到pos,所以分两种情况
	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}//利用头插函数就行
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//将 prev newnode pos 链接起来
		prev->next = newnode;
		newnode->next = pos;
	}
}

在指定位置之后插入节点函数的实现

//在指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	//不能为空链表
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//将 pos newnode pos->next 链接起来
	newnode->next = pos->next;
	pos->next = newnode;
}

删除指定位置的节点

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	//链表不能为空以及pos不能为空
	assert(pphead && *pphead);
	assert(pos);
	//两种情况一种pos为头节点,一种不为头节点
	if (*pphead == pos)
	{
		//头删
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//将prev pos pos->next 中的pos删除
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

删除指定位置之后的位置节点函数的实现

我们要区分要下删除指定位置的节点函数的代码逻辑。

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	//pos之后不能为空,要不然怎么删除
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	//pos del del->next  删除pos
	pos->next = del->next;
	free(del);
	del = NULL;
}

链表的销毁

链表节点创建出来,不用了的话我们就要销毁,养成好的代码习惯。

//链表的销毁
void SListDestroy(SLTNode** pphead)
{
	//空链表就不需要销毁
	assert(pphead && *pphead);
	SLTNode* pur = *pphead;
	while (pur)
	{
		SLTNode* next = pur->next;
		free(pur);
		pur = next;
	}
	*pphead = NULL;
}

通讯录的简单定义和说明

我们将通讯录的代码实现要用到的函数实现写到Contact.c的源文件中,将所用到的函数声明写到Contact.h文件中。

Contact.h函数声明如下:

#pragma once
#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100
//前置声明
typedef struct SListNode contact;
//用户数据
typedef struct PersonInfo
{
    char name[NAME_MAX];//姓名
    char sex[SEX_MAX];//性别
    int age;//年龄
    char tel[TEL_MAX];//电话
    char addr[ADDR_MAX];//地址
}PeoInfo;
//初始化通讯录
void InitContact(contact** con);
//添加通讯录数据
void AddContact(contact** con);
//删除通讯录数据
void DelContact(contact** con);
//展示通讯录数据
void ShowContact(contact* con);
//查找通讯录数据
void FindContact(contact* con);
//修改通讯录数据
void ModifyContact(contact** con);
//销毁通讯录数据
void DestroyContact(contact** con);

初始化通讯录函数的实现

//初始化通讯录
void InitContact(contact** con)
{
	*con = NULL;
}

添加通讯录数据函数的实现

注意:这个函数的实现,可以调用单链表函数来实现。调用尾插或头插都行。

//添加通讯录数据
void AddContact(contact** con)
{
	//尾插和头插两种方法
	PeoInfo infor;
	printf("请输入姓名!\n");
	scanf("%s", infor.name);
	printf("请输入性别!\n");
	scanf("%s", infor.sex);
	printf("请输入年龄!\n");
	scanf("%d", &(infor.age));
	printf("请输入电话!\n");
	scanf("%s", infor.tel);
	printf("请输入地址!\n");
	scanf("%s", infor.addr);
	//采用尾插插入数据,也可采用头插,单链表中有现成的函数
	SLTPushBack(con, infor);
}

展示通讯录数据函数的实现

//展示通讯录数据
void ShowContact(contact* con)
{
	printf("姓名  性别  年龄  电话  地址\n");
	contact* pcur = con;
	while (pcur)
	{
		printf("%s     %s     %d    %s   %s\n", pcur->data.name,pcur->data.sex,
			pcur->data.age,pcur->data.tel,
			pcur->data.addr);
		pcur = pcur->next;
	}
}

查找通讯录的数据函数的实现

//查找通讯录数据
void FindContact(contact* con)
{
	char name[NAME_MAX];
	printf("请输入要查找联系人的信息!\n");
	scanf("%s", name);
	contact* pcur = con;
	while (pcur)
	{
		if (strcmp(pcur->data.name, name) == 0)
		{
			printf("姓名  性别  年龄  电话  地址\n");
			printf("%s     %s     %d    %s   %s\n", pcur->data.name, pcur->data.sex,
				pcur->data.age, pcur->data.tel,
				pcur->data.addr);
			return;
		}
		pcur = pcur->next;
	}
	printf("没有此联系人!\n");
}

删除通讯录函数的实现

我们首先要找到你需要删除的联系人地址,所以我们要封装另一个函数。并且删除函数单链表里面有现成函数,我们可以直接调用。

//查找联系人数据的指针
contact* Findaddr(contact** con,char*name)
{
	contact* pcur = *con;
	while (pcur)
	{
		if (strcmp(pcur->data.name, name) == 0)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//删除通讯录数据
void DelContact(contact** con)
{
	assert(con && &con);
	char name[NAME_MAX];
	printf("请输入你要删除的联系人姓名!\n");
	scanf("%s", name);
	contact*find=Findaddr(con, name);
	if (find == NULL)
	{
		printf("没有此联系人!\n");
	}
	else
	{
		SLTErase(con, find);
		printf("删除成功!\n");
	}
}

修改通讯录数据函数的实现

//修改通讯录数据
void ModifyContact(contact** con)
{
	//修改之前联系人要存在
	char name[NAME_MAX];
	printf("请输入要修改的联系人姓名!\n");
	scanf("%s", name);
	contact* find = Findaddr(con, name);
	if (find == NULL)
	{
		printf("该联系人不存在!\n");
		return;
	}
	else
	{
		PeoInfo infor;
		printf("请输入新的姓名!\n");
		scanf("%s", infor.name);
		printf("请输入新的性别!\n");
		scanf("%s", infor.sex);
		printf("请输入新的年龄!\n");
		scanf("%d", &(infor.age));
		printf("请输入新的电话!\n");
		scanf("%s", infor.tel);
		printf("请输入新的地址!\n");
		scanf("%s", infor.addr);
		find->data = infor;
	}
}

销毁通讯录数据函数的实现

//销毁通讯录数据
void DestroyContact(contact** con)
{
	assert(con && *con);
	SListDestroy(con);
}

通讯录菜单的封装

最终我们将所以函数进行一个封装汇总。

void menu()
{
	printf("********通讯录*******\n");
	printf("请选择你的操作:\n");
	printf("1.添加联系人   2.删除联系人\n");
	printf("3.展示联系人   4.查找联系人\n");
	printf("5.修改联系人   0.退出通讯录\n");
}
int main()
{
	contact* con;
	InitContact(&con);//初始化
	int input;
	do
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:AddContact(&con);
			break;
		case 2:DelContact(&con);
			break;
		case 3:ShowContact(con);
			break;
		case 4:FindContact(con);
			break;
		case 5:ModifyContact(&con);
			break;
		case 0:printf("退出成功!\n");
			break;
		default:printf("选项错误,请重新选择!\n");
			break;
		}
	} while (input);
	DestroyContact(&con);
	return 0;
}

总代码汇总

SList.c文件

#include"SList.h"
//打印
void SLTprint(SLTNode* phead)
{
	SLTNode* pur = phead;
	while (pur)
	{
		printf("%d->", pur->data);
		pur = pur->next;
	}
	printf("NULL\n");
}
//申请节点
SLTNode* SLTBuyNode(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 = SLTBuyNode(x);//尾插前先申请空间然后才能插入进去
	//分为空链表和非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾节点
		SLTNode* ptail = *pphead;
		while (ptail->next)//如果这里是空链表的话,会报错,不能对空指针进行解引用
		{
			ptail = ptail->next;
		}
		//找到了尾节点
		ptail->next = newnode;
	}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;//不管是空链表还是非空链表都可以插入,不像尾插要分情况
	*pphead = newnode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
	//链表不能为空
	assert(pphead && *pphead);
	//判断链表节点有多个还是一个,如果没有分两种情况,程序出问题(结果出问题,但没报错)
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
	//前提是链表不为空
	assert(pphead && *pphead);
	//不管链表是只有一个还是多个,都行通
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}
//查找
//SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
//{
//	SLTNode* pur = phead;
//	while (pur)
//	{
//		if (pur->data == x)
//		{
//			return pur;
//		}
//		pur = pur->next;
//	}
//	遍历了一遍,没有找到
//	return NULL;
//}
//在指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	//不能为空链表
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//如果在头节点之前插入,将找不到pos,所以分两种情况
	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}//利用头插函数就行
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//将 prev newnode pos 链接起来
		prev->next = newnode;
		newnode->next = pos;
	}
}
//在指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	//不能为空链表
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//将 pos newnode pos->next 链接起来
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	//链表不能为空以及pos不能为空
	assert(pphead && *pphead);
	assert(pos);
	//两种情况一种pos为头节点,一种不为头节点
	if (*pphead == pos)
	{
		//头删
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//将prev pos pos->next 中的pos删除
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	//pos之后不能为空,要不然怎么删除
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	//pos del del->next  删除pos
	pos->next = del->next;
	free(del);
	del = NULL;
}
//链表的销毁
void SListDestroy(SLTNode** pphead)
{
	//空链表就不需要销毁
	assert(pphead && *pphead);
	SLTNode* pur = *pphead;
	while (pur)
	{
		SLTNode* next = pur->next;
		free(pur);
		pur = next;
	}
	*pphead = NULL;
}

SList.h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"Contact.h"
//定义节点
typedef PeoInfo 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);
//在指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//链表的销毁
void SListDestroy(SLTNode** pphead);

Contact.h文件

#pragma once
#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100
//前置声明
typedef struct SListNode contact;
//用户数据
typedef struct PersonInfo
{
    char name[NAME_MAX];//姓名
    char sex[SEX_MAX];//性别
    int age;//年龄
    char tel[TEL_MAX];//电话
    char addr[ADDR_MAX];//地址
}PeoInfo;
//初始化通讯录
void InitContact(contact** con);
//添加通讯录数据
void AddContact(contact** con);
//删除通讯录数据
void DelContact(contact** con);
//展示通讯录数据
void ShowContact(contact* con);
//查找通讯录数据
void FindContact(contact* con);
//修改通讯录数据
void ModifyContact(contact** con);
//销毁通讯录数据
void DestroyContact(contact** con);

Contact.c文件

#include"SList.h"
//初始化通讯录
void InitContact(contact** con)
{
	*con = NULL;
}
//添加通讯录数据
void AddContact(contact** con)
{
	//尾插和头插两种方法
	PeoInfo infor;
	printf("请输入姓名!\n");
	scanf("%s", infor.name);
	printf("请输入性别!\n");
	scanf("%s", infor.sex);
	printf("请输入年龄!\n");
	scanf("%d", &(infor.age));
	printf("请输入电话!\n");
	scanf("%s", infor.tel);
	printf("请输入地址!\n");
	scanf("%s", infor.addr);
	//采用尾插插入数据,也可采用头插,单链表中有现成的函数
	SLTPushBack(con, infor);
}
//展示通讯录数据
void ShowContact(contact* con)
{
	printf("姓名  性别  年龄  电话  地址\n");
	contact* pcur = con;
	while (pcur)
	{
		printf("%s     %s     %d    %s   %s\n", pcur->data.name,pcur->data.sex,
			pcur->data.age,pcur->data.tel,
			pcur->data.addr);
		pcur = pcur->next;
	}
}
//查找通讯录数据
void FindContact(contact* con)
{
	char name[NAME_MAX];
	printf("请输入要查找联系人的信息!\n");
	scanf("%s", name);
	contact* pcur = con;
	while (pcur)
	{
		if (strcmp(pcur->data.name, name) == 0)
		{
			printf("姓名  性别  年龄  电话  地址\n");
			printf("%s     %s     %d    %s   %s\n", pcur->data.name, pcur->data.sex,
				pcur->data.age, pcur->data.tel,
				pcur->data.addr);
			return;
		}
		pcur = pcur->next;
	}
	printf("没有此联系人!\n");
}
//查找联系人数据的指针
contact* Findaddr(contact** con,char*name)
{
	contact* pcur = *con;
	while (pcur)
	{
		if (strcmp(pcur->data.name, name) == 0)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
//删除通讯录数据
void DelContact(contact** con)
{
	assert(con && &con);
	char name[NAME_MAX];
	printf("请输入你要删除的联系人姓名!\n");
	scanf("%s", name);
	contact*find=Findaddr(con, name);
	if (find == NULL)
	{
		printf("没有此联系人!\n");
	}
	else
	{
		SLTErase(con, find);
		printf("删除成功!\n");
	}
}
//修改通讯录数据
void ModifyContact(contact** con)
{
	//修改之前联系人要存在
	char name[NAME_MAX];
	printf("请输入要修改的联系人姓名!\n");
	scanf("%s", name);
	contact* find = Findaddr(con, name);
	if (find == NULL)
	{
		printf("该联系人不存在!\n");
		return;
	}
	else
	{
		PeoInfo infor;
		printf("请输入新的姓名!\n");
		scanf("%s", infor.name);
		printf("请输入新的性别!\n");
		scanf("%s", infor.sex);
		printf("请输入新的年龄!\n");
		scanf("%d", &(infor.age));
		printf("请输入新的电话!\n");
		scanf("%s", infor.tel);
		printf("请输入新的地址!\n");
		scanf("%s", infor.addr);
		find->data = infor;
	}
}
//销毁通讯录数据
void DestroyContact(contact** con)
{
	assert(con && *con);
	SListDestroy(con);
}

test_1.c文件

#include"SList.h"
void menu()
{
	printf("********通讯录*******\n");
	printf("请选择你的操作:\n");
	printf("1.添加联系人   2.删除联系人\n");
	printf("3.展示联系人   4.查找联系人\n");
	printf("5.修改联系人   0.退出通讯录\n");
}
int main()
{
	contact* con;
	InitContact(&con);//初始化
	int input;
	do
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:AddContact(&con);
			break;
		case 2:DelContact(&con);
			break;
		case 3:ShowContact(con);
			break;
		case 4:FindContact(con);
			break;
		case 5:ModifyContact(&con);
			break;
		case 0:printf("退出成功!\n");
			break;
		default:printf("选项错误,请重新选择!\n");
			break;
		}
	} while (input);
	DestroyContact(&con);
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

剖析 SPI 在 Spring 中的应用

一、概述 SPI&#xff08;Service Provider Interface&#xff09;&#xff0c;是Java内置的一种服务提供发现机制&#xff0c;可以用来提高框架的扩展性&#xff0c;主要用于框架的开发中&#xff0c;比如Dubbo&#xff0c;不同框架中实现略有差异&#xff0c;但核心机制相同…

构建第一个ArkTS应用之stateStyles:多态样式

Styles和Extend仅仅应用于静态页面的样式复用&#xff0c;stateStyles可以依据组件的内部状态的不同&#xff0c;快速设置不同样式。这就是我们本章要介绍的内容stateStyles&#xff08;又称为&#xff1a;多态样式&#xff09;。 概述 stateStyles是属性方法&#xff0c;可以…

如何发布自己的Python库?

Python包发布 1、背景概述2、操作指南 1、背景概述 为什么我们要发布自己的Python库&#xff1f;如果你想让你的Python代码&#xff0c;通过pip install xxx的方式供所有人下载&#xff0c;那就需要将代码上传到PyPi上&#xff0c;这样才能让所有人使用 那么&#xff0c;如何发…

【最新整理】3ds Max 大佬都在用的10款爆火插件推荐!

在3D建模和渲染领域&#xff0c;熟悉使用各种插件已经成为了大佬们的标配&#xff0c;而3ds Max作为最受欢迎的三维建模软件之一&#xff0c;更是有着丰富的插件资源。今天&#xff0c;小编将为大家盘点一下最新整理的10款爆火插件&#xff0c;这些插件不仅能够提升你的工作效率…

集合体系java

Collection:单列集合&#xff1a;每个元素只包含一个值 Collection集合存储的是地址 Collection的三种遍历方法如下 //迭代器是用来遍历集合的专用方式&#xff08;数组没有迭代器&#xff09;&#xff0c;在java中迭代器的代表是Iterator //boolean hasNext():询问当前位置…

10万字208道Java经典面试题总结(2024修订版)- SSM篇

&#x1f345; 作者简介&#xff1a;哪吒&#xff0c;CSDN2021博客之星亚军&#x1f3c6;、新星计划导师✌、博客专家&#x1f4aa; &#x1f345; 哪吒多年工作总结&#xff1a;Java学习路线总结&#xff0c;搬砖工逆袭Java架构师 &#x1f345; 技术交流&#xff1a;定期更新…

(三)C++自制植物大战僵尸游戏项目结构说明

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/ErelL 一、项目结构 打开项目后&#xff0c;在解决方案管理器中有五个项目&#xff0c;分别是libbox2d、libcocos2d、librecast、libSpine、PlantsVsZombies五个项目&#xff0c;除PlantsVsZombies外&#xff0c;其他四个…

map与set

set使用 set在我们就是我们前面学习的k模型&#xff0c;它可以用来比对数据&#xff0c;增删查的时间复杂度都是O&#xff08;logn&#xff09;效率非常高&#xff0c;由于它底层的原因&#xff0c;它也可以实现排序&#xff0c;通过中序遍历可以输出我们的有序的数据&#xff…

#新版Onenet云平台使用(ESP8266 AT指令上报数据以及公网MQTT服务器连接测试)

1.上云方式&#xff1a;MQTT 参考&#xff1a; 新版ONENET物联网开放平台ATMQTT指令连接_at指令连接onenet的mqtt-CSDN博客https://blog.csdn.net/lilbye/article/details/131770196 ESP8266-01s入门&#xff1a;AT指令讲解、上云与MQTT通信教程-物联沃-IOTWORD物联网https:…

软考 系统架构设计师系列知识点之大数据设计理论与实践(5)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之大数据设计理论与实践&#xff08;4&#xff09; 所属章节&#xff1a; 第19章. 大数据架构设计理论与实践 第3节 Lambda架构 19.3.1 Lambda架构对大数据处理系统的理解 Lambda架构由Storm的作者Nathan Marz提出&…

SpringCloud实用篇(四)——Nacos

Nacos nacos官方网站&#xff1a;https://nacos.io/ nacos是阿里巴巴的产品&#xff0c;现在是springcloud的一个组件&#xff0c;相比于eureka的功能更加丰富&#xff0c;在国内备受欢迎 nacos的安装 下载地址&#xff1a;https://github.com/alibaba/nacos/releases/ 启动…

vscode远程连接centos

文章目录 vacode连接linux1. 安装插件2. 查看配置3. 打开ssh4. 远程连接 vacode连接linux 1. 安装插件 在扩展栏搜索remote &#xff0c;找到Remote Development插件&#xff0c;进行安装&#xff1a; 2. 查看配置 打开自己的linux终端&#xff0c;输入ifconfig&#xff0c;…

BackTrader 中文文档(九)

原文&#xff1a;www.backtrader.com/ 期货和现货补偿 原文&#xff1a;www.backtrader.com/docu/order-creation-execution/futurespot/future-vs-spot/ 发布1.9.32.116添加了对在社区中提出的一个有趣用例的支持。 通过未来开始交易&#xff0c;其中包括实物交割 让指标告诉…

怎么做预约小程序_探索我们的全新预约小程序

在繁忙的现代生活中&#xff0c;无论是想预约一次美容护理&#xff0c;还是预定一家心仪的餐厅&#xff0c;亦或是安排一次专业的咨询服务&#xff0c;我们都希望能够在最短的时间内完成这些操作&#xff0c;节省时间和精力。如今&#xff0c;一款全新的预约小程序应运而生&…

SSH安全设置

今天发现自己的公有云服务器被攻击了 然后查看了登录日志&#xff0c;如上图 ls -sh /var/log/secure vim /var/log/secure然后增加了安全相关的设置 具体可以从以下方面增加安全性&#xff1a; 修改默认SSH端口公有云修改安全组策略及防火墙端口设置登录失败次数锁定用户及限…

亚马逊CloudFront使用体验

前言 首先在体验CloudFront之前&#xff0c;先介绍一下什么是CDN&#xff0c;以及CDN的基本原理。 CDN是Content Delivery Network&#xff08;内容分发网络&#xff09;的缩写&#xff0c;是一种利用分布式节点技术&#xff0c;在全球部署服务器&#xff0c;即时地将网站、应…

LSTM 循环神经网络原理深度解读与网络结构精细剖析

长短期记忆网络&#xff08;Long Short-Term Memory, LSTM&#xff09;是一种特殊的循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;&#xff0c;设计用于解决长期依赖问题&#xff0c;特别是在处理时间序列数据时。 循环神经网络&#xff08;RNN&#xf…

每日一VUE——组件的生命周期

文章目录 VUE组件的生命周期生命周期钩子函数实例创建Teleport VUE组件的生命周期 组件生命周期 组件从创建到挂载、更新、到销毁的一系列过程被称为组件的生命周期。 生命周期函数 在组件的各个生命周期节点执行的函数&#xff0c;为生命周期钩子函数。 生命周期钩子函数…

RT-thread信号量与互斥量

1,信号量 信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。理解资源计数适合于线程间工作处理速度不匹配的场合;信号量在大于0时才能获取,在中断、线程中均可释放信号量。 为了体现使用信号量来达到线程间的同步,…