【C进阶】回调函数(指针进阶2,详解,小白必看)

目录

6. 函数指针数组

6.1简单计算器

6.2函数指针数组实现计算器

7. 指向函数指针数组的指针(仅作了解即可)

8.回调函数

8.1关于回调函数的理解​编辑

8.1.1用回调函数改良简单计算器

8.2qsort库函数的使用

8.2.1冒泡排序

8.2.2qsort的概念

8.3冒泡排序思想实现qsort


         这篇文章包括但不限于函数指针数组指向函数指针数组的指针,回调函数等知识点的总结。承接着上文指针进阶(1)知识点总结,传送门-- > http://t.csdn.cn/mgVGJ

指针进阶(3):指针和数组笔试题解析总结,传送门--> http://t.csdn.cn/aKVsj 

        如有错误,欢迎大家指点与纠正,感谢你的来访!

6. 函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
比如:
int * arr [ 10 ];
//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int ( * parr1 [ 10 ])();//1
int * parr2 [ 10 ]();//2
int ( * )() parr3 [ 10 ];//3

 答案是:parr1 //1

parr1 先和 [] 结合,说明 parr1 是数组,数组的内容是什么呢?
int (*)() 类型的函数指针。-->对于一个变量来说去掉它的名字,就是它的类型。
int my_strlen(const char* str)
{
	return 0;
}
int main()
{
  //指针数组
	char* arr[10];
  //数组指针
	int arr2[5] = { 0 };
	int(*p)[5] = &arr2;//p是一个指向数组的指针变量

	//函数指针
    int (*pf)(const char*) = my_strlen;//pf是一个指向函数的函数指针变量
	
	//函数指针数组-存放函数指针的数组
	int (*pfArr[10])(const char*);
    //1.pf先与[10]结合,代表这是一个数组,数组有10个元素;
    //2.去掉pf[10],剩余int (*)(int,int)为数组每个元素的类型--函数指针类型

	return 0;
}

以下三种写法均等价
1.   *(*pf)("abcdef");
2.   pf ("abcdef");
3.   my_strlen("abcdef");

我们通过实现一个计算器的函数来说明,函数指针数组的用途用在哪里:

6.1简单计算器

按照咱们正常的思路,写一个简单的计算器实现整数的加减乘除的功能,应该大致写成这种代码:

那么执行一下,看一下状况如何:

以上出现的问题是,输入0之后,没有立即打印"退出计算器"然后结束程序,而是要继续输入两个操作数之后才打印信息,还顺便打印了个6,这样的代码肯定存在问题,咱们稍微改进一下。

//写一个计算器能完成整数的+ - * /
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("*************************\n");
	printf("***  1.Add     2.Sub   **\n");
	printf("***  3.Mul     4.Div   **\n");
	printf("***  0.Exit            **\n");
	printf("*************************\n");
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>");
			scanf("%d %d",&x,&y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default :
			printf("选择错误\n");
			break;
		}
	}while(input);
	return 0;
}

执行代码,继续测试:

 问题是解决了,可是又发现新的问题:

 以上的代码的switch case语句太多了,当我想要在这个计数器里面增加: << >> & | ^ && ||等功能,case语句的就会越来越多,那应该怎么去纠正呢?

这时候就要用到函数指针数组了。

例子:

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
int main()
{
	//存放函数指针的数组 - 函数指针数组
	int (*pf[4])(int, int) = {Add,Sub,Mul,Div };
	                        // 0    1   2   3
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = pf[i](8, 4);
		printf("%d\n", ret);
	}

	return 0;
}

这段代码使用了函数指针数组,将四个运算函数的地址存储在数组中,然后通过循环遍历数组,依次调用四个运算函数进行计算并输出结果。这种方式可以减少代码的重复量,提高代码的可维护性。

 6.2函数指针数组实现计算器

