【数据结构】线性表之《无头单链表》超详细实现

单链表

  • 一.链表的概念及结构
  • 二.顺序表与链表的区别与联系
  • 三.单链表的实现
    • 1.创建单链表
    • 2.初始化单链表
    • 3.购买节点
    • 4.打印单链表
    • 5.插入操作
      • 1.头插
      • 2.尾插
      • 3.给定位置之前插入
    • 6.删除操作
      • 1.头删
      • 2.尾删
      • 3.删除给定位置的结点
    • 7.查找数据
    • 8.修改数据
    • 9.求单链表长度
    • 10.清空单链表
    • 11.销毁单链表
  • 四.模块化源代码
    • 1.SingleLinkList.h
    • 2.SingleLinkList.c
    • 3.test.c
  • 五.链表必做OJ题

前言:在前一章节成功实现了顺序表后,对数据结构的理解已经初具雏形,但这只是启蒙阶段,接下来我们将进入链表的探索学习。链表作为数据结构的另一种形式,不仅仅是简单的表述,它承载了更多的内涵和抽象思维,有助于深入理解数据在计算机科学中的精髓。

一.链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。

typedef int SLLDataType; //增强程序的可维护性

typedef struct SLLNode
{
	SLLDataType data;     //数据域
	struct SLLNode* next; //指针域
}SLLNode;

在这里插入图片描述
实际中要实现的链表结构非常多样(2^3=8中)。

  1. 单向,双向。
  2. 带头,不带头。
  3. 循环,非循环。

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
在这里插入图片描述

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

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

二.顺序表与链表的区别与联系

顺序表
优点:空间连续,支持随机访问。
缺点:如果空间不够要增容,增容会付出一定的性能消耗,其次可能存在一定的空间浪费;头部或者中部左右的插入 ,删除效率低——>O(N)。

链表
优点:任意位置的插入删除的时间复杂度为O(1);没有增容消耗,按需申请节点空间,但是不用了记得直接释放。
缺点:以节点为单位存储,不支持随机访问。

三.单链表的实现

1.创建单链表

链表由节点组成,每个节点要存放数据下一个节点的地址(为了找到下一个节点)。由于存放的是不同类型的数据,所以定义一个结构体,成员则有:数据域,指针域。

单链表:指向该节点的指针。

typedef int SLLDataType; //增强程序的可维护性

typedef struct SLLNode //单链表节点
{
	SLLDataType data;     //数据域
	struct SLLNode* next; //指针域
}SLLNode;

SLLNode* plist;//单链表

2.初始化单链表

注意:以下函数中的参数 phead 对应 plistpphead 对应 &plist

为什么这么做呢?
答:值传递地址传递的问题。

  1. 无需改变链表的话(比如:打印链表,查找数据等…),只需传入值。
  2. 需要改变链表的话(头插,头删等…),需要传入地址。
  3. 即使 plist 是一级指针,但是传参时仍会创建 phead 存放 plist 。本质依旧是传值。

在这里插入图片描述

一般初始化我们都习惯赋值为0,即单链表plist(*pphead)赋值为NULL。

void SLLInit(SLLNode** pphead)
{
	assert(pphead); //断言
	*pphead = NULL;
}

3.购买节点

由于头插,尾插,按位置插入链表,都要先准备一个节点。为了减少代码的重复,直接对其进行封装,创建新节点的时候直接调用该接口就行。

SLLNode* BuyNode(SLLDataType x)
{
	SLLNode* newNode = (SLLNode*)malloc(sizeof(SLLNode)); //申请节点空间
	if (newNode == NULL) //申请失败
	{
		perror("malloc fail");
		exit(1);
	}
	//申请成功
	newNode->data = x;
	newNode->next = NULL;
	return newNode; //返回新节点
}

4.打印单链表

定义一个指针指向单链表,利用NULL这一结束条件,循环遍历打印即可,较为简单。

void SLLPrint(SLLNode* phead)
{
	SLLNode* cur = phead; //定位单链表的头节点
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next; //更新为下一节点
	}
	printf("NULL\n");
}

