【数据结构和算法】认识线性表中的链表,并实现单向链表

news2024/10/6 0:34:26

本文接着上文,上文我们认识了线性表的概念,并实现了静态、动态顺序表。接下来我们认识一个新概念链表。并实现单向链表的各种操作。顺序表还有不明白的看这一篇文章

(13条消息) 【数据结构和算法】实现线性表中的静态、动态顺序表_小王学代码的博客-CSDN博客

目录

一、链表是什么?

1.链表的概念和结构

2.链表的分类

1.单向或者双向

2.带头或者不带头

3.循环或者非循环

二、链表的实现

1.无头单向非循环链表

2.函数功能的实现

1.初始化和打印链表

2.头插和尾插

3.头删和尾删

4.单链表的查找

5.在pos结点位置之前或之后插入数据

6.在pos结点位置删除数据(删除pos结点)

7.删除pos位置之后一个结点

8.摧毁链表

三、完整代码

1.LinkList.h

2.LinkList.c

3.test.c


前言

我们知道了数据结构中线性表的概念,我们应该会感觉比较好理解,因为顺序表的建立主要涉及到结构体和动态内存管理函数,是类似于数组的一种形式。

我们要思考这样一个问题

1.增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。

2.增容一般都是2倍扩容,有时候也会浪费一定的空间

于是,为了解决上面这样的问题,我们引入了线性表中的链表,这一概念。


一、链表是什么?

1.链表的概念和结构

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

图示如下:

由上图可知,链表的特征为:

1.链式结构在逻辑上是连续的,但是在物理上是不一定连续的。        (每一个结点的地址是不一定的)

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

3.从堆上申请的空间,是按照一定策略来分配,根据编辑器的不同而不同,再次申请的空间可能连续,也可能不连续。

2.链表的分类

实际上链表的结构有很多中,以下组合起来有8种主要的链表结构的情况

1.单向或者双向

2.带头或者不带头

头节点使用的话,就不需要对其数据域赋值,只起到一个成为建立链表的基点的作用,不使用的话,第一个结点存储数值就可以,创建一个新节点给这个第一个结点phead即可,这样头节点链表,就变成了非头结点的链表     

主要就是看第一个结点是否用到了其数值域

用第一结点数据域        非头节点

没用                              头节点

3.循环或者非循环

 

以上这些类型情况,我们常用的有两种,无头单向非循环链表,带头双向循环链表

如图所示

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

二、链表的实现

1.无头单向非循环链表

结构体为:

typedef int SLDataType;
//单向链表的实现、
typedef struct ListNode {
	SLDataType data;//数据域
	struct ListNode* next;//指针域
}List;

要实现的函数为:

//打印单链表
void ListPrint(List* ps);
//单链表的尾插
void ListPushBack(List** ps, SLDataType data);
//单链表的头插
void ListPushFront(List** ps, SLDataType data);
//单链表的尾删
void ListPopBack(List** ps);
//单链表的头删
void ListPopFront(List** ps);
//单链表的查找
List* ListFind(List* ps);

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos);

//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos);

//在pos位子删除数据
void ListErase(List** ps, List* pos);

//在pos位置之后一位删除数据
void ListEraseAfter(List* pos);
//单链表的摧毁
void ListDestory(List** ps);

2.函数功能的实现

1.初始化和打印链表

