单链表——“数据结构与算法”

2023-05-16

各位CSDN的uu们你们好呀,今天,小雅兰的内容终于是我们心心念念的单链表啦,这一块呢,是一个很重要的部分,也是一个对目前的我来说,比较困难的部分,下面,就让我们进入单链表的世界吧


之前小雅兰写过顺序表的内容:

顺序表(更新版)——“数据结构与算法”_认真学习的小雅兰.的博客-CSDN博客

顺序表存在一些问题:

  • 中间/头部的插入删除,时间复杂度为O(N)
  • 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  • 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

  

  

  

 思考:如何解决以上问题呢?下面给出了链表的结构来看看。


 链表

链表的概念及结构

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

 

现实中 数据结构中

 


 结构体里面的数据类型:

typedef int SLTDataType;

定义一个结构体,结构体内部嵌套了一个结构体的指针:

这个就是单链表

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

 单链表的打印:

//单链表的打印
void SLTPrint(SLTNode* phead)
{
	//可以不需要断言
	//因为:空链表也是可以打印的
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

意思是:首先,定义一个结构体的指针,该指针指向1,然后,对于cur,cur->data表示的是此结构体中的整型数据,cur->next表示的是2的地址,把cur->next赋值给cur,就是把这几块不连续的空间链接起来

表示:phead存的是第一个结点的地址,cur也存的是第一个节点的地址,就是把phead赋值给cur 

 头插

//头插
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	//assert(*pphead);
	//不能断言,链表为空,也需要能插入
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	newnode->next = *pphead;
	*pphead = newnode;
}

 

 

测试一下头插的功能:

void TestSList1()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
}
int main()
{
	TestSList1();
	return 0;
}

 

在写这段代码的过程中,很容易犯错误,可能会有很多人这样写代码:

//头插
void SLPushFront(SLTNode* phead, SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	newnode->next = phead;
	phead = newnode;
}
void TestSList1()
{
	SLTNode* plist = NULL;
	SLPushFront(plist, 1);
	SLPushFront(plist, 2);
	SLPushFront(plist, 3);
	SLPushFront(plist, 4);
	SLPushFront(plist, 5);
	SLTPrint(plist);
}
int main()
{
	TestSList1();
	return 0;
}

这是一个十分经典的错误:

传值调用了!!!

实参是形参的一份临时拷贝,对形参的修改并不会影响实参,所以phead的改变并不会影响plist

举一个简单的例子:Swap

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(&a, &b);
	printf("%d %d\n", a, b);
	return 0;
}

毫无疑问,这样写确实是正确的。

有的人在这边可能就会想:是不是只要用了指针就可以了呢?

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int main()
{
	int* px = 10;
	int* py = 20;
	Swap(px, py);
	printf("%d %d\n", px, py);
	return 0;
}

 这样写,那是绝对不行的,接下来,来看看正确的写法:

void Swap(int** p1, int** p2)
{
	int* tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int main()
{
	int* px = 10;
	int* py = 20;
	Swap(&px, &py);
	printf("%d %d\n", px, py);
	return 0;
}


我们会发现,在后续很多函数中,都需要用到创建结点这样一个功能,所以,可以把此功能封装成一个函数

//创建结点
void BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;//就相当于初始化一下
}

尾插

从指向1的地址变为指向2的地址 

循环所做的事

 

//尾插
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuyLTNode(x);
	//1.空链表
	//2.非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;//tail->next本质是解引用,用tail的值找到指向的那个内存块,在内存块里面找到tail
		}
		tail->next = newnode;
	}
}

 测试一下尾插的功能:

void TestSList2()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
}
int main()
{
	TestSList2();
	return 0;
}

下面,来更好地解释一下为什么这边需要用到二级指针:

void func1(int* p)
{
	*p = 10;
}
void func2(int** pp)
{
	*pp = (int*)malloc(sizeof(int) * 10);
}
void func3(SLTNode* pnode)
{
	pnode->next = NULL;
}
void func4(SLTNode** ppnode)
{
	*ppnode = NULL;
}
int main()
{
	//要改变int,就要传int的地址
	int a = 0;
	func1(&a);
	//要改变int*,就要传int*的地址
	int* ptr = NULL;
	func2(&ptr);
	//要改变结构体,就要传结构体的地址
	SLTNode node;
	func3(&node);
	//要改变结构体的指针,传结构体的指针的地址
	SLTNode* pnode;
	func4(&pnode);
	return 0;
}

尾删

一个典型的错误的写法:野指针问题

 

解决方式:

找到尾结点以及它的前一个结点

 

 