5.插入操作

1.头插

头插思想:创建新节点,新节点的指针域指向单链表的头节点(实际上就是单链表),再更新单链表的头节点指向新节点。

void SLLPushFront(SLLNode** pphead, SLLDataType x)
{
	assert(pphead);

	SLLNode* newNode = BuyNode(x); //购买节点
	newNode->next = *pphead; //新节点头插
	*pphead = newNode; //更新单链表的头节点
}

2.尾插

尾插思想

  1. 当单链表为NULL:单链表的头指针指向新节点。
  2. 当单链表不为NULL:找到尾节点,尾节点的指针域指向新节点。
void SLLPushBack(SLLNode** pphead, SLLDataType x)
{
	assert(pphead);

	SLLNode* newNode = BuyNode(x); //购买节点
	if (*pphead == NULL) //单链表为空
	{
		*pphead = newNode; //更新单链表的头节点
	}
	else //单链表不为空
	{
		SLLNode* tail = *pphead; //寻找尾节点
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode; //尾节点,链接新节点
	}
}

3.给定位置之前插入

思路:

  1. 当给定的位置恰好是头节点的地址时,直接调用头插。
  2. 否则要寻找 pos 指向的节点的前一个节点,新节点的指针域指向 pos 指向的节点,前一个节点的指针域指向新节点。
void SLLInsert(SLLNode** pphead, SLLNode* pos, SLLDataType x)
{
	assert(pphead);
	assert(pos);
	
	if (pos == *pphead) //pos是头节点的地址
	{
		SLLPushFront(pphead, x); //直接头插
	}
	else
	{
		SLLNode* newNode = BuyNode(x); //购买节点
		SLLNode* prev = *pphead; //定位pos前一个节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//插入操作
		newNode->next = pos; 
		prev->next = newNode; 
	}
}

6.删除操作

1.头删

思路:

  1. 当单链表为NULL:无需操作。
  2. 当单链表不为NULL:先保存头节点的下一个节点的指针,再释放头指针,最后更新头节点为保存的哪个头节点。
void SLLPopFront(SLLNode** pphead)
{
	assert(pphead);

	//单链表为空
	if (*pphead == NULL)
	{
		return; //无需释放,直接退出
	}
	else
	{
		SLLNode* cur = (*pphead)->next; //定位头节点的下一个节点
		free(*pphead); //释放头节点
		*pphead = cur; //更新单链表的头节点
	}
}

2.尾删

思路略复杂:

  1. 当单链表为NULL:无需操作。
  2. 当单链表只有一个节点:释放头节点,将头指针置为NULL。
  3. 当单链表有多个节点:先找到尾节点的前一个节点并保存,释放尾节点,将保存的节点的指针域置为NULL。
void SLLPopBack(SLLNode** pphead)
{
	assert(pphead);

	//单链表为空
	if (*pphead == NULL)
	{
		return; //无需释放,直接退出
	}
	//单链表只有一个节点
	else if ((*pphead)->next == NULL) //注意:加上括号
	{
		free(*pphead); //释放头节点
		*pphead = NULL; //单链表置为NULL
	}
	//单链表有多个节点
	else
	{
		SLLNode* prev = NULL; //定位尾节点前一个节点
		SLLNode* tail = *pphead; //定位尾节点
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail); //释放尾节点
		tail = NULL; //置为NULL,预防野指针
		prev->next = NULL; //变为尾节点后置为NULL
	}
}

3.删除给定位置的结点

思路:

  1. 当待删除的节点为头节点时,直接调用头删即可。
  2. 否则保存待删除的节点的前一个节点,将该节点的指针域指向待删除的节点的下一个节点,最后释放待删除的节点即可。
void SLLErase(SLLNode** pphead, SLLNode* pos)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead) //pos是头节点的地址
	{
		SLLPopFront(pphead); //直接头删
	}
	else
	{
		SLLNode* prev = *pphead; //定位pos前一个节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next; //删除过程
		free(pos); //释放节点
		pos = NULL; //置为NULL,预防野指针
	}
}