//函数指针数组实现计算器
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("*************************\n");
	printf("***  1.Add     2.Sub   **\n");
	printf("***  3.Mul     4.Div   **\n");
	printf("***  0.Exit            **\n");
	printf("*************************\n");
}
int main() 
{
	int input = 0;//选择菜单的功能
	int x = 0;
	int y = 0;
	int ret = 0;//用于接收值
   
    //转移表 - 函数指针的数组
	int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
							 //   0    1   2   3    4
	//NULL的作用是为了占用0下标,然后让函数指针数组的元素下标对应上菜单的功能
	do {
		//菜单
		menu();
		//进行选择
		printf("请选择:>");
		scanf("%d", &input);
		//用函数指针数组代替switch
		if (input == 0)
		{
			printf("退出计算器\n");
			return 0;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d",&x,&y);
			ret = (***pfArr[input])(x, y);//其实比函数指针的用法就多了一个[input]
			 //跟函数指针一样,调用函数的时候 * 是没有任何意义的
			printf("%d\n",ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}

函数指针数组是一个数组,其中的元素是函数指针。每个函数指针指向一个特定的函数,可以通过函数指针调用相应的函数。 函数指针数组可以用来解决代码冗余的问题,可以方便地遍历、调用不同的函数,从而简化代码的结构和逻辑,提高代码的可维护性和可扩展性。

函数指针数组的用途:转移表

7. 指向函数指针数组的指针(仅作了解即可)

//指向函数指针数组的指针
int Add(int a, int b)
{
	return a + b;
}
int Sub(int a, int b)
{
	return a - b;
}
int main()
{   //函数指针
	int(*pf)(int, int) = Add;
	//函数指针数组
	int (*pfArr[4])(int, int) = { Add,Sub };
	//指向函数指针数组的指针
	 int (*(*ppfArr)[4])(int,int) = &pfArr;

	 return 0;
}

如何理解指向函数指针数组的指针:

8.回调函数

概念

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
代码冗余问题:第一版简单的计算器存在的问题

存在着较多逻辑相同,代码冗余的部分。

 这时候就需要用到回调函数解决问题了:

8.1关于回调函数的理解

8.1.1用回调函数改良简单计算器

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
//将冗余重复部分用Calc函数封装(只用了一个函数),通过这个函数里面传入的函数指针调用计算器函数
void Calc(int(*pf)(int, int))//pf函数指针,传过来哪个函数地址就用哪种类型计算
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:>");
	scanf("%d %d", &x, &y);
    ret = pf(x, y);
	printf("%d\n", ret);
}
void menu()
{
	printf("*************************\n");
	printf("***  1.Add     2.Sub   **\n");
	printf("***  3.Mul     4.Div   **\n");
	printf("***  0.Exit            **\n");
	printf("*************************\n");
}
int main()
{
	int input = 0;
	int ret = 0;
	do{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			Calc(Add);
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
			}
		} while (input);
	return 0;
}

总结:

回调函数:被作为参数传递的函数,Add、Sub、Mul、Div四个函数就是回调函数,

而Calc函数则是工具人

8.2qsort库函数的使用

8.2.1冒泡排序

回顾一下冒泡排序的过程:

冒泡排序的思想:两两相邻的元素进行比较,假设要排成升序

怎么去写冒泡排序的代码:
①由元素个数确定趟数

②由趟数确定比较对数         

③两个元素两两交换排成升序

//冒泡排序 
void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)//趟数
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)//比较对数
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0};
	//排序
	//使用
	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_sort(arr, sz);

	print_arr(arr, sz);
	return 0;
}

不足:

但是这个冒泡排序的缺点也很明显,就是只能排整型int

如果遇到浮点型、结构体等类型的数据,就排不了,那么怎么可以解决呢?

这时候就要用到qsort了。

8.2.2qsort的概念

qsort-- quicksort

是一个库函数,是用来排序的库函数使用快速排序的方法

qsort的好处是

1.现成的

2.可以排序任意类型的数据

 

qsort是可以排序任意类型的数据

1.比较2个整数的大小,> < ==