//初始化链表
void InitList(List* ps) {
	ps->data = 0;
	ps->next = NULL;
}
//打印单链表
void ListPrint(List* ps) {
	List* cur = ps;
	while ((cur) != NULL) {
		printf("%d -> ", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

2.头插和尾插

尾部插入图示如下:

代码如下:

//创建一个新节点
List* CreateNode(SLDataType x) {
	List* newNode = (List*)malloc(sizeof(List));
	if (newNode == NULL) {
		perror("malloc fail\n");
		exit(-1);
	}
	else {
		newNode->data = x;
		newNode->next = NULL;
	}
	return newNode;
}
//单链表的尾插
void ListPushBack(List** ps, SLDataType data) {
	//创建新的节点
	assert(ps);//断言
	List* newNode = CreateNode(data);
	if (*ps == NULL) {
		//说明是空链表
		*ps = newNode;
	}
	else {
		List* tail = *ps;
		while (tail->next != NULL) {
			tail = tail->next;
		}
		tail->next = newNode;
	}
}

头部插入如图所示:

代码如下:

//单链表的头插
void ListPushFront(List** ps, SLDataType data) {
	//先断言是否为空
	assert(ps);
	//将新地址指向头结点下一个next结点的地址,然后在用头结点指向新节点
	List* newNode = CreateNode(data);
	newNode->next = (*ps);  //new指向ps当前的位置,然后new是第一个位置了,将new赋值给ps,这样new就作为头部连接链表了
	(*ps) = newNode;//原本ps位置的数值不变,这样的话就成 new->next=ps,new数值在前,ps的数值在后

}

3.头删和尾删

尾部删除如图所示:

 代码演示:

//单链表的尾删
void ListPopBack(List** ps) {
	assert(ps);//断言
	//三种情况
	//1.空链表
	//2.一个节点
	//3.多个节点
	if (*ps == NULL) {
		return;
	}
	//只有一个节点的情况为
	else if ((*ps)->next == NULL) {
		free(*ps); //如果只有一个头节点的话
		*ps = NULL;
	}
	else {
		//多个节点的情况下、
		List* tail = *ps;
		while (tail->next->next!= NULL) {
			tail = tail->next;
		}
		free(tail->next);
		tail->next= NULL;
	}
}

头部删除如图所示:

 代码如下:

//单链表的头删
void ListPopFront(List** ps) {
	assert(ps);
	//1.空
	//2.非空
	if (*ps == NULL) {
		//为空
		return;
	}
	else {
		List* tail = (*ps)->next;//创建临时变量tail,将头节点之后的地址给tail
		free(*ps);//滞空头节点
		*ps = NULL;//可有可不有,接下来也要用
		*ps = tail;//将tail也就是ps的下一个List节点给ps

	}
}

4.单链表的查找

代码如下:

//单链表的查找

List* ListFind(List* ps,SLDataType data) {
	//进行查找就是进行判断是否为空链表,为空直接返回
	if (ps == NULL) {
		printf("链表为空、无法查找\n");
		return;
	}
	List* tail = ps;
	while (tail != NULL) {//从头节点开始,进行循环,
		if (tail->data == data) {
			return tail;
		}
		tail = tail->next;
	}
	return tail;//最后还找不到data,tail就为NULL了
}

5.在pos结点位置之前或之后插入数据

在pos结点位置之前插入数据,如图所示:

代码如下:

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos) {
	//先判断是否为空
	assert(ps);
	assert(pos);
	//空链表排除
	//1.pos是第一个节点
	//2.pos不是第一个节点
	if (*ps == pos) {
		//是第一个节点,那就直接头插
		ListPushFront(ps, x);
	}
	else {
		List* prev = *ps;
		while (prev->next != pos) {
			prev = prev->next;
		}
		List* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

在pos结点位置之后插入结点,如图所示:

 代码如下:
 

//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos) {
	assert(ps);
	//assert(pos);//断言
	List* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

6.在pos结点位置删除数据(删除pos结点)

如图所示:

代码如下:

//在pos位子删除数据
void ListErase(List** ps, List* pos) {
	assert(ps);
	assert(pos);

	if (*ps == pos) {
		ListPopFront(ps);
	}
	else {
		List* next = *ps;
		while (next->next != pos) {
			next = next->next;
		}
		//这个时候next->next == pos
		next->next = next->next->next;
		/*free(next->next);*/
		free(pos);
		pos = NULL;
	}
}

7.删除pos位置之后一个结点

如图所示:

代码如下:

//在pos位置之后一位删除数据
void ListEraseAfter(List* pos) {
	assert(pos);
	List* next = pos->next;//将pos 的下一个结点赋值给next
	if (next != NULL) {
		pos->next = pos->next->next;//表示pos的下一个的下一个结点的地址赋值给pos的指针域  实质上是将pos的下一个结点给跳过
		free(next);  //将pos的下一个结点给free释放
		next = NULL;  //next指向为NULL  防止野指针
	}
}

8.摧毁链表

代码如下:

//链表的摧毁  直接将头指针指针域指向NULL
void ListDestory(List** ps) {
	//assert(ps);  //防止空链表
	一个结点一个结点释放
	//List* next = *ps;
	//while (next) {
	//	List* cur = next->next;
	//	free(next);
	//	next = cur;
	//}
	*ps = NULL;
}

因为是二级指针,所以直接 *ps=NULL 即可,或者一个一个free

三、完整代码

1.LinkList.h

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<assert.h>
typedef int SLDataType;
//单向链表的实现、
typedef struct ListNode {
	SLDataType data;//数据域
	struct ListNode* next;//指针域
}List;

//打印单链表
void ListPrint(List* ps);
//单链表的尾插
void ListPushBack(List** ps, SLDataType data);
//单链表的头插
void ListPushFront(List** ps, SLDataType data);
//单链表的尾删
void ListPopBack(List** ps);
//单链表的头删
void ListPopFront(List** ps);
//单链表的查找
List* ListFind(List* ps);

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos);

//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos);

//在pos位子删除数据
void ListErase(List** ps, List* pos);

//在pos位置之后一位删除数据
void ListEraseAfter(List* pos);
//单链表的摧毁
void ListDestory(List** ps);

2.LinkList.c

#define _CRT_SECURE_NO_WARNINGS
#include"单向链表.h"

//链表的使用,在插入上面
// 如果是尾部插入,如果是空链表直接将新节点给ps 是先找到链表尾部,然后创建新节点,连接即可
// 如果是头部插入,先进行断言判空,之后创建新节点,将新节点的数据
// new->next=ps  这个是找到对应的位置,连接起来
// ps=new;  将新节点的信息传递给ps,这样ps还是头节点
// 
// 
// 
// 
// 


//进行单链表的实现

//初始化链表
void InitList(List* ps) {
	ps->data = 0;
	ps->next = NULL;
}
//打印单链表
void ListPrint(List* ps) {
	List* cur = ps;
	while ((cur) != NULL) {
		printf("%d -> ", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

//创建一个新节点
List* CreateNode(SLDataType x) {
	List* newNode = (List*)malloc(sizeof(List));
	if (newNode == NULL) {
		perror("malloc fail\n");
		exit(-1);
	}
	else {
		newNode->data = x;
		newNode->next = NULL;
	}
	return newNode;
}
//单链表的尾插
void ListPushBack(List** ps, SLDataType data) {
	//创建新的节点
	assert(ps);//断言
	List* newNode = CreateNode(data);
	if (*ps == NULL) {
		//说明是空链表
		*ps = newNode;
	}
	else {
		List* tail = *ps;
		while (tail->next != NULL) {
			tail = tail->next;
		}
		tail->next = newNode;
	}
}
//单链表的头插
void ListPushFront(List** ps, SLDataType data) {
	//先断言是否为空
	assert(ps);
	//将新地址指向头结点下一个next结点的地址,然后在用头结点指向新节点
	List* newNode = CreateNode(data);
	newNode->next = (*ps);  //new指向ps当前的位置,然后new是第一个位置了,将new赋值给ps,这样new就作为头部连接链表了
	(*ps) = newNode;//原本ps位置的数值不变,这样的话就成 new->next=ps,new数值在前,ps的数值在后

}

//单链表的尾删
void ListPopBack(List** ps) {
	assert(ps);//断言
	//三种情况
	//1.空链表
	//2.一个节点
	//3.多个节点
	if (*ps == NULL) {
		return;
	}
	//只有一个节点的情况为
	else if ((*ps)->next == NULL) {
		free(*ps); //如果只有一个头节点的话
		*ps = NULL;
	}
	else {
		//多个节点的情况下、
		List* tail = *ps;
		while (tail->next->next!= NULL) {
			tail = tail->next;
		}
		free(tail->next);
		tail->next= NULL;
	}
}


//单链表的头删
void ListPopFront(List** ps) {
	assert(ps);
	//1.空
	//2.非空
	if (*ps == NULL) {
		//为空
		return;
	}
	else {
		List* tail = (*ps)->next;//创建临时变量tail,将头节点之后的地址给tail
		free(*ps);//滞空头节点
		*ps = NULL;//可有可不有,接下来也要用
		*ps = tail;//将tail也就是ps的下一个List节点给ps

	}
}

//单链表的查找

List* ListFind(List* ps,SLDataType data) {
	//进行查找就是进行判断是否为空链表,为空直接返回
	if (ps == NULL) {
		printf("链表为空、无法查找\n");
		return;
	}
	List* tail = ps;
	while (tail != NULL) {//从头节点开始,进行循环,
		if (tail->data == data) {
			return tail;
		}
		tail = tail->next;
	}
	return tail;
}

//在pos位置上插入数据
void ListInsertBefore(List** ps, SLDataType x, List* pos) {
	//先判断是否为空
	assert(ps);
	assert(pos);
	//空链表排除
	//1.pos是第一个节点
	//2.pos不是第一个节点
	if (*ps == pos) {
		//是第一个节点,那就直接头插
		ListPushFront(ps, x);
	}
	else {
		List* prev = *ps;
		while (prev->next != pos) {
			prev = prev->next;
		}
		List* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}
//在pos位置之后插入数据
void ListInsertAfter(List** ps, SLDataType x, List* pos) {
	assert(ps);
	//assert(pos);//断言
	List* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//在pos位子删除数据
void ListErase(List** ps, List* pos) {
	assert(ps);
	assert(pos);

	if (*ps == pos) {
		ListPopFront(ps);
	}
	else {
		List* next = *ps;
		while (next->next != pos) {
			next = next->next;
		}
		//这个时候next->next == pos
		next->next = next->next->next;
		/*free(next->next);*/
		free(pos);
		pos = NULL;
	}
}
//在pos位置之后一位删除数据
void ListEraseAfter(List* pos) {
	assert(pos);
	List* next = pos->next;//将pos 的下一个结点赋值给next
	if (next != NULL) {
		pos->next = pos->next->next;//表示pos的下一个的下一个结点的地址赋值给pos的指针域  实质上是将pos的下一个结点给跳过
		free(next);  //将pos的下一个结点给free释放
		next = NULL;  //next指向为NULL  防止野指针
	}
}
//链表的摧毁  直接将头指针指针域指向NULL
void ListDestory(List** ps) {
	//assert(ps);  //防止空链表
	一个结点一个结点释放
	//List* next = *ps;
	//while (next) {
	//	List* cur = next->next;
	//	free(next);
	//	next = cur;
	//}
	*ps = NULL;
}

3.test.c

#define _CRT_SECURE_NO_WARNINGS

#include"单向链表.h"
void test()
{
	List* phead=NULL;//作为头节点

	//单链表的尾插
	ListPushBack(&phead, 1);
	ListPushBack(&phead, 2);
	ListPushBack(&phead, 3);
	ListPushBack(&phead, 4);
	ListPushBack(&phead, 5);
	ListPrint(phead);

	ListPushFront(&phead, 1);
	ListPrint(phead);

	ListPopBack(&phead);
	ListPrint(phead);
	
	ListPopFront(&phead);
	ListPrint(phead);
	ListErase(&phead, phead->next);
	ListInsertAfter(&phead, 10, phead->next);
	ListEraseAfter(phead->next);

	ListPrint(phead);
	ListDestory(&phead);
}
int main()
{
	test();
	return 0;
}

总结

本文主要讲解了链表的分类是什么,两种常用的类型,无头单向非循环链表、有头双向循环链表,我们实现了无头单向非循环链表,这是比较简单的一种链表的实现,我们使用的是二级指针传参,当然使用一级指针传参也可以,主要实现函数为头尾插入,头尾删除,pos指定结点位置前后添加或者删除元素。

接下来,下文我们将跟大家介绍一下最常用链表的另一种形式,带头双向循环链表。

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

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

相关文章

leetcode--链表

链表1.链表的基本操作&#xff08;1&#xff09;反转链表(206)&#xff08;2&#xff09; 合并两个有序链表(21)&#xff08;3&#xff09;两两交换链表中的节点(24)2.其它链表技巧&#xff08;1&#xff09;相交链表(160)&#xff08;2&#xff09;回文链表(234)3.练习&#x…

力扣 2293. 极大极小游戏

题目 给你一个下标从 0 开始的整数数组 nums &#xff0c;其长度是 2 的幂。 对 nums 执行下述算法&#xff1a; 设 n 等于 nums 的长度&#xff0c;如果 n 1 &#xff0c;终止 算法过程。否则&#xff0c;创建 一个新的整数数组 newNums &#xff0c;新数组长度为 n / 2 &…

手把手带初学者快速入门 JAVA Web SSM 框架

博主也是刚开始学习SSM&#xff0c;为了帮大家节省时间&#xff0c;写下SSM快速入门博客 有什么不对的地方还请 私信 或者 评论区 指出 ​只是一个简单的整合项目&#xff0c;让初学者了解一下SSM的大致结构 项目先把框架写好&#xff0c;之后在填写内容 项目压缩包 完整的蓝奏…

浅谈phar反序列化漏洞

目录 基础知识 前言 Phar基础 Phar文件结构 受影响的函数 漏洞实验 实验一 实验二 过滤绕过 补充 基础知识 前言 PHP反序列化常见的是使用unserilize()进行反序列化&#xff0c;除此之外还有其它的反序列化方法&#xff0c;不需要用到unserilize()。就是用到了本文…

C 语言零基础入门教程(十一)

C 数组 C语言支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量&#xff0c;比如 runoob0、runoob1、…、runoob99&#xf…

【Linux】调试器 - gdb 的使用

目录 一、背景知识 二、debug 与 release 1、生成两种版本的可执行程序 2、debug 与 release 的区别 三、gdb 的使用 1、调试指令与指令集 2、源代码显示、运行与退出调试 3、断点操作 4、逐语句与逐过程 5、调试过程中的数据监视 6、调试过程中快速定位问题 一、背…

吴恩达机器学习笔记(三)逻辑回归

机器学习&#xff08;三&#xff09; 学习机器学习过程中的心得体会以及知识点的整理&#xff0c;方便我自己查找&#xff0c;也希望可以和大家一起交流。 —— 吴恩达机器学习第五章 —— 四、逻辑回归 线性回归局限性 线性回归对于分类问题的局限性&#xff1a;由于离群点…

LeetCode动态规划经典题目(九):入门

学习目标&#xff1a; 了解动态规划 学习内容&#xff1a; 1. LeetCode509. 斐波那契数https://leetcode.cn/problems/fibonacci-number/ 2. LeetCode70. 爬楼梯https://leetcode.cn/problems/climbing-stairs/ 3. LeetCode746. 使用最小花费爬楼梯https://leetcode.cn/proble…

ice规则引擎==启动流程和源码分析

启动 git clone代码 创建数据库ice&#xff0c;执行ice server里的sql&#xff0c;修改ice server的配置文件中的数据库信息 启动ice server 和ice test 访问ice server localhost:8121 新增一个app,默认给了个id为1&#xff0c;这个1可以看到在ice test的配置文件中指定…

MP503空气质量气体传感器介绍

MP503空气质量气体传感器简介MP503空气质量气体传感器采用多层厚膜制造工艺&#xff0c;在微型Al2O3陶瓷基片的两面分别制作加热器和金属氧化物半导体气敏层&#xff0c;封装在金属壳体内。当环境空气中有被检测气体存在时传感器电导率发生变化&#xff0c;该气体的浓度越高&am…

Spring Boot开发自定义的starter

目录 一、Spring Boot的starter概述 二、自定义starter的命名规则 三、自定义starter实战 1. 创建spring工程 2. 修改pom.xml 3. 编写配置类 4. 安装到本地maven仓库 5. 在其他项目中引入 6. 测试 一、Spring Boot的starter概述 SpringBoot中的starter是一种非常重要的机…

【web前端】CSS浮动

多个块级元素纵向排列找标准流&#xff0c;横向排列找浮动 浮动的特性&#xff1a; &#xff08;1&#xff09;浮动元素会脱离标准流&#xff08;脱标&#xff09; &#xff08;有的浮动&#xff0c;有的没浮&#xff09; &#xff08;2&#xff09;浮动的元素会在一行内显示…

C#中[]的几种用法

一、导入外部DLL函数 如[DllImport(“kernel32.dll”)]这叫引入kernel32.dll这个动态连接库。这个动态连接库里面包含了很多WindowsAPI函数,如果你想使用这面的函数&#xff0c;就需要这么引入。举个例子&#xff1a; [DllImport(“kernel32.dll”)] private static extern vo…

栈与队列总结

文章目录栈栈的概述栈的实现栈API设计栈代码实现栈的应用栈在系统中的应用括号匹配问题字符串去重问题逆波兰表达式问题队列队列的概述队列的实现队列的API设计队列代码实现队列的经典题目滑动窗口最大值问题求前 K 个高频元素栈 栈的概述 栈是一种基于先进后出(FILO)的数据结…

Android开发环境搭建

前面从全局和整体角度看了下Android包含哪些东西&#xff0c;自然&#xff0c;也涵盖了开发需要了解的内容&#xff0c;具体参见博文&#xff1a;从技术角度看Android大系统的构成_龙赤子的博客-CSDN博客 写完博文&#xff0c;感觉对Android开发也胸有成竹了&#xff0c;于是就…

ActiveReports.NET 17.0 Crack

ActiveReports.NET 17 添加新的 RDL 仪表板报告类型、新的 Blazor Web Designer&#xff0c;以及对 .NET 7 的全面支持。 2023 年 1 月 25 日 - 15:28新版本 特征 RDL 仪表板 - 新报告类型 RDL 仪表板提供了一种在可滚动的交互式容器中显示数据可视化控件&#xff08;例如图表、…

【Typescript学习】使用 React 和 TypeScript 构建web应用(三)所有组件

教程来自freecodeCamp&#xff1a;【英字】使用 React 和 TypeScript 构建应用程序 跟做&#xff0c;仅记录用 其他资料&#xff1a;https://www.freecodecamp.org/chinese/news/learn-typescript-beginners-guide/ 第三天 以下是视频(0:40-0:60) 的内容 目录第三天1 创建Todo…

JavaEE day6 初识JavaScript

什么是JS JS是通行在各种浏览器的一种语言&#xff0c;JAVA后端代码运行在服务器上&#xff0c;JS代码内容配合HTML&#xff0c;浏览器对JS代码进行解释运行&#xff0c;然后展现在浏览器上&#xff0c;web开发离不开JS。 一般步骤为&#xff1a;&#xff08;index.html与scr…

LinuxC—高级IO

高级IO 1 非阻塞IO/有限状态机编程 1.1 基本概念 定义 有限状态机(Finite State Machine) 缩写为 FSM&#xff0c;状态机有 3 个组成部分&#xff1a;状态、事件、动作。 状态&#xff1a;所有可能存在的状态。包括当前状态和条件满足后要迁移的状态。事件&#xff1a;也称为…

自动驾驶环境感知——视觉传感器技术

文章目录1. 摄像头的成像原理1.1 单目视觉传感器的硬件结构1.2 单目视觉的成像原理 –小孔成像模型1.3 单目视觉的成像原理 – 像素坐标系1.4 单目视觉三维坐标系转换 – 外参1.5 单目视觉的坐标系转换 – 从世界坐标点到像素坐标1.6 单目视觉的特性2. 视觉传感器的标定2.1 视觉…