3。数据结构(1)

news2024/12/23 10:56:01

嵌入式软件开发第三部分,各类常用的数据结构及扩展,良好的数据结构选择是保证程序稳定运行的关键,(1)部分包括数组,链表,栈,队列。(2)部分包括树,堆,散列表,图。

一 数组

优点:按照索引查询速度快、遍历数组方便

缺点:

1 数组大小固定后无法扩容

2 数组只能存储一种类型的数据

3 添加删除慢(需要移动其它元素)

使用场景:频繁查询,对存储空间要求不大;增加删除少的情况

所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。

 遍历一维数组

#include <stdio.h>
 
int main ()
{
   int n[ 10 ]; /* n 是一个包含 10 个整数的数组 */
   int i,j;
 
   /* 初始化数组元素 */         
   for ( i = 0; i < 10; i++ )
   {
      n[ i ] = i + 100; /* 设置元素 i 为 i + 100 */
   }
   
   /* 输出数组中每个元素的值 */
   for (j = 0; j < 10; j++ )
   {
      printf("Element[%d] = %d\n", j, n[j] );
   }
 
   return 0;
}

1 多维数组

type name[size1][size2]...[sizeN];

二维数组

多维数组最简单的形式是二维数组。一个二维数组,在本质上,是一个一维数组的列表。声明一个 x 行 y 列的二维整型数组,形式如下:

type arrayName [ x ][ y ];

int x[3][4];

因此,数组中的每个元素是使用形式为 a[ i , j ] 的元素名称来标识的,其中 a 是数组名称,i 和 j 是唯一标识 a 中每个元素的下标。

 初始化二维数组

int a[3][4] = { 
{0, 1, 2, 3} ,   /*  初始化索引号为 0 的行 */
{4, 5, 6, 7} ,   /*  初始化索引号为 1 的行 */
{8, 9, 10, 11}   /*  初始化索引号为 2 的行 */
};

int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

 访问单个二维数组元素

int val = a[2][3];

上面的语句将获取数组中第 3 行第 4 个元素。您可以通过上面的示意图来进行验证。让我们来看看下面的程序,我们将使用嵌套循环来处理二维数组:

 遍历二维数组

#include <stdio.h>
 
int main ()
{
   /* 一个带有 5 行 2 列的数组 */
   int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};
   int i, j;
 
   /* 输出数组中每个元素的值 */
   for ( i = 0; i < 5; i++ )
   {
      for ( j = 0; j < 2; j++ )
      {
         printf("a[%d][%d] = %d\n", i,j, a[i][j] );
      }
   }
   return 0;
}

2 把数组作为参数传递给函数

如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。

1)形式参数是一个指针(常用)

void myFunction(int *param) { . . . }

 2)形式参数是一个已定义大小的数组

void myFunction(int param[10]) { . . . }

 3)形式参数是一个未定义大小的数组:(常用)

void myFunction(int param[]) { . . . }

例子

#include <stdio.h>
 
/* 函数声明 */
double getAverage(int arr[], int size);
 
int main ()
{
   /* 带有 5 个元素的整型数组 */
   int balance[5] = {1000, 2, 3, 17, 50};
   double avg;
 
   /* 传递一个指向数组的指针作为参数 */
   avg = getAverage( balance, 5 ) ;
 
   /* 输出返回值 */
   printf( "平均值是: %f ", avg );
    
   return 0;
}
 
double getAverage(int arr[], int size)
{
  int    i;
  double avg;
  double sum=0;
 
  for (i = 0; i < size; ++i)
  {
    sum += arr[i];
  }
 
  avg = sum / size;
 
  return avg;
}

3 从函数返回数组

C 语言不允许返回一个完整的数组作为函数的参数。但是,您可以通过指定不带索引的数组名来返回一个指向数组的指针。

通过声明一个返回指针的函数,从函数中返回数组

int * myFunction() { . . . }

例子: 生成 10 个随机数,并使用数组来返回它们

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
/* 要生成和返回随机数的函数 */
int * getRandom( )
{
  static int  r[10];
  int i;
 
  /* 设置种子 */
  srand( (unsigned)time( NULL ) );
  for ( i = 0; i < 10; ++i)
  {
     r[i] = rand();
     printf( "r[%d] = %d\n", i, r[i]);
 
  }
 
  return r;
}
 