7.查找数据

思路:循环遍历单链表即可,找到返回地址,未找到返回NULL。

SLLNode* SLLFind(SLLNode* phead, SLLDataType x)
{
	SLLNode* cur = phead; //定位值为x的节点
	while (cur != NULL) //遍历单链表
	{
		if (cur->data == x)
		{
			return cur; //找到了,返回节点的地址
		}
		cur = cur->next;
	}
	return NULL; //找不到,返回NULL
}

8.修改数据

思路:直接通过SLLFind函数得到地址,在该处修改即可,较为简单,同时SLLErase与SLLInsert函数都要通过SLLFind函数得到地址。

void SLLModify(SLLNode** pphead, SLLNode* pos, SLLDataType x)
{
	assert(pphead);
	assert(pos); //防止对NULL解引用导致程序崩溃
	pos->data = x; //直接修改就行了
}

9.求单链表长度

思路:利用头指针,向后循环遍历直到不为空即可。

int SLLLength(SLLNode* phead)
{
	int len = 0;
	SLLNode* cur = phead;
	while (cur != NULL)
	{
		cur = cur->next;
		len++;
	}
	return len;
}

10.清空单链表

思路:这里不像顺序表一样,顺序表只需释放一个指针arr(连续开辟的空间),而单链表物理上是不连续的,需要释放每一个节点,循环遍历单链表即可。

void SLLClear(SLLNode** pphead)
{
	assert(pphead);

	//从头开始逐个释放
	SLLNode* cur = *pphead;
	while (cur != NULL)
	{
		*pphead = cur->next;
		free(cur);
		cur = *pphead;
	}
}

11.销毁单链表

思路:自认为销毁与清空单链表没有太大区别

void SLLDestory(SLLNode** pphead)
{
	assert(pphead);

	SLLClear(pphead); //与清空单链表无区别
}

四.模块化源代码

1.SingleLinkList.h

//#pragma once 防止头文件被重复包含,导致效率下降
#ifndef __SINGLELINKLIST_H__
#define __SINGLELINKLIST_H__

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

typedef int SLLDataType; //增强程序的可维护性

typedef struct SLLNode
{
	SLLDataType data;     //数据域
	struct SLLNode* next; //指针域
}SLLNode;

void SLLInit(SLLNode** pphead);//初始化单链表(需要修改单链表,传地址)

SLLNode* BuyNode(SLLDataType x);//购买节点

void SLLPrint(SLLNode* phead);//打印单链表(无需修改单链表,传值)

void SLLPushBack(SLLNode** pphead, SLLDataType x);//尾插(同理,传地址)

void SLLPushFront(SLLNode** pphead, SLLDataType x);//头插

void SLLPopBack(SLLNode** pphead);//尾删

void SLLPopFront(SLLNode** pphead);//头删

SLLNode* SLLFind(SLLNode* phead, SLLDataType x);//查找

void SLLInsert(SLLNode** pphead, SLLNode* pos, SLLDataType x);//插入:通过《SLLFind函数》找到pos,在pos前插入x

void SLLErase(SLLNode** pphead, SLLNode* pos);//删除:通过《SLLFind函数》找到pos,删除pos位置的值

void SLLModify(SLLNode** pphead, SLLNode* pos, SLLDataType x);//修改:通过《SLLFind函数》找到pos,修改pos位置的值

int SLLLength(SLLNode* phead);//求单链表的长度

void SLLClear(SLLNode** pphead);//清空单链表

void SLLDestory(SLLNode** pphead);//销毁单链表

#endif

2.SingleLinkList.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"SingleLinkList.h"

void SLLInit(SLLNode** pphead)
{
	assert(pphead); //断言
	*pphead = NULL;
}

SLLNode* BuyNode(SLLDataType x)
{
	SLLNode* newNode = (SLLNode*)malloc(sizeof(SLLNode)); //申请节点空间
	if (newNode == NULL) //申请失败
	{
		perror("malloc fail");
		exit(1);
	}
	//申请成功
	newNode->data = x;
	newNode->next = NULL;
	return newNode; //返回新节点
}

