数据结构--图解单链表

news2024/12/24 10:25:00

学习链表最重要的就是会画图,尤其是要理解链表的逻辑结构和物理结构,理解链表的底层原理才能使用的如鱼得水。 希望这篇文章可以帮助各位,记得关注收藏哦;若发现问题希望私信博主,十分感谢。

当然学习链表是需要大家对指针和结构体能够较为熟练的使用,尤其是指针,需要能够理解一级指针和二级指针,所以如果大家对指针不够熟练的话,可以去看一下博主的文章。

链表的概念及结构

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

链表的图形化解释

单链表的物理结构

从物理结构中我们可以看出,头节点plist存储的是下一个节点的地址,而下一个节点也通过一种结构去存储了它下一个节点的地址,以此类推,虽然在物理存储结构上是非连续,非顺序的存储方式,但是可以通过其存储的地址精准找到一下个节点,从而得到了整个链表。 

从物理结构中可以看出,链表分为两个部分

  单链表的逻辑结构

我们可以说链表在逻辑结构上是连续的,但是在现实的物理结构中,是没有箭头这种链接形式,箭头在逻辑结构中主要是为了理解更加方便,从逻辑结构上可以更加清晰的发现,链表之间的链接是通过上一个节点存储的地址去找到下一个节点。

单链表的初始化

从物理结构中可以发现,单链表需要使用到两种数据,一个是Data,类型是根据自己要存储的类型随时改变的,例如int或者float等等。还有一个就是指针类型的数据,用来存储下一个节点位置。

标准的初始化        

typedef int SLTDateType;
typedef struct SLTNode
{
	SLTDateType data;
	struct SLTNode* next;
}SLTNode;
  1.  在之前的文章中,我们也经常使用 typedef 去重命名数据类型,这样做的好处就是,当类型发生改变的时候,我们可以直接在头文件中改变一个 int 就行,否则你就要在所有文件中找到 int 去挨个改变了。
  2. struct SLTNode* next  很多同学非常疑惑,为什么这个指针的类型是结构体类型,这就考验到同学们对于指针类型的理解,其实指针类型的确定是与它指向的类型保持一致,我们是使用结构体创建的链表,那么指针指向的下一个节点也必然是一个结构体。

初始化的经典错误 

很多同学在初始化的时候经常会写成这样

typedef int SLTDateType;
typedef struct SLTNode
{
	SLTDateType data;
	SLTNode* next;
}SLTNode;

 他的想法就是,我已经将结构体命名为SLTNode了,那么就可以使用SLTNode进行命名了,但是问题在于程序是自上而下运行的,先运行结构体,在运行重命名,所以这样写出来之后,结构体内部不会识别出SLTNode这是个结构体。

接口实现

在学会使用链表之前最重要的是要先学会链表的各种接口是如何实现的,比如头插头删,尾插尾删等等,虽然之后大家都是直接调用接口,但是只有先理解基本的实现原理,才会明白哪种接口效率高,什么场景下适合用哪种接口。

创建新节点

因为之后进行尾插头插或者某一个位置插入的程序中,总要创建新的节点,所以我们可以先写一个创建节点的函数,之后直接使用。

SLTNode* BuyListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("fail in malloc");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

创建过程比较简单,就不多赘述了,如果还是对malloc有问题的同学,请看一下这篇文章 可参考动态内存管理icon-default.png?t=N7T8https://blog.csdn.net/Senyu_nuanshu/article/details/131934727

打印链表 

提前写出链表的打印,接口写完之后马上使用打印去判断一下接口是否写错了

void PrintSLT(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
	printf("\n");
}

这里有一个点需要注意,就是尽量不要直接使用头节点,而是创建一个变量保存头节点的地址,使用这个变量实现代码,因为接口有很多种,不可能每次都使用一种,基本都是各种接口的混合使用,当头节点的地址被改变了就会影响下一个接口的使用了 

cur = cur->next;会在尾插详细解释 

单链表尾插

尾插的逻辑结构