/* 要调用上面定义函数的主函数 */
int main ()
{
   /* 一个指向整数的指针 */
   int *p;
   int i;
 
   p = getRandom();
   for ( i = 0; i < 10; i++ )
   {
       printf( "*(p + %d) : %d\n", i, *(p + i));
   }
 
   return 0;
}

4 指向数组的指针

众所周知,数组名是一个指向数组中第一个元素的常量指针。因此,在下面的声明中:

double balance[50];

balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。因此,下面的程序片段把 p 赋值为 balance 的第一个元素的地址:

double *p;
double balance[10];

p = balance;

然后就可以使用*p、*(p+1)、*(p+2) 等来访问数组元素

访问数组

printf("*(p + %d) : %f\n", i, *(p + i) );

printf("*(balance + %d) : %f\n", i, *(balance + i) );

二 栈

栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作 。

特点: 先进后出(压子弹)

使用场景 :栈常应用于实现递归功能方面的场景,例如斐波那契数列。

1、栈的定义
首先对栈进行定义,构建一个简单的结构体,采用typedef struct 的类型,然后包含栈顶、栈底和栈内元素三个部分

typedef struct{
    char data[100];
    int top;
    int bottom;
}stack;//结构体名为stack

2、栈的创建
然后是栈的构建,来为栈开辟内存空间,存储我们进行入站出栈的元素。只需在栈操作开始前进行一次栈的构建即可,无需重复。

stack *StackCreate(){
    stack *p=(stack*)malloc(sizeof(stack));//分配新空间 
    if(p==NULL)//分配失败 
    return 0;
    p->bottom=p->top=0;//分配成功 
    return p;
}
上述代码段为向p内分配内存,成功则返回p

3、入栈(压栈)
栈构建完毕后就开始进行栈的操作了,首先就是如何将字符、数字等我们想要的内容送入栈中,就需要进行入栈操作。