//尾删
void SLPopBack(SLTNode** pphead)
{
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	assert(*pphead);
	SLTNode* prev = NULL;//前一个结点
	SLTNode* tail = *pphead;
	//找尾
	while (tail->next != NULL)
	{
		prev = tail;
		tail = tail->next;
	}
	free(tail);
	prev->next = NULL;
}

 还可以找倒数第二个

//尾删
//找倒数第二个
void SLPopBack(SLTNode** pphead)
{
	//没有结点(空链表)
	//一个结点
	//多个结点
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	assert(*pphead);//暴力的检查

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
	
}

测试一下尾删的功能:

void TestSList3()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLTPrint(plist);
}

 

头删

头删和尾删都有三种情况:

  • 没有结点(也就是空链表)
  • 有一个结点
  • 有多个结点

//头删
void SLPopFront(SLTNode** pphead)
{
	//没有结点(空链表)
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	assert(*pphead);//暴力的检查
	//链表为空,不能头删
	//一个结点
	//多个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* del = *pphead;//不能直接free掉
		//如果直接free的话,就找不到下一个结点的地址啦
		*pphead = del->next;
		free(del);
	}
}

 

测试一下头删的功能:

void TestSList4()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLTPrint(plist);
}

 


 

 单链表查找

//头插、尾插、头删、尾删都要修改链表,所以要传二级指针
//单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	//不用assert,因为空链表也是可以查找的
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

测试一下单链表查找的功能:

void TestSList4()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 2);
	pos->data = 30;
	SLTPrint(plist);

}

 


任意位置数据的插入(pos之前插入)

 

//在pos的位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (*pphead == pos)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

后插

//在pos的位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

测试一下前插和后插的功能:

void TestSList5()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 3);
	if (pos != NULL)
	{
		SLTInsert(&plist, pos, 20);
	}
	SLTPrint(plist);
	pos = SLTFind(plist, 2);
	if (pos != NULL)
	{
		SLTInsertAfter(pos, 30);
	}
	SLTPrint(plist);
}

 

删除pos位置的值

//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

后删

//删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}
void TestSList5()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 5);
	SLTPrint(plist);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SLPushBack(&plist, 10);
	SLTPrint(plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLTPrint(plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 3);
	if (pos != NULL)
	{
		SLTInsert(&plist, pos, 20);
	}
	SLTPrint(plist);
	pos = SLTFind(plist, 2);
	if (pos != NULL)
	{
		SLTInsertAfter(pos, 30);
	}
	SLTPrint(plist);
	pos = SLTFind(plist, 7);
	if (pos != NULL)
	{
		SLTErase(&plist,pos);
	}
	SLTPrint(plist);

}


源代码如下:

SList.h的内容:

#define _CRT_SECURE_NO_WARNINGS 1
#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 SLPushFront(SLTNode** pphead, SLTDataType x);
//尾插
void SLPushBack(SLTNode** pphead, SLTDataType x);
//头删
void SLPopFront(SLTNode** pphead);
//尾删
void SLPopBack(SLTNode** pphead);
//单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在pos的位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x);
//在pos的位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos位置之前的值
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos);

SList.c的内容:

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//单链表的打印
void SLTPrint(SLTNode* phead)
{
	//可以不需要断言
	//因为:空链表也是可以打印的
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
//头插
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	//assert(*pphead);
	//不能断言,链表为空,也需要能插入
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	newnode->next = *pphead;
	*pphead = newnode;
}
//创建结点
SLTNode* BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;//就相当于初始化一下
	return newnode;
}
//尾插
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	//assert(*pphead);
	//不能断言,链表为空,也需要能插入
	SLTNode* newnode = BuyLTNode(x);
	//1.空链表
	//2.非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;//tail->next本质是解引用,用tail的值找到指向的那个内存块,在内存块里面找到tail
		}
		tail->next = newnode;
	}
}
尾删
// 找倒数第一个
//void SLPopBack(SLTNode** pphead)
//{
//	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
//	assert(*pphead);
//	SLTNode* prev = NULL;//前一个结点
//	SLTNode* tail = *pphead;
//	//找尾
//	while (tail->next != NULL)
//	{
//		prev = tail;
//		tail = tail->next;
//	}
//	free(tail);
//	prev->next = NULL;
//}
//尾删
//找倒数第二个
void SLPopBack(SLTNode** pphead)
{
	//没有结点(空链表)
	//一个结点
	//多个结点
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	assert(*pphead);//暴力的检查

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
	
}
//头删
void SLPopFront(SLTNode** pphead)
{
	//没有结点(空链表)
	assert(pphead);//链表为空,pphead也不能为空,因为它是头指针plist的地址
	assert(*pphead);//暴力的检查
	//链表为空,不能头删
	//一个结点
	//多个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* del = *pphead;//不能直接free掉
		//如果直接free的话,就找不到下一个结点的地址啦
		*pphead = del->next;
		free(del);
	}
}
//头插、尾插、头删、尾删都要修改链表,所以要传二级指针
//单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	//不用assert,因为空链表也是可以查找的
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos的位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (*pphead == pos)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}
//在pos的位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}
//删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