//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x)
{
	assert(pphead);
	SLTNode* newnode = BuyListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}
  1.  断言(assret)的意义:接下来看到的接口实现当中,基本上都可以看到断言的存在,那么它存在的意义是什么呢,其实就是一种自我警告,判断一下断言里面的内容是不是空指针,当然,断言不止可以判断指针,别的也可以,详细各位可以去网上搜索一下。在这个断言里面,我们判断的是二级指针pphead是否为空,那么pphead是代表什么的呢?我们知道,一级指针可以代表一个变量或者一个函数的地址,那么二级指针其实就是代表了一级指针的地址,既然有了一级指针为什么呢还要使用二级指针呢?因为接口的本质就是函数之间的调用,而函数的参数又分为形参和实参,如果你使用一级指针传参,又使用一级指针接收,那么接收的一级指针其实是形参,形参的改变不影响实参,而我们插入删除的时候改变的是什么,是一级指针,那你函数内部的改变不影响函数外部,岂不是无用功了。所以我们就需要使用二级指针接收一级指针的传参,让二级指针去改变一级指针,因为二级指针就是一级指针的地址,当我们运行二级指针的时候,他就会自动找到一级指针所在的位置,去改变一级指针了。
  2. cur->next是什么意思:结构体里面有两个变量,next就是其中一个,表示下一个节点的地址,我们让cur = cur->next;其实就是将指针的位置换到了下一个节点中了。
  3. 那这段代码的整体逻辑是什么呢:首先第一步就是断言,要判断一下二级指针是不是空指针,因为它存储的是一级指针的地址,它要是空指针,说明开始就是错误的,没存储上,那接下来的所有都无法运行;然后我们要判断一下这个链表是不是空链表,各位要想明白,空链表是可以插入的,如果是空链表,其实尾插就变成头插了,尾插正常逻辑应该是先找到链表的最后一个节点,如何找呢,链表的最后一个节点的特征就是它的next是NULL,如果是空链表,压根就没有next,那按照正常逻辑肯定找不到,所以要先排除这个情况;接下来就是找尾了,同样道理,不要轻易动用头节点,找到尾之后将尾节点next变成newnode的地址。

单链表尾插的经典错误

//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x)
{
	assert(pphead);
	SLTNode* newnode = BuyListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur)
		{
			cur = cur->next;
		}
		cur= newnode;
	}
}

想要了解这个经典的错误,就要理解单链表的物理结构

尾插的物理结构

首先内存中链表的物理结构是没有箭头的,正常是下面的样子

 

当上面的代码刚开始运行的时候,在物理结构上是下面的样子;cur存储了头节点的地址,接下来的  cur=cur->next  使得cur不断更新,存储的一直都是下一个节点的地址。

当不断更新之后,当cur的最终结果NULL的时候,跳出循环;此时cur存储的是4这个节点的next

运行cur = newnode;那么就变成下面的情况

 

从这图中我们可以看出来,cur是逻辑结构里面的连接线吗,其实不是对吧,他就是一个临时变量来存储地址的, cur = newnode;cur确实存储了newnode的地址,但是cur是临时变量,除了函数就被销毁了,4这个节点就很尴尬啊,4想着我把我的地址给你了啊,你怎么还拿着newnode的地址跑了呢。其实尾插的本质是什么,就是将原尾节点中要存储新的尾节点的地址。那么cur的作用应该是什么,通俗一点就是更新链表的作用,4的地址给了cur,cur也判断出这是个尾节点了,那么就应该将4的next变成newnode的地址,就是cur->next = newnode;同时还有一处错误,就是判断条件啊,我们要判断到4这个节点就应该出来了,要是用cur去作为判断条件,最终cur就变成4的next了,它应该是一个桥梁作用,指引着4去找到newnode,不是自己变成newnode。

单链表头插

从图中其实可以看出,头插其实很简单,就是把节点之间的关系换一下 

//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDateType x)
{
	assert(pphead);
	SLTNode* newnode = BuyListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

 

单链表尾删

链表的空间是使用malloc函数开辟出来的,所以在删除的时候就要多考虑一些,而且链表里面都是指针,一旦没有将删除的指针置空,就会导致野指针的出现。

//单链表尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	//链表中只有一个元素的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//链表中的元素大于1个的时候
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next->next)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}
  1. 从代码中我们可以看出来,我们要考虑一种特殊情况,就是链表中只有一个元素,因为删完这个元素之后,就剩下头节点了,这个时候头节点如果不置空,就是导致其变成野指针。
  2. 如果是大于一个元素,那么尾删还是要先找到尾巴,然后在删除,使用free将空间还给操作系统,最重要的是将其置空。
  3. 断言:尾删为什么要断言*pphead,因为单链表为空的时候是不能删除的,啥都没有删除什么,所以断言的用法要灵活,不要找规律,而是要理解。

单链表头删

//单链表头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* prev = *pphead;
	*pphead = (*pphead)->next;
	free(prev);
	prev = NULL;
}

单链表节点查找