//qsort函数的使用者提供这个函数
//qsort 默认是升序
int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
    //排成倒序的话,什么都不用动,只需要return *(int*)p2 - *(int*)p1;//调换顺序即可
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
test1()
{
	int arr[] = { 3,1,5,2,4,9,8,6,5,7 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//使用qsort来排序整型数组,这里就要提供一个比较函数,这个比较函数能够比较2个整数的大小
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}
int main()
{
   test1();
}

 

2.比较2个字符串,strcmp -->回顾字符串知识:http://t.csdn.cn/V7E9a

3.比较2个结构体数据(学生:张三、李四)指定比较的标准

回顾结构体对象访问成员的知识:http://t.csdn.cn/DVEVj

//测试qsort 排序结构体数据
struct Stu {
	char name[20];
	int age;
};
void print_arr1(struct Stu* arr, int sz)//打印年龄
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		//printf("%d ", (*(arr + i)).age);
		//printf("%d ", (arr+i)->age);
		printf("%d ", arr[i].age);

	}
	printf("\n");
}
void print_arr2(struct Stu*arr, int sz)//打印姓名
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s ", (arr+i)->name);
		//printf("%s ", (*(arr+i)).name);
		//printf("%s ", arr[i].name);

	}
	printf("\n");
}
//按照年龄来比较
int cmp_stu_by_age(const void*p1,const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;

}
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test2()
{
	struct Stu s[] = { {"zhangsan",30},{"lisi",25},{"wangwu",50} };
	int sz = sizeof(s) / sizeof(s[0]);
	//测试按照年龄来排序
	print_arr1(s, sz);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	print_arr1(s, sz);
	//测试按照名字来排序
	/*print_arr2(s,  sz);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	print_arr2(s, sz);*/
}

打印年龄:

打印性别:

8.3冒泡排序思想实现qsort

使用冒泡排序的思想来实现一个类似于qsort这个功能的冒泡排序函数bubble_sort()

各参数组成:
base,num,width,cmp函数,elem1和elem2

接下来说明一下qsort各参数的设计思路:

①为什么base定义为void*类型?

void*是 C 语言中的一种通用指针类型,可以指向任意类型的数据。在 qsort 函数中,由于需要对不同类型的数据进行排序,因此需要使用void*类型的指针,以便能够接受各种类型的数据,base参数是一个指向要排序数组第一个元素的指针。由于它要指向任意类型的数据,所以使用void*是最通用的。

举例:

int a = 10;

void* p = &a;

优点:

无具体类型的指针,所以它可以接收任何类型的地址

缺点:

*p  //-->  void*的指针不能解引用操作符

p++  //-->  因为不知道是什么数据类型,不能直接++

正确用法:

*(int*)p;//也可以转化成其它类型

②为什么num要设计成size_t?

  • size_t 在 C 语言中是一种无符号整数类型,用于表示大小、长度和索引值,使用它表示数组元素个数很合适。
  • 数组元素个数是一个非负的值,使用无符号类型size_t可以避免出现负数,更加合理。

  • 使用专门的 size_t 类型比简单的 unsigned int 更能表明这个参数的语义 - 它表示一个大小或长度,而不是一个整数。

③为什么width要设计成size_t

在qsort函数中,width参数表示每个数组元素的大小(以字节为单位)

  1. width表示一个"大小"的概念,使用size_t类型可以更清楚地表达这个参数的语义。

  2. width的单位是字节,size_t通常是无符号整数类型,不会有负数,所以更适合表示正的值。

  3. width需要足够大的范围来表示任意数据类型的大小。size_t类型依赖平台,但通常是机器字大小,能满足大多数数据元素的大小需求。

  4. 在访问数组元素时,索引位置需要乘以元素大小才能获得地址偏移量。既然索引是size_t类型,那么两者相乘的结果也应该是size_t类型,以避免溢出问题。

④为什么要设计一个函数指针cmp?

  1. qsort 本身是一个通用的排序函数,不能知道要排序的元素类型,也就无法直接比较两个元素。采用函数指针可以将比较操作的实现留给用户。

  2. 通过函数指针,用户可以自行实现针对自己数据类型的比较函数,将具体的比较逻辑封装起来。这提高了qsort的通用性和灵活性。

  3. 对于不同的数据类型,比较操作的逻辑可能不同。使用函数指针实现可以避免在qsort中写大量的条件分支逻辑。

  4. 函数指针提供了一种扩展机制。如果用户需要改变比较操作的逻辑,只需要传入一个新的函数指针就可以,而不需要改动qsort函数本身。