void SLLPrint(SLLNode* phead)
{
	SLLNode* cur = phead; //定位单链表的头节点
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next; //更新为下一节点
	}
	printf("NULL\n");
}

void SLLPushBack(SLLNode** pphead, SLLDataType x)
{
	assert(pphead);

	SLLNode* newNode = BuyNode(x); //购买节点
	if (*pphead == NULL) //单链表为空
	{
		*pphead = newNode; //更新单链表的头节点
	}
	else //单链表不为空
	{
		SLLNode* tail = *pphead; //寻找尾节点
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode; //尾节点,链接新节点
	}
}

void SLLPushFront(SLLNode** pphead, SLLDataType x)
{
	assert(pphead);

	SLLNode* newNode = BuyNode(x); //购买节点
	newNode->next = *pphead; //新节点头插
	*pphead = newNode; //更新单链表的头节点
}

void SLLPopBack(SLLNode** pphead)
{
	assert(pphead);

	//单链表为空
	if (*pphead == NULL)
	{
		return; //无需释放,直接退出
	}
	//单链表只有一个节点
	else if ((*pphead)->next == NULL) //注意:加上括号
	{
		free(*pphead); //释放头节点
		*pphead = NULL; //单链表置为NULL
	}
	//单链表有多个节点
	else
	{
		SLLNode* prev = NULL; //定位尾节点前一个节点
		SLLNode* tail = *pphead; //定位尾节点
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail); //释放尾节点
		tail = NULL; //置为NULL,预防野指针
		prev->next = NULL; //变为尾节点后置为NULL
	}
}

void SLLPopFront(SLLNode** pphead)
{
	assert(pphead);

	//单链表为空
	if (*pphead == NULL)
	{
		return; //无需释放,直接退出
	}
	else
	{
		SLLNode* cur = (*pphead)->next; //定位头节点的下一个节点
		free(*pphead); //释放头节点
		*pphead = cur; //更新单链表的头节点
	}
}

SLLNode* SLLFind(SLLNode* phead, SLLDataType x)
{
	SLLNode* cur = phead; //定位值为x的节点
	while (cur != NULL) //遍历单链表
	{
		if (cur->data == x)
		{
			return cur; //找到了,返回节点的地址
		}
		cur = cur->next;
	}
	return NULL; //找不到,返回NULL
}

void SLLInsert(SLLNode** pphead, SLLNode* pos, SLLDataType x)
{
	assert(pphead);
	assert(pos);
	
	if (pos == *pphead) //pos是头节点的地址
	{
		SLLPushFront(pphead, x); //直接头插
	}
	else
	{
		SLLNode* newNode = BuyNode(x); //购买节点
		SLLNode* prev = *pphead; //定位pos前一个节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//插入操作
		newNode->next = pos; 
		prev->next = newNode; 
	}
}

void SLLErase(SLLNode** pphead, SLLNode* pos)
{
	assert(pphead);

	if (pos == *pphead) //pos是头节点的地址
	{
		SLLPopFront(pphead); //直接头删
	}
	else
	{
		SLLNode* prev = *pphead; //定位pos前一个节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next; //删除过程
		free(pos); //释放节点
		pos = NULL; //置为NULL,预防野指针
	}
}

void SLLModify(SLLNode** pphead, SLLNode* pos, SLLDataType x)
{
	assert(pphead);
	assert(pos); //防止对NULL解引用导致程序崩溃
	pos->data = x; //直接修改就行了
}

int SLLLength(SLLNode* phead)
{
	int len = 0;
	SLLNode* cur = phead;
	while (cur != NULL)
	{
		cur = cur->next;
		len++;
	}
	return len;
}

void SLLClear(SLLNode** pphead)
{
	assert(pphead);

	//从头开始逐个释放
	SLLNode* cur = *pphead;
	while (cur != NULL)
	{
		*pphead = cur->next;
		free(cur);
		cur = *pphead;
	}
}