//单链表结点查找
SLTNode* SLTNodeFind(SLTNode* phead, SLTDateType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
			cur = cur->next;
	}
	return NULL;
}

指针问题:不是所有的接口实现都需要用到二级指针,只有要修改链表的时候才需要用到,像查找使用一级指针就足够了

单链表结点插入(在pos之前插入)

首先解释一下pos是什么,pos就是单链表节点查找的时候返回的节点位置。

  1. 从图中可以发现,从pos之前插入是比较麻烦的,因为你要找到pos的位置,但是你插入的时候还需要pos之前的节点位置才能正常插入 ,所以这个时候就非常考验大家对链表的理解
  2. 当然还要分成两种情况去考虑,如果pos就是第一个节点,那就变成头插了

//单链表结点插入(在pos之前插入)
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pphead);
	assert(pos);
	SLTNode* newnode = BuyListNode(x);
	//头插
	if (*pphead == pos)
	{
		newnode->next = pos;
		*pphead = newnode;
	}
	//非头插
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}

}

空链表可以插入,但是要确保pos的位置是存在链表当中的,其实就是pos不能为NULL,因为我们是使用自己写的查找程序去寻找,当找不到的时候就会返回NULL;

 单链表结点插入(在pos之后插入) 

void SLTInsertBack(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pos);
	assert(pphead);
	SLTNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

最后赋值的顺序千万不能写错,因为要是先写pos->next = newnode;就会将pos->next节点改变了,那再写newnode->next = pos->next,newnode->next其实就变成newnode自己了。

单链表结点删除(删除pos位置的结点) 

  1. 头节点就是pos
  2. 头节点不是pos

头节点如果是pos,那其实就是头删了,但是如果头节点不是pos,就要注意一个问题,就是删除之后,pos前面的节点与后面的节点之间的链接问题。

删除pos 

从图中就可以非常清晰的看出如何在删除之后处理节点,其实就是要找到pos的前一个节点,找到它就可以找到后面的节点了 

//单链表结点删除(删除pos位置的结点)
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	if (*pphead == pos)
	{
		free(*pphead);
		*pphead = NULL;
	}

	else
	{
		SLTNode* plist = *pphead;
		while (plist->next != pos)
		{
			plist = plist->next;
			assert(plist->next);
		}
		plist->next = plist->next->next;
		free(pos);
	}
}

销毁单链表

//销毁单链表
void SLTDestory(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

销毁链表,不能直接free(phead),因为链表在物理结构上是不连续存储的,销毁链表必须要一个结点一个结点去销毁!!!!最后不要忘记把phead置为NULL。

总代码

头文件

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDateType;
typedef struct SLTNode
{
	SLTDateType data;
	struct SLTNode* next;
}SLTNode;
//创建一个结点
SLTNode* BuyListNode(SLTDateType x);
//销毁单链表
void SLTDestory(SLTNode** pphead);
//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDateType x);
//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x);
//单链表头删
void SLTPopFront(SLTNode** pphead);
//单链表尾删
void SLTPopBack(SLTNode** pphead);
//单链表结点查找
SLTNode* SLTNodeFind(SLTNode* phead, SLTDateType x);
//单链表结点删除(删除pos位置的结点)
void SLTErase(SLTNode** pphead, SLTNode* pos);
//单链表结点插入(在pos之前插入)
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x);
// 单链表结点插入(在pos之后插入)
void SLTInsertBack(SLTNode** pphead, SLTNode* pos, SLTDateType x);
//打印单链表
void PrintSLT(SLTNode* phead);

节点代码

#define _CRT_SECURE_NO_WARNINGS 1
#include"SL-list.h"

void PrintSLT(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
	printf("\n");
}


//创建一个结点
SLTNode* BuyListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("fail in malloc");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x)
{
	assert(pphead);
	SLTNode* newnode = BuyListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDateType x)
{
	assert(pphead);
	SLTNode* newnode = BuyListNode(x);
	SLTNode* cur = *pphead;
	newnode->next = cur->next;
	cur = newnode;
}
//单链表头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* prev = *pphead;
	*pphead = (*pphead)->next;
	free(prev);
	prev = NULL;
}
//单链表尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	//链表中只有一个元素的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//链表中的元素大于1个的时候
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next->next)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}

//单链表结点查找
SLTNode* SLTNodeFind(SLTNode* phead, SLTDateType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
			cur = cur->next;
	}
	return NULL;
}