void StackInput(stack *p,char str){
    p->data[p->top]=str;//存入栈中 
    p->top++;//栈顶指针加1 

将字符str存入栈中,位置为top,只存在data中,然后栈top++

4、出栈
当我们想要栈顶的元素时,就用到了出栈的操作

char StackOutput(stack *p,char str){
    if(p->top!=p->bottom){//栈非空 
        str=p->data[p->top-1];//栈顶内容输出 
        p->top--;//栈顶减1 
        return str;
    }

因为top位置为栈顶值的下一个,因此将data中top-1的值输出,栈顶top–,返回的值为栈顶元素str

5、栈的遍历
但我们想要输出栈内存储的所有元素,那么就需要使用到遍历

void StackPrint(stack *p){
    while(p->top!=p->bottom){
        printf("%c",p->data[p->top-1]);
        p->top--;
    }
}

例子:将字符串压入栈中,然后从栈中输出

int main(){
    int i;
    stack *p;//定义栈名 
    char a[10]="asdfgh";
    p=StackCreate();//创建栈      
    for(i=0;i<strlen(a);i++)//将字符串a的字符入栈 
    StackInput(p,a[i]);
    printf("输出栈中所有字符:\n"); 
    StackPrint(p);
}

#include<stdio.h>
#include<malloc.h>
#include<string.h>

//定义栈 
typedef struct{
	char data[100];
	int top;
	int bottom;
}stack;

//创建栈
stack *StackCreate(){
	stack *p=(stack*)malloc(sizeof(stack));//分配新空间 
	if(p==NULL)//分配失败 
	return 0;
	p->bottom=p->top=0;//分配成功 
	return p;
}

//入栈
void StackInput(stack *p,char str){
	p->data[p->top]=str;//存入栈中 
	p->top++;//栈顶指针加1 
} 

//出栈 
char StackOutput(stack *p,char str){
	if(p->top!=p->bottom){//栈非空 
		str=p->data[p->top-1];//栈顶内容输出 
		p->top--;//栈顶减1 
		return str;
	}
} 

//输出 
void StackPrint(stack *p){
	while(p->top!=p->bottom){
		printf("%c",p->data[p->top-1]);
		p->top--;
	}
}

//主函数 
int main(){
	int i;
	stack *p;//定义栈名 
	char a[10]="asdfgh";
	p=StackCreate();//创建栈 	 
	for(i=0;i<strlen(a);i++)//将字符串a的字符入栈 
	StackInput(p,a[i]);
	printf("输出栈中所有字符:\n"); 
	StackPrint(p);
}

三 队列(Queue)

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(head)进行删除操作,而在表的后端(tail)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

特点:先进先出(漏桶算法)

使用场景 : 因为队列先进先出的特点,在多线程阻塞队列管理中非常适用。

使用链表来实现队列

1.头文件

所用标准库

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

定义结构体

typedef int QDateType;//队列存储数据类型

typedef struct QueueNode //队列元素节点
{
    QDateType val;
    struct QueueNode* next;
}QueueNode;

typedef    struct Queue //队列
{
    QueueNode* head;
    QueueNode* tail;
}Queue;

函数声明

void QueueInti(Queue* pq);
// 队列初始化
void QueueDestory(Queue* pq);
// 队列的销毁
void QueuePush(Queue* pq, QDateType x);
// 入队
void QueuePop(Queue* pq);
// 出队
QDateType QueueFront(Queue* pq);
// 取出队首元素
int QueueSize(Queue* pq);
// 求队列的长度
bool QueueEmpty(Queue* pq);
// 判断队是否为空

2 各功能函数的实现

队列的初始化,将头、尾置为空指针即可

void QueueInti(Queue* pq)
{
	assert(pq); //防止pq为空指针
	pq->head = pq->tail = NULL;
}

队列的销毁,遍历队列元素,然后将每一个元素释放。

void QueueDestory(Queue* pq)
{
	assert(pq); //防止pq为空指针
	QueueNode* cur = pq->head;
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->tail = pq->head = NULL;
}

入队,对于入队,我们首先需要去开辟一个新的节点来存储数据,然后将这个节点加入到tail后即可。此时我们就要分别考虑。

  1. 如果为空队列,那么我们不仅要改变tail,还要改变head的值(head = tail)
  2. 如果不为空队列,只用改变tail即可。
void QueuePush(Queue* pq, QDateType x)
{
	assert(pq); //防止pq为空指针

	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NULL == newNode)
	{
		printf("malloc error\n");
		exit(-1);
	}
	newNode->val = x;
	newNode->next = NULL;//开辟一个新节点存储数据

	if (pq->tail == NULL)//判断是否为空队列
	{
		assert(pq->head == NULL);
		pq->head = pq->tail = newNode;
	}
	else
	{
		pq->tail->next = newNode;
		pq->tail = newNode;
	}
}

出队,同样需要考虑两种情况

  1. 队列为空,改变head的同时改变tail(head = tail)
  2. 队列不为空,改变head即可。
void QueuePop(Queue* pq)
{
	assert(pq);//防止pq为空指针
	assert(pq->head && pq->tail); //防止队列为空队列
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QueueNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

取出队首元素,直接访问头节点取出即可

QDateType QueueFront(Queue* pq)
{
	assert(pq);//防止pq为空指针
	assert(pq->head && pq->tail); //防止队列为空队列

	return pq->head->val;
}

判断是否为空队列,只需要判断头指针是否为NULL,如果是则为空

bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->head == NULL;
}

求队伍长度,创建一个变量,遍历队伍求长度。

int QueueSize(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	int count = 0;
	while (cur)
	{
		cur = cur->next;
		count++;
	}
	return count;
}

用链表实现队列(先进先出)完整代码

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

typedef int QDateType;

typedef struct QueueNode
{
	QDateType val;
	struct QueueNode* next;
}QueueNode;

typedef	struct Queue
{
	QueueNode* head;
	QueueNode* tail;
}Queue;

void QueueInti(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

void QueueDestory(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->tail = pq->head = NULL;
}

void QueuePush(Queue* pq, QDateType x)
{
	assert(pq);

	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NULL == newNode)
	{
		printf("malloc error\n");
		exit(-1);
	}
	newNode->val = x;
	newNode->next = NULL;

	if (pq->tail == NULL)
	{
		assert(pq->head == NULL);
		pq->head = pq->tail = newNode;
	}
	else
	{
		pq->tail->next = newNode;
		pq->tail = newNode;
	}

}

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head && pq->tail);
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QueueNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->head == NULL;
}