⑤为什么elem1和elem2类型是const void*类型?

  1. qsort要对任意类型的数据进行排序,所以比较函数需要能处理任意类型的元素。使用void*可以指向任意类型的数据,const用于表示不应修改指针指向的内容。

  2. qsort要对任意类型的数据进行排序,所以比较函数需要能处理任意类型的元素。使用void*可以指向任意类型的数据,const用于表示不应修改指针指向的内容。

  3. 在调用qsort时可以直接将数组元素的地址强制转换为const void* 类型传给比较函数,简化调用。

  4. qsort函数本身不需要了解元素具体类型,只要把void*指针传给比较函数,由比较函数转换并解释指针内容即可。

开始模拟实现:冒泡排序大题思路不需要改变,只需要对排序方式进行即可

int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
void bubble_sort(void* base, size_t num, size_t width,
	int (*cmp)(const void* p1, const void* p2))
{
    //冒泡排序大题思路不需要改变,只需要对排序方式进行即可
	//要确定趟数
	size_t i = 0;
	for (i = 0; i < num - 1; i++)
	{
		int flag = 0;//假设已经有序了
		//一趟冒泡排序的过程
		size_t j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//两个相邻的元素比较
			//arr[j] arr[j+1]
			if (cmp((char*)base + j * width, (char*)base+(j+1)*width)>0)
			{
				//交换
				flag = 0;
				Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
			}
		}
	}


}

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test3()
{
	int arr[] = { 3,1,5,2,4,9,8,6,5,7 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("排序前:");
	print_arr(arr, sz);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	printf("排序后:");
	print_arr(arr, sz);
}

qsort对于代码的解析:

如何交换数据:

为什么这个地方写成char*?

在bubble_sort函数中,由于不知道base指向的数据类型,因此需要将其强制转换为char*类型,从而让指针的步长为1。这样,在进行指针的加减运算时,就可以根据width参数来控制指针的步长,从而实现对任意数据类型的排序。

注意:width对于char来说是1个字节的意思,但是对于其它类型来说就不是了,width是根据参数不同来设置不同的数据类型,控制指针步长的。

交换的流程:1,2,3,4表示顺序 

 代码的整体流程:

排序:

通过新定义的冒泡排序排序年龄以及姓名

struct Stu {
	char name[20];
	int age;
};
int cmp_stu_by_age(const void*p1,const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;

}
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
void bubble_sort(void* base, size_t num, size_t width,
	int (*cmp)(const void* p1, const void* p2))
{
	//要确定趟数
	size_t i = 0;
	for (i = 0; i < num - 1; i++)
	{
		int flag = 0;//假设已经有序了
		//一趟冒泡排序的过程
		size_t j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//两个相邻的元素比较
			//arr[j] arr[j+1]
			if (cmp((char*)base + j * width, (char*)base+(j+1)*width)>0)
			{
				//交换
				flag = 0;
				Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
			}
		}
	}


}
void print_arr3(struct Stu* s, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", (s + i)->age);
		//printf("%s ", (*(arr+i)).name);
		//printf("%s ", arr[i].name);

	}
	printf("\n");

}
void print_arr4(struct Stu* s, int sz)
{
	int i = 0;
		for (i = 0; i < sz; i++)
		{
			printf("%s ", (s+i)->name);
			//printf("%s ", (*(arr+i)).name);
			//printf("%s ", arr[i].name);
	
		}
		printf("\n");

}
void test4()
{
	struct Stu s[] = { {"zhangsan",30} ,{"lisi",25},{"wangwu",50}};
	int sz = sizeof(s) / sizeof(s[0]);
	//测试按年龄来排序
	/*print_arr3(s, sz);
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	print_arr3(s, sz);*/
	//测试按照名字来排序
	print_arr4(s, sz);
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	print_arr4(s, sz);

}

