一篇文章让你搞懂函数指针数组,指向函数指针数组的指针,回调函数

news/2024/5/19 13:49:24/文章来源:https://blog.csdn.net/fjj2397194209/article/details/131599326

1函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如
整型指针放在一个数组中

#include<stdio.h>
int main()
{//函数指针数组---数组中每个元素是函数指针类型int* arr1[5];//整型指针数组char* arr2[5];//字符指针数组return 0;
}

现在我们要设计一个计算器实现整数的除法,乘法,加法,减法,我们看下面代码

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("ret = %d\n", ret);break;case 2:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = Div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,重新选择\n");break;}} while (input);
//
return 0;
}

如果我把

	printf("请输入两个操作数:");scanf("%d %d", &x, &y);

放到下面这段代码后面,逻虽然i解决了case语句冗余的问题,如果case语句有更多的话,冗余现象更明显,解决起来更麻烦,但是代码逻辑上是有问题的行不通的

	menu();printf("请选择:>");scanf("%d", &input);printf("请输入两个操作数:");scanf("%d %d", &x, &y);

为了解决casey=语句冗余的问题,现在我们就要用到函数指针数组
看下面代码

#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;
}
//...
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    4do{menu();printf("请选择:>");scanf("%d", &input);if (input >= 1 && input <= 4){printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("ret = %d\n", ret);}else if(input == 0){printf("退出计算器\n");}else{printf("选择错误,重新选择\n");}} while (input);return 0;
}

对于下面这一行d代码给大家做一下解释

int (* pfArr[5])(int, int) = {NULL, Add, Sub, Mul, Div};

这里如果没有NULL的话,Add的起始位置就是1,后面一次加1,现在我要将Add放在1的位置上以便和下面的case语句对应上,Sub后面的同理,这个是个我们就多增加一个元素,我在Add前面放一个元素NULL,这时Add就自然而然的在1这个位置了,后面的同理
但是这个函数指针数组只适用于整型的运算
这里呢还是需要注意一下代码的书写习惯,要符合编译器的语法要求

int main()
{int* p;//正确int(*pf)(int, int);//正确int (*)(int, int)pf//写法错误return 0;
}

前两种正确,第三种写法不符合语法规定,应按照第二种来书写

7.指向函数指针数组的指针

再次复习一下前面讲过的数组和函数知识
数组名是数组首元素的地址
&数组名是整个数组的地址
函数
函数名是函数的地址
&函数名也是函数的地址

#include<stdio.h>
int main()
{int(*pf)(int, int);//函数指针int (*pfArr[4])(int, int);//函数指针数组int (*(*p[4]))(int, int);//p就是指向函数指针数组的指针return 0;
}

大家看到这里也许有点懵逼,没关系我给大家做一下解释
看下面代码

#include<stdio.h>
void test(const char* str)
{printf("%s\n", str);
}
int main()
{void (*pf)(const char*) = test;//pf是函数指针变量//这个大家没有问题void (*pfArr[10])(const char*);//pfArr是存放函数指针的数组//这个大家没有问题void (* (*p) [10])(const char*) = &pfArr;//p指向函数指针数组的指针//(*p)的()优先级更高,说明p是一个指针,这个指针指向的一个有10个元素的数组,这个数组是用于存放函数指针/所以p是指向函数指针数组的指针return 0;
}

8. 回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当
这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调
用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
简言之回调函数就是通过函数指针去调用那个函数就被称为回调函数
看下面代码

#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;
}
void menu()
{printf("***************************\n");printf("*****  1.add  2.sub  ******\n");printf("*****  3.mul  4.div  ******\n");printf("*****  0.exit        ******\n");printf("***************************\n");
}
void Calc(int (*pf)(int, int))
{int x = 0;int y = 0;int ret = 0;printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}
int main()
{int input = 0;int x = 0;int y = 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;
}