QDateType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head);

	return pq->head->val;
}

int QueueSize(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	int count = 0;
	while (cur)
	{
		cur = cur->next;
		count++;
	}
	return count;
}

四 链表(基于链表实现队列和栈)

链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域(内存空间),另一个是指向下一个结点的指针域。根据指针的指向,链表能形成不同的结构。例如:单链表,双向链表,循环链表等。

优点:

  • 很常用的一种数据结构,不需要初始化容量,可以任意加减元素;
  • 添加或删除元素时只需要改变前后两个元素结点的指针域指向地址即可,所以添加删除速度很快。

缺点:

  • 因含有大量的指针域,占用空间较大;

  • 查找元素需要遍历链表来查找,非常耗时。

使用场景:数据量较小,需要频繁添加删除操作的场景。

8种链表结构

循环带头单链表

循环带头双向链表

循环不带头单链表

循环不带头双向链表

非循环带头单链表

非循环带头双向链表

非循环不带头单链表

非循环不带头双向链表

(1)单向或者双向

 (2)带头或者不带头

 (3)循环或者非循环

常用的为

非循环不带头单链表

 头文件及函数声明

// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
 SLTDateType data;
 struct SListNode* next;
}SListNode;
 
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);

申请内存函数


SListNode* BuySListNode(SLTDateType x)
{
	SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
	if (tmp == NULL)
	{
		printf("无法给节点开辟空间\n");
		return NULL;
	}
	else
	{
		tmp->data = x;
		tmp->next = NULL;
		return tmp;
	}
}

打印单链表内容函数

// 单链表打印
void SListPrint(SListNode* plist)
{
	SListNode* head = plist;
	while (head != NULL)
	{
		printf("%d ", head->data);
		head = head->next;
	}
}

单链表尾部插入函数

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	
	SListNode* newnode = BuySListNode(x);
	if ( *pplist== NULL)
	{
		*pplist = newnode;
	}
	else
	{
		SListNode* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

单链表尾部删除函数

// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
	assert(*pplist);
	SListNode* cur = *pplist;
	SListNode* prev = NULL;
	if (cur->next == NULL)
	{
		free(cur);
		*pplist = NULL;
	}
	else
	{
		while (cur->next != NULL)
		{
			prev = cur;
			cur = cur->next;
		}
		free(cur);
		prev->next = NULL;
	}
}

单链表头部插入函数

// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		newnode->next = *pplist;
		*pplist = newnode;
	}
}

单链表头部删除函数

// 单链表头删
void SListPopFront(SListNode** pplist)
{
	assert(*pplist);
	SListNode* cur = *pplist;
	if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	else
	{
		cur = cur->next;
		free(*pplist);
		*pplist = cur;
	}
}

单链表查找函数

// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	assert(plist);
	while (plist != NULL)
	{
		if (plist->data == x)
		{
			return plist;
		}
		plist = plist->next;
	}
	return NULL;
}

单链表在 pos 位置之后插入x 函数

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* newnode = BuySListNode(x);
		newnode->next = pos->next;
		pos->next = newnode;
}

单链表删除 pos 位置之后的值 函数

// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		printf("后面无数据\n");
		return;
	}
	else
	{
		SListNode* prev = pos;
		SListNode* cur = pos->next;
		prev->next = cur->next;
		free(cur);
		cur = NULL;
	}
 
}

循环带头双向链表

 头文件及函数声明

// 2、带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode 
{
	ListDateType val;
	struct ListNode* prev;
	struct ListNode* next;
}ListNode;	
 