//单链表结点插入(在pos之前插入)
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pphead);
	assert(pos);
	SLTNode* newnode = BuyListNode(x);
	//头插
	if (*pphead == pos)
	{
		newnode->next = pos;
		*pphead = newnode;
	}
	//非头插
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
			assert(prev->next);
		}
		newnode->next = pos;
		prev->next = newnode;
	}

}

//单链表结点删除(删除pos位置的结点)
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	if (*pphead == pos)
	{
		free(*pphead);
		*pphead = NULL;
	}

	else
	{
		SLTNode* plist = *pphead;
		while (plist->next != pos)
		{
			plist = plist->next;
			assert(plist->next);
		}
		plist->next = plist->next->next;
		free(pos);
	}
}

// 单链表结点插入(在pos之后插入)
void SLTInsertBack(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pos);
	assert(pphead);
	SLTNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

//销毁单链表
void SLTDestory(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

测试程序

#define _CRT_SECURE_NO_WARNINGS 1
#include"SL-list.h"

void test1()
{
	SLTNode* SLT = NULL;
	SLTPushBack(&SLT, 1);
	SLTPushBack(&SLT, 2);
	SLTPushBack(&SLT, 3);
	SLTPushBack(&SLT, 7);
	SLTPushBack(&SLT, 8);
	SLTPushBack(&SLT, 9);
	SLTPushBack(&SLT, 4);
	SLTPushFront(&SLT, 1);
	SLTPopBack(&SLT);
	SLTPopFront(&SLT);
	SLTNode* ret = SLTNodeFind(SLT, 3);
	SLTInsert(&SLT, ret, 20);
	SLTErase(&SLT, ret);
	PrintSLT(SLT);
	SLTNode* PHEAD = SLTNodeFind(SLT, 2);
	printf("%p ", PHEAD);

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

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

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

相关文章

【PWN · heap | unlink | free_hook】[SUCTF 2018 招新赛]unlink

在前期学习了unlink后&#xff0c;今天翻NSSCTF找到一道名为unlink的题目&#xff0c;尝试不看wp做。过程很顺利&#xff01; 前言 题目对于知识点unlink还是非常裸的&#xff0c;很直接&#xff0c;思路很清晰。 一、题目 二、思路浅析 通过对该程序的反编译&#xff0c;我们…

AGV与AMR的区别

如今&#xff0c;市面上最受关注的两类工业移动机器人分别是AGV和AMR。但大众对于两者的区别还是不甚了解&#xff0c;因此小编将通过这篇文章为大家详细解释。 一、概念阐述 【AGV 】 AGV (Automated Guided Vehicle) 即自动导引运输车&#xff0c;可指基于各种定位导航技术…

第二章:main 方法

系列文章目录 文章目录 系列文章目录前言一、main 方法总结 前言 main方法详解。 一、main 方法 //main方法的形式 public static void main(String[] args){}解释main方法main方法被虚拟机调用Java虚拟机需要调用类的main()方法&#xff0c;所以该方法的访问权限必须是publi…

C++类中的const使用

目录 一&#xff0c;const成员函数 1&#xff0c;const成员函数的语法 2&#xff0c;相同限定符之间的调用 3&#xff0c;不同限定符对象与函数的调用 4&#xff0c;不同限定符函数之间的调用 一&#xff0c;const成员函数 1&#xff0c;const成员函数的语法 将const修饰…

关于MySQL优化的思考二【性能分析工具、优化原则】

在实际的工作中&#xff0c;我们不免需要对SQL预计进行分析和优化&#xff0c;今天我们就来一起看下相关内容&#xff1a; SQL性能分析 SQL优化原则 1 SQL性能分析 对SQL进行性能分析&#xff0c;主要有&#xff1a; 查看慢SQL 通过profile详情查看 explain执行计划 1.1…

2023年【广东省安全员B证第四批(项目负责人)】考试及广东省安全员B证第四批(项目负责人)试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 广东省安全员B证第四批&#xff08;项目负责人&#xff09;考试考前必练&#xff01;安全生产模拟考试一点通每个月更新广东省安全员B证第四批&#xff08;项目负责人&#xff09;试题及解析题目及答案&#xff01;多…

Redhat7设置国内可用yum源

问题&#xff1a; 因为最近安装了redhat7&#xff0c;在使用的时候提示系统未注册订阅&#xff0c;无法使用官方的yum源进行安装软件。为此&#xff0c;我使用centos7国内的yum源替换redhat的官方的yum源实现软件安装。 “This system is not registered with an entitlement …

iOS:何为空指针和野指针

一&#xff1a;什么是空指针和野指针 1、空指针 ①.没有存储任何内存地址的指针就成为空指针&#xff08;NULL指针&#xff09; ②.空指针就是被赋值为0的指针&#xff0c;在没有被具体初始化之前&#xff0c;其值为0. //以下都是空指针&#xff0c;eg: Person *p1 NULL; …

CSDN每日一题学习训练——Java版(数据流的中位数、乘积最大子数组、旋转链表)

版本说明 当前版本号[20231113]。 版本修改说明20231113初版 目录 文章目录 版本说明目录数据流的中位数题目解题思路代码思路参考代码 乘积最大子数组题目解题思路代码思路参考代码 旋转链表题目解题思路代码思路参考代码 数据流的中位数 题目 中位数是有序列表中间的数。…

SPSS时间序列模型预测

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件请点击此链接下…

Leetcode刷题详解——解数独

1. 题目链接&#xff1a;37. 解数独 2. 题目描述&#xff1a; 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能…

Vue基础之组件通信(二)

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大二在校生 &#x1f921; 个人主页&#xff1a;坠入暮云间x &#x1f43c;座右铭&#xff1a;懒惰受到的惩罚不仅仅是自己的失败&#xff0c;还有别人的成功。 &#x1f385;**学习目标: 坚持每一次的学习打卡 文章…

ROS基础—话题通信的执行

1、打开终端启动roscore 2、手动添加命令到.bashrc Ⅰ.切换到home目录下&#xff0c;按Ctrlh显示隐藏的文件夹&#xff0c;找到并打开./bashrc。 Ⅱ.在./bashrc最后一行添加命令: # xxx是你工作空间的名称 source ~/xxx/devel/setup.bash 之后保存.bashrc文件即可。 这样子…

IMU识别实验鼠的运动轨迹

临床前通常要做小鼠行为试验。然而&#xff0c;研究人员在观察和分析其行为和反应上往往需要耗费大量时间。针对这种情况&#xff0c;结合目前IMU的飞速发展&#xff0c;香港的研究员们构思了一种基于惯性测量单元&#xff08;IMU&#xff09;的传感器&#xff0c;用于捕获实验…

保姆级vue-pdf的使用过程

第一步 引入vue-pdf npm install --save vue-pdf 第二步 按照需求我们慢慢进行 01.给你一个pdf文件的url&#xff0c;需要在页面渲染 代码 <template><div><pdfref"pdf":src"url"></pdf></div> </template> <…

vue3 setup() 高级用法

文章目录 前言一、选项式API 和 组合式API 区别用一张图告诉你它们的区别&#xff1a; 二、setup 具体怎么用&#xff1f;2.1、setup 什么时候执行&#xff1f;2.2、setup 数据和方法如何使用&#xff1f;2.3、setup 内部有 this 吗&#xff1f;2.4、setup 内钩子函数如何使用&…

SDL2 播放视频数据(YUV420P)

1.简介 这里以常用的视频原始数据YUV420P为例&#xff0c;展示视频的播放。 SDL播放视频的流程如下&#xff1a; 初始化SDL&#xff1a;SDL_Init();创建窗口&#xff1a;SDL_CreateWindow();创建渲染器&#xff1a;SDL_CreateRenderer();创建纹理&#xff1a;SDL_CreateText…

vivado产生报告阅读分析-常规报告2

1、Report I/O “ I/O Report ” &#xff08; I/O 报告 &#xff09; 用于替代 AMD ISE Design Suite PAD 文件。“ I/O Report ”可列出 &#xff1a; • “ Pin Number ” &#xff08; 管脚编号 &#xff09;&#xff1a; 表示器件中的所有管脚 • “ Signal Name ” …

vue3配置@别名

在项目开发中&#xff0c;通常我们是不写相对路径的&#xff0c;因为有些文件需要在不同的文件中使用&#xff0c;如果使用相对路径&#xff0c;那么我们每次去CV路径的时候就要重新修改。因此通常我们是写跟路径的&#xff0c;但是从头开始又太过于麻烦&#xff0c;因此我们使…

ChatGLM3本地部署运行(入门体验级)

文章目录 前言零 硬件小白基知填坑eForce Game Ready驱动程序CUDA常用命令 环境准备NVIDIA驱动更新CUDA安装 部署补充内容体验 前言 学习自B站up主技术爬爬虾&#xff0c;感谢up主提供的整合包&#xff01; 零 硬件 6GB以上显存的NVIDIA显卡&#xff08;品质越高&#xff0c…