void SLLDestory(SLLNode** pphead)
{
	assert(pphead);

	SLLClear(pphead); //与清空单链表无区别
}

3.test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"SingleLinkList.h"

enum //匿名枚举
{
	EXIT,
	PUSHBACK,
	PUSHFRONT,
	POPBACK,
	POPFRONT,
	INSERT,
	ERASE,
	FIND,
	MODIFY,
	PRINT,
	LENGTH,
	CLEAR
};

void Menu()
{
	printf("*************单链表************\n");
	printf("****1.尾插           2.头插****\n");
	printf("****3.尾删           4.头删****\n");
	printf("****5.插入           6.删除****\n");
	printf("****7.查找           8.修改****\n");
	printf("****9.打印          10.长度****\n");
	printf("***11.清空           0.退出****\n");
	printf("*******************************\n");
}

int main()
{
	SLLNode* plist;
	SLLInit(&plist);
	int select = 0;       //操作选项
	SLLDataType value;    //接收值
	SLLDataType value1;   //接收值
	SLLNode* pos = NULL;  //接收指针
	do
	{
		Menu();
		printf("请输入您的操作:");
		scanf("%d", &select);
		switch (select)
		{
		case EXIT:
			printf("退出单链表!\n");
			break;
		case PUSHBACK:
			printf("请输入您要尾插的值(输入-1代表结束):");
			while ((scanf("%d", &value), value != -1)) //逗号表达式
			{
				SLLPushBack(&plist, value);
			}
			break;
		case PUSHFRONT:
			printf("请输入您要头插的值(输入-1代表结束):");
			do
			{
				scanf("%d", &value);
				if (value != -1)
				{
					SLLPushFront(&plist, value);
				}
			} while (value != -1);
			break;
		case POPBACK:
			SLLPopBack(&plist);
			break;
		case POPFRONT:
			SLLPopFront(&plist);
			break;
		case INSERT:
			printf("请输入您要插入到《何值前面》以及《插入的值》:");
			scanf("%d %d", &value1, &value);
			pos = SLLFind(plist, value1);
			if (pos != NULL)
			{
				SLLInsert(&plist, pos, value);
			}
			else
			{
				printf("该值不存在,无法插入!\n");
			}
			break;
		case ERASE:
			printf("请输入您要删除的值:");
			scanf("%d", &value);
			pos = SLLFind(plist, value);
			if (pos != NULL)
			{
				SLLErase(&plist, pos);
			}
			else
			{
				printf("该值不存在,无法删除!\n");
			}
			break;
		case FIND:
			printf("请输入您要查找的值:");
			scanf("%d", &value);
			int ret = SLLFind(plist, value);
			if (ret == -1)
			{
				printf("您要查找的值不存在!\n");
			}
			else
			{
				printf("您要查找的值存在!\n");
			}
			break;
		case MODIFY:
			printf("请输入您要《要修改的值》以及《修改后的值》:");
			scanf("%d %d", &value1, &value);
			pos = SLLFind(plist, value1);
			if (pos != NULL)
			{
				SLLModify(&plist, pos, value);
			}
			else
			{
				printf("该值不存在,无法修改!\n");
			}
			break;
		case PRINT:
			SLLPrint(plist);
			break;
		case LENGTH:
			printf("单链表的长度:%d\n", SLLLength(plist));
			break;
		case CLEAR:
			SLLClear(&plist);
			break;
		}
	} while (select);
	SLLDestory(&plist); //记得最后要销毁,防止内存泄漏
	return 0;
}

五.链表必做OJ题

  1. 反转单链表
  2. 链表的中间结点
  3. 合并两个有序链表
  4. 判断链表是否有环?
  5. 求环形链表的入口点?

以后还会更新其余的链表:带头,循环,双链等等组合的链表。

创作不易,如果能帮到你的话能赏个三连吗?感谢啦!!!

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

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

相关文章

机器学习课程复习——聚类算法