//初始化双向链表
ListNode* ListInit(ListNode* phead);
//双向链表打印
void ListPrint(ListNode* phead);
// 创建返回链表的头结点.
ListNode* BuyList(ListDateType x);
//双向链表尾插
void ListPushBack(ListNode* phead,ListDateType x);
//双向链表尾删
void ListPopBack(ListNode* phead);
//双向链表头插
void ListPushFront(ListNode* phead, ListDateType x);
//双向链表头删
void ListPopFront(ListNode* phead);
//双向链表查找
ListNode* ListFind(ListNode* pHead, ListDateType x);
//在pos之前插入
void ListInsert(ListNode* pos, ListDateType x);
//删除pos位置
void ListErase(ListNode* pos);

初始化双向链表函数

//初始化双向链表
ListNode* ListInit(ListNode* phead)
{
	phead = BuyList(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

双向链表打印函数

//双向链表打印
void ListPrint(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
 
}

返回链表的头节点 函数 

返回链表的头节点是非常重要的,因为它是整个链表的入口。通过获取链表的头节点,可以迅速访问链表的所有节点,并对这些节点进行操作。

在很多链表应用中,我们需要遍历整个链表或者将整个链表反转,而这些操作都需要通过获取链表头节点来实现。同时,当我们需要对链表进行删除、插入或修改等操作时,我们也需要知道链表的头节点才能进行这些操作。

此外,许多算法问题也需要要求返回链表的头节点,例如找到链表的倒数第k个节点、判断链表是否存在环等问题,都需要访问链表的头节点。

因此,获取链表的头节点是基于链表进行各种操作和解决问题的基础,是链表操作的必备步骤之一。

// 创建返回链表的头结点
ListNode* BuyList(ListDateType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		printf("BuyList fail\n");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;
    return newnode;
}

双向链表尾部插入函数

//双向链表尾插
void ListPushBack(ListNode* phead, ListDateType x)
{
	assert(phead);
	ListNode* newnode = BuyList(x);
	ListNode* tail = phead->prev;
 
	tail->next = newnode;
	phead->prev = newnode;
	newnode->next = phead;
	newnode->prev = tail;
}

双向链表尾部删除函数

//双向链表尾删
void ListPopBack(ListNode* phead)
{
	assert(phead->next != phead);
	ListNode* tail = phead->prev;
	ListNode* prev = tail->prev;
	phead->prev = prev;
	prev->next = phead;
	free(tail);
	tail = NULL;
}

双向链表头部插入函数

//双向链表头插
void ListPushFront(ListNode* phead, ListDateType x)
{
	assert(phead);
	ListNode* newnode = BuyList(x);
	ListNode* head = phead->next;
 
	phead->next = newnode;
	head->prev = newnode;
	newnode->next = head;
	newnode->prev = phead;
}

双向链表头部删除函数

//双向链表头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
 
	ListNode* head = phead->next;
	ListNode* next = head->next;
 
	phead->next = next;
	next->prev = phead;
	free(head);
	head = NULL;
}

双向链表查找函数

//双向链表查找
ListNode* ListFind(ListNode* phead, ListDateType x)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* pos = phead->next;
	while (pos != phead)
	{
		if (pos->val == x)
		{
			return pos;
		}
		pos = pos->next;
	}
	return NULL;
}

单链表在 pos 位置之后插入x 函数

//在pos之前插入
void ListInsert(ListNode* pos, ListDateType x)
{
	assert(pos);
	ListNode* newnode = BuyList(x);
	ListNode* prev = pos->prev;
 
	prev->next = newnode;
	pos->prev = newnode;
	newnode->prev = prev;
	newnode->next = pos;
}

单链表删除 pos 位置之后的值 函数

//删除pos位置
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
	free(pos);
	pos = NULL;
}

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

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

相关文章

使用rt thread studio新建一个rt thread工程的详细操作说明(以stm32F411CEU6)为例

新建工程 这里以rt thread 5.0以及stm32F411CEU6芯片为例&#xff0c;创建工程 先建一个工作空间 .metadata是建工作空间自己生成的。 这时候&#xff0c;工作空间是空的&#xff0c;没有项目: 新建项目 首先下载RT-thread源文件和芯片支持包&#xff1a; 2023.05.02&#x…