好啦,小雅兰今天的单链表的内容就到这里啦,内容还是非常多的,也比较难,小雅兰会继续加油学习的,冲冲冲!!!

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单链表——“数据结构与算法” 的相关文章

  • 主流VIO/VSLAM系统改造与工程化落地

    今天主要写针对主流VIO和VSLAM如VINS MONO和DSO的工程改造思路 肯定是有相当价值的 xff0c 总体写得比较简单 xff0c 需要具备软件 硬件 算法等各方面综合能力才能掌握主要路径 xff0c 具体实现方面以后由其他同事来
  • VIO与全局快门及轮速计的一些应用小技巧

    封面就用一个可爱的小车车 之前各种针对VIO xff0c VSLAM和VINS的工程注意事项都讲过了 今天的内容主要是针对VSLAM xff0c VIO的实用性 比如Td xff0c 同步对时 xff0c 内参 xff0c 外参这一串 最近
  • 关于机器人状态估计(12)-VIO/VSLAM的稀疏与稠密

    VIO三相性与世界观室内ALL IN ONE 首先以此链接先对近期工作的视频做个正经的引流 xff0c 完成得这么好的效果 xff0c 仅仅是因为知乎限流1分钟以内的视频 xff0c 导致整个浏览量不到300 xff0c 让人非常不爽 这套
  • 关于机器人状态估计(13)-线性代数有多重要?18.06总结

    太久没更新主要是在忙开发和测试 xff0c 这几个月被很多同学提问 xff0c 同时接触了一些实习生 普遍发现动手能力不错 xff0c 数学基础却差异很大 从我身边电子 xff0c CV或者SLAM做得比较杰出的朋友来看 xff0c 大家普
  • postman的安装与使用

    目录 第一部分 xff1a 基础篇postman1 安装postman进入postman官网 如果是mac系统可以直接点击mac app安装 第二部分 xff1a 进阶篇1 使用自带的脚本对接口进行测试 第一部分 xff1a 基础篇 pos
  • DMA案例 外设到内存搬运

    DMA案例 外设到内存搬运 需求 使用DMA的方式将串口接收缓存寄存器的值搬运到内存中 xff0c 同时闪烁LED1 CubeMX配置 串口配置 DMA配置 串口中断配置 用到的库函数 HAL UART ENABLE span class
  • 用rs_lidar雷达跑lio_sam

    1 准备工作 imu绑定串口有线连接雷达并能用rviz显示雷达点云用两个imu标定包标定imu在完成第二步必要的工作后 xff0c 配置LIO SAM config 下的params yaml参数 xff0c 更改之前建议备份在旁边复制粘贴
  • ubuntu18.04安装ros及解决rosdep update失败问题

    1 安装ros 转自 https blog csdn net qq 44830040 article details 106049992 ops request misc 61 257B 2522request 255Fid 2522 25
  • 球机是枪机和云台机的结合体

    1 枪机是监控类CCD摄像机中一种 枪机外观长方体 xff0c 前面是C CS镜头接口 xff0c 枪机不包含镜头 所谓的枪机主要从外型 镜头安装接口上区分 2 监控类摄像机主要有 xff1a 枪机 小半球 大半球 一体机 球机几个类别 枪
  • ROS学习笔记-1

    一 ROS简介 ROS全称Robot Operating System 机器人操作系统 ROS是适用于机器人的开源元操作系统 ROS集成了大量的工具 xff0c 库 xff0c 协议 xff0c 提供类似OS所提供的功能 xff0c 简化对
  • C语言指针详解(1)

    指针详解 之前我说过一篇关于指针在C语言中的基本使用 xff0c 这次我再来细讲一下指针的其他内容 目录 一 指针详解 1 指针定义 2 指针类型 3 野指针 4 如何规避野指针 xff1f 1 指针定义 指针理解的2个要点 xff1a 1
  • C语言结构体详解 (2) 结构体内存对齐,默认对齐数

    前言 上次 xff0c 我讲到了关于结构体的基本使用 xff0c 大家若感兴趣的话看一看我之前写的一篇结构体博客 xff0c 里面记载了我对于结构体的创建 初始化 嵌套结构体 结构体的访问访问方式和结构体传参方式等知识的见解 xff0c C
  • C语言——十进制转换十六进制

    请编写程序 xff0c 输入十进制数 xff0c 输出对应的十六进制数 输入格式 十进制非负整数 输出格式 对应的十六进制非负整数 要求 xff1a 十六进制数中的字母均为大写形式 输入样哩 5050 输出样例 13BA 代码输入 xff1
  • 如何编写头文件及使用Makefile

    头文件中应该写什么 xff1a 头文件可能会被任意源文件包含 xff0c 意味着头文件中的内容可能会在多个目标文件中存在 xff0c 要保证合并时不要冲突 重点 xff1a 头文件只编写声明语句 xff0c 不能有定义语句 全局变量声明 函
  • 剖析Linux内存中的/proc/meminfo参数

    PROC MEMINFO之谜 proc meminfo是了解Linux系统内存使用状况的主要接口 xff0c 我们最常用的 free vmstat 等命令就是通过它获取数据的 xff0c proc meminfo所包含的信息比 free 等
  • 看完秒懂:Linux DMA mapping机制分析

    说明 xff1a Kernel版本 xff1a 4 14ARM64处理器 xff0c Contex A53 xff0c 双核使用工具 xff1a Source Insight 3 5 xff0c Visio 1 概述 DMA xff08 D
  • linux网络编程-多进程实现TCP并发服务器

    服务端流程步骤 socket函数创建监听套接字lfd bind函数将监听套接字绑定ip和端口 listen函数设置服务器为被动监听状态 xff0c 同时创建一条未完成连接队列 xff08 没走完tcp三次握手流程的连接 xff09 xff0
  • Linux内核中断下半部工作队列(work queue)

    工作队列work queue 工作队列 xff08 work queue xff09 是中断下半部的一种实现机制 xff0c 主要用于耗时任务处理 xff0c 由内核线程代表进程执行 工作队列运行于进程上下文 xff0c 因此允许阻塞 运行
  • 手把手带你部署Ceph集群

    前言 xff1a Ceph作为开源的分布式文件系统 xff0c 可以轻松地将存储容量扩展到PB以上并拥有不错的性能 Ceph提供对象存储 块存储和文件系统三种存储方式 xff0c 如果不想花时间安装ceph xff0c 可以通过ceph d
  • Linux 内核安全增强—— stack canary

    一 背景知识 aarch64的函数栈 1 栈生长方向与push pop操作 栈是一种运算受限的线性表 入栈的一端为栈顶 xff0c 另一端则为栈底 其生长方向和操作顺序理论上没有限定 而在aarch64平台上 栈是向低地址方向增长的 STA