这里的pf相当于指向了我们要调用的函数,然后后面的代码根据时机进行调用
在这里插入图片描述
这就是回调函数的基本用法
C语言标准库里面有一个函数qsort,是用来排序的
此处我们将冒泡排序复习一下
这里注意一下我们要用到俩个for循环,外循环循环n-1趟,最后一趟已经是升序的格式了,所以就是n-1趟
对于内循环这则是在外循环每循环一趟的基础上再-1,下面请看代码演示

#include<stdio.h>
#include<Windows.h>
//冒泡排序
//有一组整数,需要排序为升序
//1. 两两相邻的元素比较
//2. 如果不满足顺序就交换
//
//当前的代码只适合于整型数据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;}}}}
int main()
{int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");system("pause");return 0;
}

冒泡排序图片演示
在这里插入图片描述
好了,搞懂了冒泡排序之后我们=正式进入qsort函数的学习,大家可以先点击这个链接自己尝试着自主理解一下
qsort函数

void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));

在这里插入图片描述
对于qsort函数里面各个参数的意思我给大家做以下解释
我将解释附在代码后面了

void qsort(void* base, //指向了需要排序的数组的第一个元素size_t num, //排序的元素个数size_t size,//一个元素的大小,单位是字节int (*cmp)(const void*, const void*));//函数指针类型 - 这个函数指针指向的函数,能够比较base指向数组中的两个元素

现在我们就代码来给大家分析一下

//void qsort(void* base, //指向了需要排序的数组的第一个元素
//    size_t num, //排序的元素个数
//    size_t size,//一个元素的大小,单位是字节
//    int (*cmp)(const void*, const void*));//函数指针类型 - 这个函数指针指向的函数,能够比较base指向数组中的两个元素
//测试qsort排序结构体数据
//测试qsort排序整型数据
#include <stdlib.h>
#include<stdio.h>
#include<string.h>
int cmp_int(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
void print(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}
void test1()
{int arr[10] = { 3,1,5,2,4,7,9,6,8,0 };int sz = sizeof(arr) / sizeof(arr[0]);//默认是升序的qsort(arr, sz, sizeof(arr[0]), cmp_int);print(arr, sz);
}
//测试qsort排序结构体数据
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;
}
void test2()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int cmp_stu_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test3()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{test1();test2();test3();return 0;
}
//void* 的指针 - 无具体类型的指针
//void* 类型的指针可以接收任意类型的地址
//这种类型的指针是不能直接解引用操作的
//也不能直接进行指针运算的

这里qsort函数本质上是运用到了函数指针的知识
qsort的第四个函数指针的参数类型为什么为void呢?
这里就不得不给大家讲解一下void
的含义
void* 的指针 - 无具体类型的指针
void* 类型的指针可以接收任意类型的地址
这种类型的指针是不能直接解引用操作的
也不能直接进行指针运算的
对于返回值(return value)
在这里插入图片描述
返回大于/等于/或者小于0的数
int(*cmp)(const void, const void));//函数指针类型 - 这个函数指针指向的函数,能够比较base指向数组中的两个元素,这个是比较好理解的
那么对于qsort排序结构体数据我们应该怎么样测试呢?
我们来看下面代码

#include <stdlib.h>
#include<stdio.h>
#include<string.h>
int cmp_int(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
void print(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}
void test1()
{int arr[10] = { 3,1,5,2,4,7,9,6,8,0 };int sz = sizeof(arr) / sizeof(arr[0]);//默认是升序的qsort(arr, sz, sizeof(arr[0]), cmp_int);print(arr, sz);
}
//测试qsort排序结构体数据
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;
}
void test2()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int cmp_stu_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test3()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{test1();test2();test3();return 0;
}
//void* 的指针 - 无具体类型的指针
//void* 类型的指针可以接收任意类型的地址
//这种类型的指针是不能直接解引用操作的
//也不能直接进行指针运算的