NOIP2000 提高组 方格取数 线性DP 坐标优化 全局最优

&#x1f351; 算法题解专栏 &#x1f351; [NOIP2000 提高组] 方格取数 题目描述 设有 N N N \times N NN 的方格图 ( N ≤ 9 ) (N \le 9) (N≤9)&#xff0c;我们将其中的某些方格中填入正整数&#xff0c;而其他的方格中则放入数字 0 0 0。如下图所示&#xff08;见样…

延时队列的三种实现方案

延时队列的三种实现方案 什么是延时队列延时队列的应用场景基于Java DelayQueue的实现源码剖析 基于Redis的zset实现实现步骤Redis延时队列优势Redis延时队列劣势 基于RabbitMQ的延时队列实现TTL DXL(死信队列)插件实现 总结参考文章 什么是延时队列 在分布式系统中&#xff…

Go(二):包管理、通道、协程并发、互斥锁基础

包管理、协程并发基础 生成包管理文件go-mod第一步&#xff08;初始化创建包管理文件&#xff09;第二步&#xff08;导入包&#xff09; 常用命令导入远程包&#xff08;示例&#xff1a;gin&#xff09;第一步&#xff08;导入包&#xff09;第二步&#xff08;安装包&#x…

操作系统之进程同异步、互斥

引入 异步性是指&#xff0c;各并发执行的进程以各自独立的、不可预知的速度向前推进。 但是在一定的条件之下&#xff0c;需要进程按照一定的顺序去执行相关进程&#xff1a; 举例说明1&#xff1a; 举例说明2: 读进程和写进程并发地运行&#xff0c;由于并发必然导致异步性…

【Python】如何在Python中绘制带有连接线的双饼图?

文章目录 一、导入所需的库二、准备数据三、绘制双饼图3.1 创建画布和子图对象3.2 绘制大饼图3.3 绘制小饼图3.4 连接线1&#xff0c;连接大饼图的上边缘和小饼图的饼块3.5 连接线2&#xff0c;连接大饼图的下边缘和小饼图的饼块3.6 添加连接线3.7 调整子图布局 四、源代码 在 …

Linux 内核组织(kernel.org)将关闭 FTP 服务

Linux 内核组织&#xff08;kernel.org&#xff09;是一家建立于 2002 年的加利福尼亚公共福利公司&#xff0c;其目的是公开地免费分发 Linux 内核和其它开源软件。它接受 Linux 基金会的管理&#xff0c;包括技术、资金和人员支持&#xff0c;用以维护kernel.org 的运营。 Li…

2 ROS2话题通讯基础(1)

2 ROS2话题通讯基础 2.1 ROS2话题通讯介绍2.2 ROS2常用的消息类型介绍2.2.1 std_msgs消息类型2.2.2 geometry_msgs消息类型 2.3 使用C/C创建基础消息类型的话题通讯2.3.1 创建C/C发布话题信息的功能包并配置VSCode环境2.3.2 编写ROS2发布话题节点CPP文件2.3.3 配置C/C发布话题功…

【Elasticsearch】SQL操作相关

文章目录 SQL操作数据准备查询索引下的数据SQL转化为DSL(本质)SQL与DSL混合使用查看所有索引查询指定索引查看索引(表)结构where条件过滤group by分组having 对分组后的数据进行过滤order by 排序limit 限制查询数量cursor 游标->为缓存设计聚合操作支持的函数和运算比较运算…

虚拟机和Docker有什么区别?

虚拟机 对于虚拟机&#xff0c;抽象层或抽象软件成为管理程序。管理程序就是帮助虚拟机模拟物理计算机的东西。在管理程序下面&#xff0c;我们有些硬件。管理程序管理单个物理主机上不同虚拟机之间的资源分配。管理程序管理单个物理主机上不同虚拟机之间的资源分配。也就是管…

微信小程序学习实录3(环境部署、百度地图微信小程序、单击更换图标、弹窗信息、导航、支持腾讯百度高德地图调起)