按姓名排序:

 按年龄排序

这篇文章到这里就结束了,如有错误欢迎大家指正,然后下来就是这篇关于sizeof和strlen的详细总结:,指针和数组笔试题解析总结,传送门--> http://t.csdn.cn/aKVsj

欢迎大家来访。

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

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

相关文章

docker存储空间报错解决(谨慎操作,会影响原来的容易镜像,不熟练切勿操作)

报错内容 [rootDream package]# docker build -t imapp . [] Building 21.0s (6/19)> [internal] load build definition from Dockerfile 0.1s> > transferring …

TCP 三次握手四次挥手浅析

大家都知道传输层中的TCP协议是面向连接的&#xff0c;提供可靠的连接服务&#xff0c;其中最出名的就是三次握手和四次挥手。 一、三次握手 三次握手的交互过程如下 喜欢钻牛角尖的我在学习三次握手的时候就想到了几个问题&#xff1a;为什么三次握手是三次&#xff1f;不是…

AnimateDiff论文解读-基于Stable Diffusion文生图模型生成动画

文章目录 1. 摘要2. 引言3. 算法3.1 Preliminaries3.2. Personalized Animation3.3 Motion Modeling Module 4. 实验5.限制6. 结论 论文&#xff1a; 《AnimateDiff: Animate Your Personalized Text-to-Image Diffusion Models without Specific Tuning》 github: https://g…

销售易和管易云接口打通对接实战

销售易和管易云接口打通对接实战 来源系统:销售易 销售易CRM支持企业从营销、销售到服务的全流程自动化业务场景&#xff0c;创新性地利用AI、大数据、物联网等新型互联网技术打造双中台型CRM&#xff1b;既能帮助B2B企业连接外部经销商、服务商、产品以及最终用户&#xff0c;…

支持多种通信方式和协议方便接入第三方服务器或云平台

2路RS485串口是一种常用的通信接口&#xff0c;可以支持Modbus Slave协议&#xff0c;并可接入SCADA、HMI、DSC、PLC等上位机。它还支持Modbus RTU Master协议&#xff0c;可用于扩展多达48个Modbus Slave设备&#xff0c;如Modbus RTU远程数据采集模块、电表、水表、柴油发电机…

【Rust学习 | 基础系列3 | Hello, Rust】编写并运行第一个Rust程序

文章目录 前言一&#xff0c;创建项目二&#xff0c;两种编译方式1. 使用rustc编译器编译2. 使用Cargo编译 总结 前言 在开始学习任何一门新的编程语言时&#xff0c;都会从编写一个简单的 “Hello, World!” 程序开始。在这一章节中&#xff0c;将会介绍如何在Rust中编写并运…

强推!大语言模型『百宝书』,一文缕清所有大模型!

夕小瑶科技说 原创 作者 | 王思若 最近&#xff0c;大型语言模型无疑是AI社区关注的焦点&#xff0c;各大科技公司和研究机构发布的大模型如同过江之鲫&#xff0c;层出不穷又眼花缭乱。 让笔者恍惚间似乎又回到了2020年国内大模型“军备竞赛”的元年&#xff0c;不过那时候…

DSA之图(4):图的应用

文章目录 0 图的应用1 生成树1.1 无向图的生成树1.2 最小生成树1.2.1 构造最小生成树1.2.2 Prim算法构造最小生成树1.2.3 Kruskal算法构造最小生成树1.2.4 两种算法的比较 1.3 最短路径1.3.1 两点间最短路径1.3.2 某源点到其他各点最短路径1.3.3 Dijkstra1.3.4 Floyd 1.4 拓扑排…

【前端知识】React 基础巩固(三十六)——RTK中的异步操作

React 基础巩固(三十六)——RTK中的异步操作 一、RTK中使用异步操作 引入RTK中的createAsyncThunk&#xff0c;在extraReducers中监听执行状态 import { createSlice, createAsyncThunk } from "reduxjs/toolkit"; import axios from "axios";export cons…

第七篇:k8s集群使用helm3安装Prometheus Operator