随机推荐

  • Linux下Makefile的简单编写与使用

    Makefile 一个工程文件中的源文件可能有很多 xff0c 并且不同的功能 模块等都放在不同的目录中 xff0c 常规的编译已经不能高效化的处理这样的问题 xff0c 而Makefile就是为解决这一问题而来 Makefile一旦写好
  • STM32 USART 串口DMA收发注意事项

    正常情况这里不介绍 目录 1 低波特率情况 xff0c 接收信号可能会出现干扰 2 波特率300时 xff0c DMA接收无法工作 3 波特率1200时DMA发送 4 具体现象如下 环境 xff1a 主频72M STM32F103C8 注意
  • STM32学习笔记———几种简单传感器的数据读取

    引言 传感器正如计算机的眼睛 从广义上讲 xff0c 传感器就是一种能感知外界信息 xff0c 并将这些信息按照一定规律转换成可用的电信号或其他形式的输出信号的装置 xff0c 达到对信息的存储 xff0c 传输 xff0c 控制的目的 本
  • 基于STM32F103C8T6的USART1串口的中断接收

    一 串口介绍 二 项目所需硬件 1 USB转串口模块 三 项目代码 一 串口介绍 USART Universal Synchronous Asynchronous Receiver Transmitter 通用同步 异步串行接收 发送器 U
  • 第5讲—寄存器编程

    编程方式两种 xff0c 一种是寄存器编程 xff0c 一种是函数库编程 什么是寄存器 stm32芯片 61 ARM内核生产Cortex内核 43 st公司 xff08 在内核基础上 xff09 开发stm32 寄存器是用来地址操作的 寄存
  • C语言运行HTTP代码示例

    include lt iostream gt include lt algorithm gt include lt cstring gt include 34 curl curl h 34 usingnamespace std static
  • 在Linux上实现HTTP请求

    在Linux上实现HTTP请求是一种非常有用的技能 xff0c 有时候我们需要从Web服务器获取数据 xff0c 而不是使用浏览器发起网络调用 HTTP是一种常用的网络协议 xff0c 可以用于在Linux上实现HTTP请求 要在Linux
  • Linux下设置堆栈系统参数

    Linux系统下有几个与堆栈相关的系统参数 xff1a 1 ulimit s xff1a 此参数用于限制进程的堆栈大小 可以使用该命令来查看和更改进程的堆栈大小限制 2 proc sys kernel stack protect xff1a
  • Leetcode每日一题——“消失的数字”

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰又开新专栏了 xff0c 以后会在Leetcode上面进行刷题 xff0c 尽量每天写一道 xff0c 请大家监督我 xff01 xff01 xff01 好啦 xff0c 让
  • Leetcode每日一题——“轮转数组”

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰的内容是轮转数组 xff0c 下面 xff0c 让我们进入轮转数组的世界吧 小雅兰之前其实就已经写过了字符串旋转的问题了 xff1a C语言刷题 xff08 7 xff09
  • Python一行命令搭建HTTP服务器并外网访问【内网穿透】

    文章目录 1 前言2 本地http服务器搭建2 1 Python的安装和设置2 2 Python服务器设置和测试 3 cpolar的安装和注册3 1 Cpolar云端设置3 2 Cpolar本地设置 4 公网访问测试5 结语 转载自远程内网
  • 顺序表(更新版)——“数据结构与算法”

    各位CSDN的uu们你们好呀 xff0c 今天小雅兰又来更新新专栏啦 xff0c 其实之前我就已经写过了顺序表的内容 xff0c 只是之前的内容不是最新版的顺序表 xff0c 现在 xff0c 我来更新一下最新版的顺序表 xff0c 下面
  • Leetcode每日一题——“移除元素”

    各位CSDN的uu们你们好呀 xff0c 小雅兰又来啦 xff0c 今天 xff0c 小雅兰的内容是移除元素 xff0c 下面 xff0c 让我们进入Leetcode的世界吧 说明 为什么返回数值是整数 xff0c 但输出的答案是数组呢 请
  • Leetcode每日一题——“回文数”

    各位CSDN的uu们 xff0c 你们好呀 xff0c 今天小雅兰又来刷力扣啦 xff0c 今天的题目是回文数 xff0c 下面 xff0c 让我们进入回文数的世界吧 示例 1 xff1a 输入 xff1a x 61 121 输出 xff1
  • 【社区图书馆】《新程序员005:开源深度指南 & 新金融背后的科技力量》

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰来给大家推荐一本书 xff0c 此书的书名为新程序员005 xff1a 开源深度指南 amp 新金融背后的科技力量 xff0c 为什么小雅兰今天要给大家推荐这样一本书呢 xf
  • Nodejs快速搭建简单的HTTP服务器,并发布公网远程访问

    文章目录 前言1 安装Node js环境2 创建node js服务3 访问node js 服务4 内网穿透4 1 安装配置cpolar内网穿透4 2 创建隧道映射本地端口 5 固定公网地址 转载自内网穿透工具的文章 xff1a 使用Node
  • Leetcode每日一题——“合并两个有序数组”

    各位CSDN的uu们你们好呀 xff0c 又到小雅兰的愉快题解时候啦 xff0c 今天 xff0c 我们的题目内容是合并两个有序数组 xff0c 下面 xff0c 让我们进入合并两个有序数组的世界吧 示例 1 xff1a 输入 xff1a
  • Leetcode每日一题——“链表的中间结点”

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰愉快的刷题内容是链表的中间结点 嘿嘿 xff0c 小雅兰的单链表还在偷懒ing xff0c 一直没有更新 xff0c 最近应该会更新出来 下面 xff0c 就让我们进入链表的
  • VRPN-体验

    VRPN简介 VRPN提供封装在库里的一套类 xff0c 在VR系统中 xff0c 用来为应用程序和物理外设提供网络传输接口 详细点击这里 VRPN源码获取 git clone https github com vrpn vrpn git
  • 单链表——“数据结构与算法”

    各位CSDN的uu们你们好呀 xff0c 今天 xff0c 小雅兰的内容终于是我们心心念念的单链表啦 xff0c 这一块呢 xff0c 是一个很重要的部分 xff0c 也是一个对目前的我来说 xff0c 比较困难的部分 xff0c 下面 x