百度地图微信小程序 一、环境部署1.need to be declared in the requiredPrivateInfos2.api.map.baidu.com 不在以下 request 合法域名3.width and heigth of marker id 9 are required 二、核心代码&#xff08;一&#xff09;逻辑层index.js&#xff08;二&#xff09;渲染层…

vue diff算法与虚拟dom知识整理(2) snabbdom简介并搭建开发环境

snabbdom算是diff算法 和 虚拟dom 的一个鼻租了 vue源码借鉴了snabbdom 这个单词翻译出来叫速度 命名还是用了点心的 后面是 dom 这个 我们大概去猜作者的意思 大概想表示的就是 一个比较快的dom操作 snabbdom的get地址如下 https://github.com/snabbdom/snabbdom 这里的简…

「OceanBase 4.1 体验」|快速安装部署[OBD方式]

文章目录 一、Oceanbase数据库简介1.1 核心特性1.2 系统架构1.2.1 存储层1.2.2 复制层1.2.3 均衡层1.2.4 事务层1.2.4.1 原子性1.2.4.2 隔离性 1.2.5 SQL 层1.2.5.1 SQL 层组件1.2.5.2 多种计划 1.2.6 接入层 二、OceanBase 数据库社区版部署2.1 部署方式2.2 基础环境配置2.3 通…

【华为OD机试真题】信号发射和接收(javaC++python)100%通过率 超详细代码注释 代码深度解读

信号发射和接收 知识点数组栈 单调栈时间限制: 1s 空间限制: 256MB 限定语言:不限 题目描述: 有一个二维的天线矩阵&#xff0c;每根天线可以向其他天线发射信号也能接收其他天线的信号&#xff0c;为了简化起见&#xff0c;我们约定每根天线只能向东和向南发射信号&#xf…

【ROS仿真实战】获取机器人在gazebo位置真值的三种方法(三)

文章目录 前言一. 使用ROS tf库二、 使用Gazebo Model Plugin三、 使用libgazebo_ros_p3d插件四、总结 前言 在ROS和Gazebo中&#xff0c;获取机器人的位置信息通常通过ROS消息传递进行。在这篇文章中&#xff0c;我们将介绍三种获取机器人在Gazebo中位置真值的方法&#xff1…

CTF ASCII码 密码解密题 简单

1. 题目 这次的CTF题目就是一张图片如下&#xff0c;并且说有几个蛋被打乱过。明显是一个密码学的解码题。 2. 解题思路 左边表格给出10种颜色&#xff0c;特别是第二列给出了数字0&#xff0c;种种迹象都指向了10进制。每一个蛋都有三种颜色&#xff0c;代表每个蛋都是三位…

【GORM框架】一文学会用gorm实现对单表的增删改查操作

博主简介&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;数据结构、Go&#xff0c;Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: GORM框架学习 近期目标&#xff1a;写好专栏的每一篇文章 文章目录 一、…

M1 Mac配置JAVA环境

1、下载JDK 目前JDK有Oracle的JDK还有zulu的Open JDK可供选择&#xff0c;因为需要JAVA1.8所以下文以zulu的JDK为例。 Zulu官网&#xff1a;https://www.azul.com/downloads/?packagejdk 选择所需的JDK版本&#xff08;注意选择ARM架构&#xff09;> 下载.dmg包 > 安装 …

DAY 47 Ngnix优化与防盗链

Ngnix优化主要有两种&#xff0c;一种是配置上的优化&#xff0c;一种是内核上的优化 隐藏响应头中的版本号 方法一&#xff1a;curl命令 网页查看 隐藏版本信息 修改nginx的运行用户和组 方法一&#xff1a;在编译安装时&#xff0c;指定运行用户和组 [root nginx-1.12.2]#…

【英语】100个句子记完7000个雅思单词

其实主要的7000词其实是在主题归纳里面&#xff0c;不过过一遍100个句子也挺好的&#xff0c;反正也不多。 文章目录 Sentence 01Sentence 02Sentence 03Sentence 04Sentence 05Sentence 06Sentence 07Sentence 08Sentence 09Sentence 10Sentence 11Sentence 12Sentence 13Sent…