安装Prometheus Operator 目前网上主要有两种安装方式&#xff0c;分别为&#xff1a;1. 使用kubectl基于manifest进行安装 2. 基于helm3进行安装。第一种方式比较繁琐&#xff0c;需要手动配置yaml文件&#xff0c;特别是需要配置pvc相关内容时&#xff0c;涉及到的yaml文件太…

程序员做项目必用的工具【更新中...】

每个程序员多多少少都会有自己简化项目的小工具&#xff0c;我采访了我们公司所有的工程师总结了程序员必备工具篇。 一.unisms 官网&#xff1a;https://unisms.apistd.com/ 不会有人这年头写注册登录还是自己写验证码模块吧&#xff1f; 你该得拥有一个短信验证码平台了&…

【GUI】基于开关李雅普诺夫函数的非线性系统稳定(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

pytest 入门

1,安装pytest 打开终端或命令提示符窗口,在终端中运行以下命令来安装pytest: pip install pytestpip install -i https://pypi.tuna.tsinghua.edu.cn/simple pytest 确保您的系统上已经安装了Python。您可以在终端中运行以下命令来检查Python的安装情况: pytest --version…

汽车分析,随时间变化的燃油效率

简述 今天我们来分析一个汽车数据。 数据集由以下列组成&#xff1a; 名称&#xff1a;每辆汽车的唯一标识符。MPG&#xff1a;燃油效率&#xff0c;以英里/加仑为单位。气缸数&#xff1a;发动机中的气缸数。排量&#xff1a;发动机排量&#xff0c;表示其大小或容量。马力&…

伦敦金在非农双向挂单

对伦敦金投资有一定经验的投资者都知道&#xff0c;在非农时期&#xff0c;伦敦金市场会出现很大的波动&#xff0c;那么我们如何才能抓住这些波动呢&#xff1f;答案是很难的。但是&#xff0c;有些投资者在多年实践中发明了一种双向挂单的方法&#xff0c;这里和大家一切分享…

使用easyui的tree组件实现给角色快捷分配权限功能

这篇文章主要介绍怎么实现角色权限的快捷分配功能&#xff0c;不需要像大多数项目的授权一样&#xff0c;使用类似穿梭框的组件来授权。 具体实现&#xff1a;通过菜单树的勾选和取消勾选来给角色分配权限&#xff0c;在这之前&#xff0c;需要得到角色的菜单树&#xff0c;角色…

vue实现flv格式视频播放

公司项目需要实现摄像头实时视频播放&#xff0c;flv格式的视频。先百度使用flv.js插件实现&#xff0c;但是两个摄像头一个能放一个不能放&#xff0c;没有找到原因。&#xff08;开始两个都能放&#xff0c;后端更改地址后不有一个不能放&#xff09;但是在另一个系统上是可以…

盛元广通实验室教学仪器设备综合信息管理系统LIMS

实验室作为学生以及教师进行科研教学环境&#xff0c;对于实验室设备的使用情况、维护、借还、台账管理、盘点、报废等需要得到有效的管理&#xff0c;以促进科研教学工作的高质量开展&#xff0c;介于传统手动管理方式越发不能满足现代科研的飞速发展需要&#xff0c;实验室的…

使用Django自带的后台管理系统进行数据库管理的实例

Django自带的后台管理系统主要用来对数据库进行操作和管理。它是Django框架的一个强大功能&#xff0c;可以让你快速创建一个管理界面&#xff0c;用于管理你的应用程序的数据模型。 使用Django后台管理系统&#xff0c;你可以轻松地进行以下操作&#xff1a; 数据库管理&…

MySQL高级篇第4章(逻辑架构)

文章目录 1、逻辑架构剖析1.1 服务器处理客户端请求1.2 Connectors1.3 第一层&#xff1a;连接层1.4 第二层&#xff1a;服务层1.5 第三层&#xff1a;引擎层1.6 存储层1.7 小结 2、SQL执行流程2.1 MySQL 中的 SQL执行流程2.2 MySQL8中SQL执行原理2.3 MySQL5.7中SQL执行原理2.4…