Q:什么是硬聚类,什么是软聚类? 如果一个样本只能属于一个类,则称为硬聚类(hard clustering); 如果一个样本可以属于多个类,则称为软聚类(soft clustering)。 Q:聚类和分类的区别? 聚类分类学习类型无监督学习方法 不需要事先标记的数据 通过发现数据中的模式或结构来组…

【毕业设计】Django 校园二手交易平台(有源码+mysql数据)

此项目有完整实现源码&#xff0c;有需要请联系博主 Django 校园二手交易平台开发项目 项目选择动机 本项目旨在开发一个基于Django的校园二手交易平台&#xff0c;为大学生提供一个安全便捷的二手物品买卖平台。该平台将提供用户注册和认证、物品发布和搜索、交易信息管理等…

Linux-Https协议

文章目录 前言一、Https协议二、常见的加密方式对称加密非对称加密数据摘要&&数据指纹中间人攻击 三、Https的加密历程方案1-只使用对称加密方案2-只使用非对称加密方案3-双方都使用非对称加密方案4-非对称加密对称加密 四、CA证书什么是CA证书CA证书的合法性如何生成.…

Python9 Tkinter创建GUI

1.Tkinter简单介绍 Tkinter是Python的标准GUI&#xff08;图形用户界面&#xff09;库&#xff0c;提供一种简单的方式来创建窗口程序。Tkinter封装了访问Tk GUI工具包的接口&#xff0c;Tk是一个跨平台的GUI开发库&#xff0c;广泛用于创建图形界面。 使用Tkinter&#xff0…

++++++局部变量、全局变量及变量的存储类别++++====+++指针+++

局部变量、全局变量及变量的存储类别 局部变量与全局变量的基本概念 局部变量&#xff1a;在函数内部定义的变量称为局部变量&#xff0c;也称为内部变量。它们只在定义它们的函数内部有效&#xff0c;即只有在这个函数被调用时&#xff0c;局部变量才会被分配内存空间&#x…

Java学习【深入解读File类:从基础到高级的完整指南】

Java学习【深入解读File类&#xff1a;从基础到高级的完整指南】 ⭐File的概述和构造方法⭐File常见的成员方法&#x1f319;构造方法&#x1f319;length()&#x1f319;getAbsoluteFile()和getPath()&#x1f319;getName()和lastModified() ⭐文件的创建和删除⭐获取并遍历 …

从 0 打造私有知识库 RAG Benchmark 完整实践

背景介绍 最近从 0 构建了一个大模型知识库 RAG 服务的自动化 Benchmark 评估服务&#xff0c;可以基于私有知识库对 RAG 服务进行批量自动化测试与评估。本文是对这个过程的详细记录。 本文实际构建的是医疗行业知识库&#xff0c;基于高质量的医学指南和专家共识进行构建。…

8K Stars!Text2SQL还不够?试试更精准的RAG2SQL开源工具

▼618钜惠专场直播&#xff0c;点击预约按钮免费预约。 SQL 是查询数据库的关键&#xff0c;但如何高效地构建满足个人需求的 SQL 语句呢&#xff1f;随着 AI 大模型的进步&#xff0c;我们现在已经拥有了 Text2SQL 的技术&#xff0c;这一技术已被多款产品广泛应用。 今天&…

考研数学|做完《660》,做《880》还是吃力,怎么办?

880吃力说明基础还是不太扎实&#xff0c;建议配合知能行再刷880。 强化之前做660&#xff0c;然后在强化的时候再做880。 660整体难度属于基础阶段到强化阶段。而且是选填部分的题目&#xff0c;所以还是要做一些其他题 然后说一下推荐的习题册&#xff1a;基础不好先做1800、…

如何学习 Java 中的 Socket 编程,进行网络通信

Socket编程是网络编程的核心技术之一&#xff0c;它使得不同主机之间可以进行数据通信。Java提供了丰富的网络编程API&#xff0c;使得编写网络应用程序变得相对简单和直观。本文将详细讲解如何学习Java中的Socket编程&#xff0c;并通过示例代码展示如何实现网络通信。 一、S…