这里我们需要将void*类型的指针强制类型转化为结构体指针
对于名字是一个字符串,我们需要运用strcmp函数进行字符串的比较
到了这里相信大家大体上明白了qsort函数的用法
现在我们来模拟实现以下qsort
因为我们没有学习过快速排序算法
所以我们使用冒泡排序的思想,实现一个功能类似qsort的函数
bubble_sort()
1.使用冒泡排序的思想
2.适用于任意类型数据的排序
我们还是先直接把代码给大家呈现出来,然后一步步的去分析

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素
{int i = 0;char tmp = 0;for (i = 0; i < size; i++){tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{int  i = 0;//趟数for (i = 0; i < num - 1; i++){int j = 0;//一趟内部比较的对数for (j = 0; j < num - 1 - i; j++){//假设需要升序cmp返回>0,交换if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)//两个元素比较,需要将arr[j],arr[j+1]的地址要传给cmp{//交换Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}}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;
}//测试bubble_sort 排序结构体数据
//void test2()
//{
//	struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };
//	int sz = sizeof(arr) / sizeof(arr[0]); 
//	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
//}int cmp_stu_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}void test3()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 50},{"wangwu", 15} };int sz = sizeof(arr) / sizeof(arr[0]);printf("%d\n", sizeof(struct Stu));bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}//B
int cmp_int(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}//测试bubble_sort 排序整型数据
void test1()
{int arr[10] = { 3,1,5,2,4,7,9,6,8,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
}
int main()
{//test1();//test2();test3();return 0;
}

我们再次来看一下qsort函数的各个参数

void qsort(void* base, //指向了需要排序的数组的第一个元素size_t num, //排序的元素个数size_t size,//一个元素的大小,单位是字节int (*cmp)(const void*, const void*));//函数指针类型 - 这个函数指针指向的函数,能够比较base指向数组中的两个元素

void qsort(void* base, //指向了需要排序的数组的第一个元素
size_t num, //排序的元素个数
size_t size,//一个元素的大小,单位是字节
int (cmp)(const void, const void*));//函数指针类型 - 这个函数指针指向的函数,能够比较base指向数组中的两个元素
base:排序的数据的起始位置
num:从base开始向后有num个元素
size:一个元素的大小
类似于冒泡排序,外循环和内循环的趟数是不会改变的
唯一可以改变的是内循环里面的判断部分,看下图
在这里插入图片描述
对于结构体的比较不能简单的用大于小于符号进行操作了
对于不同类型的数据不能简单的使用大于小于符号进行比较
不同的数据类型交换方式有所差距
这里我们参照qsort函数的参数类型来写,类似于下面代码

void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))

这里面的Swap函数的原理是什么呢?
我给大家画个图演示一下
在这里插入图片描述
通过解引用拿到了第一个数据和第二个数据
因为一个整型占4个字节
所以char每一次访问一个字节每利用完一次中间变量进行两个整型的每一个字节的 交换,然后buf1++,buf2++依次交换完两个元素的字节,也就是交本质上进行了两个元素的交换
对于结构体我们应该怎么处理呢?
因为我们Swap函数传过去的参数已经告示我们了参数类型应该为char而不是void
这里的size大小就为24,说明我们的Swap函数内部要进行24次交换
两个字符串比较的时候,也就是strcmp函数在进行字符串比较的时候是每一个字符进行比较比如zhangsan和lisi,z大于l,所以进行交换,依此类推进行交换
最后给大家讲一下下面这个代码的意思

cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0

为什么要强制类型转化为char而不是其他指针类型呢?
size表示一个元素的大小,base是void
不能进行加减,需要进行强制类型转化转化为char*
因为我们的指针类型就决定了我们的指针类型指向多大的一块空间,所以要对一个内存进行连续的拷贝所以强制类型转为为char*,一个字节一个字节的跨过去
在这里插入图片描述

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

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

相关文章

前端Vue自定义精美底部操作栏导航栏工具栏 可用于电商购物车底部导航

随着技术的发展&#xff0c;开发的复杂度也越来越高&#xff0c;传统开发方式将一个系统做成了整块应用&#xff0c;经常出现的情况就是一个小小的改动或者一个小功能的增加可能会引起整体逻辑的修改&#xff0c;造成牵一发而动全身。通过组件化开发&#xff0c;可以有效实现单…

18 SAR图像和光学图像的配准算法(matlab程序)

1.简述 合成孔径雷达(synthetic aperture radar,SAR)图像配准的主要目标是对同一或不同传感器在不同时间、不同视点捕获的SAR图像进行配准。SAR因具有全天候成像能力和地物穿透能力&#xff0c;因此具有非常广泛的应用&#xff0c;如变化检测[1]、图像融合[2]、目标检测与识别[…

9.10UEC++生成、销毁actor

BeginPlay&#xff1a; 1.SpawnActor&#xff1a;<模板类>&#xff08;模板::staticclass&#xff08;&#xff09;&#xff0c;FVector const class&#xff0c;FRotation const class&#xff09; 生成一个actor 2.Destory&#xff08;&#xff09;从世界中销毁一个a…

three.jsgsap滚动交互网页实现 1-模糊背景线条

实现 模型只有其中的一根线条 加载完模型后 将其圆形排列一周 要实现圆形排列一周可以自己计算sin和cos实现&#xff0c;更简单的可以使用vector3的setFromCylindricalCoords方法实现 该方法计算返回圆柱上的点&#xff0c;圆柱上的点由参数决定 使用方法&#xff1a; for (let…

Pinia基础使用 (vite vue3)

Pinia 概念 是 Vue 的存储库&#xff0c;它允许您跨组件/页面共享状态。 Vue官方推荐状态库 Pinia 的优点 pinia 符合直觉&#xff0c;易于学习。 pinia 是轻量级状态管理工具&#xff0c;大小只有1KB. pinia 模块化设计&#xff0c;方便拆分。 pinia 没有 mutations&#x…

仅个人记录 CMX复现

文章解析(214条消息) CMX: Cross-Modal Fusion for RGB-X SemanticSegmentation with Transformers_cmx: cross-modal fusion for rgb-x semantic segment_翰墨大人的博客-CSDN博客 代码 GitHub - huaaaliu/RGBX_Semantic_Segmentation 一、 环境配置 conda create -n rgbx…

爬虫入门07——requests中携带cookie信息

爬虫入门07——requests中携带cookie信息 对于需要登陆的网站如果不携带cookie是无法获取我们所需内容的就以查看我在CSDN中的订单为例&#xff0c;在登陆后可以查看到订单信息 而当我们使用Python代码发出请求时&#xff0c;是不携带cookie&#xff0c;因此无法拿到订单相关信…

使用 VMware 安装 docker

1、首先创建一个新的虚拟机 &#xff08;1&#xff09;配置自定义就可以 &#xff08;2&#xff09;选择稍后安装 &#xff08;3&#xff09;选择Linux &#xff08;4&#xff09;安装位置自己选择 &#xff08;5&#xff09;处理器数量根据自己电脑来&#xff08;一般2个就够…

【Java EE】-JVM

作者&#xff1a;学Java的冬瓜 博客主页&#xff1a;☀冬瓜的主页&#x1f319; 专栏&#xff1a;【JavaEE】 分享: 雨下整夜 我的爱溢出就像雨水 ——《七里香》 主要内容&#xff1a;JDK&#xff0c;JRE&#xff0c;JVM三者之间的联系。JVM内存区域划分&#xff1a;本地方法栈…

【数据分析 - 基础入门之NumPy③】日常难题解决

知识目录 前言一、启动Jupyter Notebook报错没有这样的目录结语# 往期文章&相关导读 前言 本篇文章用于整理在学习 NumPy 过程中遇到的错误&#xff0c;以此做个记录&#xff0c;希望能帮助到大家&#xff0c;让大家少走弯路。 一、启动Jupyter Notebook报错没有这样的目…

Delphi XE编写OCX控件

1、new->other 2、Active libary 3、再次New->Other,才出现ActiveX组件内容 设置类名及参数

开源预训练框架 MMPRETRAIN官方文档(概览、环境安装与验证、基础用户指南)

MMPretrain是全新升级的开源预训练框架。它已着手提供多个强大的预训练骨干网并支持不同的预训练策略。MMPretrain 源自著名的开源项目 MMClassification 和MMSelfSup&#xff0c;并开发了许多令人兴奋的新功能。目前&#xff0c;预训练阶段对于视觉识别至关重要。凭借丰富而强…

Docker 常用指令集合,更换镜像(Ubantu)

1.更换镜像 先进入root用户 cat /etc/docker/daemon.json 查看有没有镜像创建目录,创建并编辑damon,json文件 mkdir -p /etc/docker vim /etc/docker/daemon.json# 填写内容 {"registry-mirrors": ["https://h5rurp1p.mirror.aliyuncs.com"] } 重新启…

如何从零开始学习自动化测试?终于找到靠谱的教程了

目录 前言 测试基础 Python基础 selenium appium requests unittest 项目实战&#xff1a; 总结&#xff1a; 前言 最近有几个小伙伴在后台给安静私信说&#xff0c;如何学习自动化&#xff0c;不知道如何入手&#xff1f;在网上看的资料都是乱七八糟的&#xff0c;每…

VSCode编译github上面的C++项目

1、下载cmake 在这里下载对应的版本 https://cmake.org/download/ 测试下载的是这个 下载完成后安装&#xff0c;安装都比较简单 2、安装CMake工具扩展 3、安装C扩展 4、下载github项目 例如&#xff1a;下载这个项目 https://gitcode.net/mirrors/zrax/pycdc?utm_source…

使用凌鲨进行数据标注

在AI研发团队中&#xff0c;数据的数量和质量通常比算法本身更重要。为了获得大量高质量的数据&#xff0c;标注软件是必不可少的。目前许多开源标注软件在权限、任务管理和审核方面都存在较大问题。 在凌鲨(linksaas)0.3.8版本中增加了数据标注功能&#xff0c;支持 音频分类…

123.【SpringBoot 源码刨析B】

SpringBoot-核心功能 (三)、SpringBoot核心功能1.配置文件1.1、properties1.2、yaml(1).yaml 简介(2).yaml 基本语法(3).数据类型(4).示列 1.3、配置提示 2.WEB 开发1.SpringMVC自动配置概览2.简单功能分析(1).静态资源访问&#xff08;1.1&#xff09;.静态资源目录&#xff0…

用真人模型制作3D虚拟人物,岂不是更真实?

3D虚拟人物是指利用计算机技术和图形学技术创建的一种能够模拟真实人体形态、行为和语言的虚拟实体。与传统的平面图像或视频不同&#xff0c;3D虚拟人物具有立体感和真实感&#xff0c;能够在虚拟环境中实现人机交互和情感交流&#xff0c;给用户带来全新的沉浸式体验。 随着…

国内环境安装Atlas OS步骤与网络问题解决

国内环境安装Atlas OS步骤与网络问题解决 Atlas 是一个开源、透明的项目&#xff0c;它修改了 Windows&#xff0c;并消除了影响游戏性能的所有负面缺点。我们是减少系统延迟、网络延迟、输入延迟和保持系统私密性的绝佳选择&#xff0c;同时仍然关注性能。我不久前在安装时遇…

【在线文件管理】响应式文件管理AngularJS

目录 1.功能展示截图 2.实现代码 2.1HTML页面代码 2.2后台代码 2.2.1项目结构 2.2.2项目代码 其他问题 1.功能展示截图 项目中需要用到文件管理来自由控制文件的上传、下载、删除等&#xff0c;就想到做一个简单的在线文件管理功能。 支持在线编辑&#xff1a; 2.实现代…