船舶能源新纪元:智能管理引领绿色航运潮流

在蓝色的大海上&#xff0c;无数船只乘风破浪&#xff0c;为全球的贸易和文化交流贡献着力量。然而&#xff0c;随着环保意识的提升和可持续发展的要求&#xff0c;船舶的能源消耗和排放问题逐渐成为了人们关注的焦点。在这个关键时刻&#xff0c;船舶能源管理系统应运而生&…

智能合约开发的过程

智能合约是一种运行在区块链上的程序&#xff0c;可以自动执行预先设定的条款和条件。智能合约具有去中心化、透明、不可篡改等特点&#xff0c;因此被广泛应用于金融、供应链、物联网等领域。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流…

西门子学习笔记12 - BYTE-REAL互相转化

这是针对于前面MQTT协议的接收和发送数组只能是BYTE数组做出的对应的功能块封装。 1、BYTE-REAL转化 1、把byte数组转成字符串形式 2、把字符串转成浮点数 2、REAL-BYTE转化 1、把浮点数转成字符串 2、把字符串转成Byte数组

通过编辑器工具改变Inspector锁定状态

是在看一系列视频的时候&#xff0c;看到的&#xff0c;感觉挺有用&#xff0c;就记录下来。 就是这个小锁的按钮&#xff0c;后续可以通过快捷键&#xff0c;快速锁定和解锁。代码里没有加入快捷键控制&#xff0c;有需要用到的可以自己加一下&#xff0c;比较简单 using Uni…

前端上传minio

参考【GitCode - 全球开发者的开源社区,开源代码托管平台】 注意事项&#xff1a;nodejs服务&#xff0c;文件扩展名为mjs&#xff0c;版本号8.0&#xff0c;引入的时候 import * as Minio from minio import Minio as * from minio// 实例化对象存储服务的MinIO客户端 // p…

c语言字符串函数详解(全)

字符串函数 1.strlen函数 求字符串长度的 //求字符串长度 int my_strlen(char* str) {int count 0;while (*str ! \0){count;*str;}return count; } int main() {char arr[] "abcdef";int red my_strlen(arr);printf("%d\n", red);return 0; } 2. st…

【MySQL】(基础篇十二) —— 子查询

分组数据 本文介绍什么是子查询以及如何使用它们。 SQL允许我们创建子查询&#xff08;subquery&#xff09;&#xff0c;即嵌套在其他查询中的查询。这样可以实现更复杂的查询&#xff0c;理解这个概念的最好方法是考察几个例子。 利用子查询进行过滤 需求&#xff1a;查询…

一行超长日志引发的 “血案” - Containerd 频繁 OOM 背后的真相

案发现场&#xff1a;混沌初现 2024年6月10日&#xff0c;本应是平静的一天。但从上午 9 点开始&#xff0c;Sealos 公有云的运维监控告警就开始不停地响。北京可用区服务器节点突然出现大量 “not ready” 告警&#xff0c;紧接着&#xff0c;系统自动触发 004 节点重启&…

重学java 75.JDK新特性 ① Lambda表达式

你所做的事情&#xff0c;也许暂时看不到成果&#xff0c;你不是没有成长&#xff0c;而是在扎根 —— 24.6.19 一、函数式编程思想和Lambda表达式定义格式 1.面向对象思想:是Java的核心编程思想 强调的是找对象,帮我们做事儿 比如:去北京 -> 强调的是怎么去,火车,高铁,飞机…

为什么人们对即将推出的 Go 1.23 迭代器感到愤怒

原文&#xff1a;gingerBill - 2024.06.17 TL;DR 它让 Go 变得太“函数式”&#xff0c;而不再是不折不扣的命令式语言。 最近&#xff0c;我在 Twitter 上看到一篇帖子&#xff0c;展示了 Go 1.23&#xff08;2024 年 8 月&#xff09;即将推出的 Go 迭代器设计。据我所知&a…