回调函数的应用(sqort函数)——指针进阶(三)

news2024/9/20 22:08:33

目录

前言

什么是回调函数?

回调函数的应用

qsort函数

qsort函数的使用

qsort模拟实现

 模拟函数的使用

总结


前言

回调函数是一种非常常见的编程技术,在许多不同的编程语言和框架中都有广泛的应用。但它到底是什么,以及如何使用呢?本期我们就来说说什么是回调函数,以及回调函数的基础应用


什么是回调函数?

关于回调函数是这样定义的。

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

 但看这个定义或许你会觉得很绕,那么我们看下面的代码:

上期我们提到使用函数指针数组可以很好的解决代码冗余的问题,今天我们在此以简单计算器的实现为例,我们使用函数指针来解决(回调函数)

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;
}


 以这个简单的程序为例,我们来看一下到底哪个是回调函数?

在上述代码中

Calc函数它接受一个函数指针作为参数(int (*pf)(int, int)),根据不同的选择传入不同函数的地址,并在内部通过该函数指针调用相应的函数完成计算。

AddSubMulDiv函数被Calc函数调用,当Add函数被调用时Add就是回调函数,当Sub函数被调用时Sub就是回调函数,当我们通过Calc中的pf调用Add、Sub、Mul、Div这些函数时,这些函数就被称为回调函数。

 那我们为什么要使用回调函数呢?

通过程序可以发现使用回调函数的方法我们也可以避免程序冗余的问题。这也是它其中的一个特点之一。

回调函数的优势在于可以将代码的控制权交给调用方,使得调用方能够自定义需要执行的功能。这种灵活性使得回调函数在事件处理、异步编程等场景中得到广泛应用。

回调函数的应用

说到回调函数的应用,这里我们就要提及qsort函数的应用了。

qsort函数

一般我们在写的简单排序,如冒泡排序时都只是对简单的整数排序。排序的数据类型也比较单一,但是qsort函数不同,qsort函数可以用于排序任何数据类型,包括基本数据类型(如整型、浮点型等)和自定义数据类型(如结构体、类等)。

它是C标准库中的一个函数,它的函数原型如下:

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

 qsort函数接受四个参数:

1.void *base:指向待排序数组的首元素的指针。

2.size_t num:数组中元素的个数。

3.size_t size:每个元素的大小(以字节为单位)。

4.int (*compar)(const void *, const void *):指向比较函数的指针。

 比较函数compar 用于定义排序的顺序。它接受两个指向待比较元素的指针,并返回一个整数值,表示两个元素的大小关系。根据返回值的不同,qsort函数会按照升序或降序对数组进行排序。

qsort函数的使用

qsort函数就用到了回调函数。

我们先以最简单的整形排序为例,使用qsort函数进行排序。

#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
 return (*( int *)p1 - *(int *) p2);
}
int main()
{
  int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
  int i = 0;
 
  qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
  for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
 {
   printf( "%d ", arr[i]);
 }
  printf("\n");
  return 0;
}

 qsort函数的使用其实也很简单,重点就在于比较函数compar的定义。

比较函数的函数原型如下:

int compare(const void *a, const void *b);

a和b是指向待比较元素的指针,类型为const void*。在比较函数中,我们需要将这些指针转换为正确的类型,并进行比较。

返回值的含义如下:

  • 如果 a 小于 b,则返回一个负整数。
  • 如果 a 等于 b,则返回 0。
  • 如果 a 大于 b,则返回一个正整数

这里的void*大家是否疑惑,一般我们见到的都是整形、字符型、浮点型、结构体类型等等,几乎都没见过void*类型的指针。

void* 是C语言中的一种通用指针类型,可以用来指向任意类型的数据。void* 类型的指针可以存储任何类型的地址,但不能直接解引用,因为编译器不知道指针指向的具体类型,这里就是为什么要对指针进行强制类型转换的原因。

在以上示例中,我们首先将指针p1 和 p2转换为 int* 类型,然后通过解引用操作符 * 获取指针指向的值,最后返回它们的差值,当然如果默认返回值,排序都是按照依次递增排序的,想要按照递减排序只需调整返回值的正负号即可。

 如我想让数组递减排序就可以这样修改:

int int_cmp(const void * p1, const void * p2)
{
 return (-(*( int *)p1 - *(int *) p2));//或者 return (*( int *)p2 - *(int *) p1)
}

 既然我们已经了解了qsort函数的基本使用方法,那我们来练一练。答案我会放在本期博客的最后

使用qsort对结构体进行比较(提示:结构体比较可以根据结构体某个成员进行比较排序)。

qsort模拟实现

我们将会使用大家都熟悉的冒泡排序来模拟实现qsort函数,以达到可以排序任意类型的数据。

 我们先使用函数封装一个冒泡排序的函数:

void bubble(int* a, int sz)
{
	
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - 1 - i; j++)
		{
			if(a[j]>a[j+1])
			{
				int t = a[j];
				a[j] = a[j + 1];
				a[j + 1] = t;
			}
			
		}
	}
}
int main()
{
	int a[10] = { 1,3,6,8,0,9,7,4,2,5 };
	int sz = sizeof(a) / sizeof(a[0]);
	bubble(a, sz);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

 当前的冒泡排序仅限于整形的排序,并不能像qsort那样可以排任意类型的数据,我们依据这个特点对程序进行分析。

问题一:排序单调,函数只能接收整形的数据。

问题二:比较大小,数据类型不同不能直接的使用 “ > ”和“ < ”比较大小

问题三:交换,数据类型不同交换的方法也不同。

这将是我们接下来要解决的问题。

问题一:排序单调问题,我们可以模仿qsort函数使用void*类型的函数去接收不同类型的参数

void bubble(void* arr, int num, int sz, int(*cmp)(const void*, const void*))

 问题二:数据比较问题,我们可以像qsort函数那样,定义一个compar函数比较关系,重难点就在于cmp函数的实现。对于整形来说很简单,可以套用上边qsort函数的格式去写,也仅仅只是修改一下变量名。那对于字符串我们要怎么进行比较呢?

在这之前我们需要知道字符串是如何比较的规则如下:

  1. 如果两个字符串在相同位置上的字符相等,则继续比较下一个位置的字符。
  2. 如果两个字符串在相同位置上的字符不相等,则返回第一个不相等字符的ASCII码差值(即第一个字符串的字符ASCII码减去第二个字符串的字符ASCII码)。
  3. 如果遇到其中一个字符串的结尾(即遇到'\0'字符),则返回两个字符串长度的差值(即第一个字符串的长度减去第二个字符串的长度)。

注意点:字符串比较,我们可想到哪个库函数? strcmp,它可以实现对字符串的比较。

如果两个字符串相等,则返回0;如果第一个字符串小于第二个字符串,则返回一个负值;如果第一个字符串大于第二个字符串,则返回一个正值。

return strcmp((*(char*)p1), (*(char*)p2));

 出了返回值类型,我们还需要关注的是cmp函数传参的数据类型,相邻两元素作为参数传递给cmp函数,那要怎么设计才能传递任何类型的数据呢?

我们已经知道传参无论是什么类型,传进去的都是数组首元素的地址,根据这点我们可以进行设计:

cmp((char*)arr + j * sz, (char*)arr + (j + 1) * sz)

 为什么选择强制类型转化为字符型,因为字符在内存中仅占一个字节,是占内存最小的数据类型,所以char类型是最合适的选择。并且数据的类型不同,arr+1所跳过的空间大小也不同,我们需要通过char类型模拟各个类型的数组遍历,于是便这样设计:(char*)arr + j * sz(sz为传入数据类型所占的字节大小)。

通过+j*sz以实现对任意类型的数组相邻两元素的传参。

一整形为例:

 如图所示,数组相邻两个元素之间相差4个字节(不同的数据类型占空间大小也各不相同),正常情况下,数组名+1会自动跳到第二个元素的位置。但我们在上述代码中,将arr强制类型转化为char类型,这时数组名+1就会指向如下图中的位置

 这样并不能将数据完整的传过去。所以我们需要根据不同的数据所占的字节大小自动的调整跳过的字节大小,所以(char*)arr + j * sz,整形的sz为4一次跳过四个字节的空间,刚好可以将数据完整的传递。

 

 问题三:数据交换问题,交换的数据可能是字符,整形,浮点型数据,那么我们就要设计一个能够符合所有类型数据交换的函数(swap函数)。

void bubble(void* arr, int num, int sz, int(*cmp)(const void*, const void*))
{
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)arr + j * sz, (char*)arr + (j + 1) * sz)>0)
			{
				swap((char*)arr + j * sz, (char*)arr + (j + 1) * sz,sz);
			}
		}
	}
}

 结合上述思路,我们将相邻两元素传到swap函数中,强制类型转换为char类型来实现不同类型数据的传参。此外我们在传值到swap函数时也要将sz传过去。

void swap(char* p1, char* p2,int sz)
{
	int t = 0;
	for (int i = 0; i < sz; i++)
	{
		t = *p1;
		*p1 = *p2;
		*p2 = t;
		p1++;
		p2++;
	}
}

 传过去时为char类型,接收也使用char类型。这里的交换是将两元素对应的每个字节一次交换

 于是我们就使用冒泡排序来模拟实现了qsort函数

完整代码整理:

void swap(char* p1, char* p2,int sz)
{
	int t = 0;
	for (int i = 0; i < sz; i++)
	{
		t = *p1;
		*p1 = *p2;
		*p2 = t;
		p1++;
		p2++;
	}
}
void bubble(void* arr, int num, int sz, int(*cmp)(const void*, const void*))
{
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)arr + j * sz, (char*)arr + (j + 1) * sz)>0)
			{
				swap((char*)arr + j * sz, (char*)arr + (j + 1) * sz,sz);
			}
		}
	}
}

 模拟函数的使用

我们使用我们模拟实现的qsort函数来对结构体进行排序

先创建一个简单的结构体

struct stu {
	char name[20];
	int age;
};

 创建一个结构体数组。

int main()
{
	
	struct stu arr1[3] = { {"zhangsan",17},{"lisi",18},{"wangwu",19} };
	bubble(arr1, num1, sizeof(arr1[0]), cmp_stu_name);
	printf("按名字排序:\n");
	print_struct(arr1, num1, sizeof(arr1[0]));
	bubble(arr1, num1, sizeof(arr1[0]), cmp_stu_age);
	printf("按年龄排序:\n");
	print_struct(arr1, num1, sizeof(arr1[0]));
	return 0;
}

 cmp函数定义:

int cmp_stu_age(const void* p1, const void* p2)
{
	return (((struct stu*)p1)->age - ((struct stu*)p2)->age);
}
int cmp_stu_name(const void* p1, const void* p2)
{
	return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}

 完整代码:

struct stu {
	char name[20];
	int age;
};
int cmp_stu_age(const void* p1, const void* p2)
{
	return (((struct stu*)p1)->age - ((struct stu*)p2)->age);
}
int cmp_stu_name(const void* p1, const void* p2)
{
	return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
void swap(char* p1, char* p2,int sz)
{
	int t = 0;
	for (int i = 0; i < sz; i++)
	{
		t = *p1;
		*p1 = *p2;
		*p2 = t;
		p1++;
		p2++;
	}
}
void bubble(void* arr, int num, int sz, int(*cmp)(const void*, const void*))
{
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)arr + j * sz, (char*)arr + (j + 1) * sz)>0)
			{
				swap((char*)arr + j * sz, (char*)arr + (j + 1) * sz,sz);
			}
		}
	}
}

void print_struct(struct stu* arr1,int num, int sz)
{
	for (int i = 0; i < num; i++)
	{
		printf("%s %d\n", (arr1 + i)->name, (arr1 + i)->age);
	}
	printf("\n");
}
int main()
{
	struct stu arr1[3] = { {"zhangsan",17},{"lisi",18},{"wangwu",19} };
	int num1 = sizeof(arr1) / sizeof(arr1[0]);
	bubble(arr1, num1, sizeof(arr1[0]), cmp_stu_name);
	printf("按名字排序:\n");
	print_struct(arr1, num1, sizeof(arr1[0]));
	bubble(arr1, num1, sizeof(arr1[0]), cmp_stu_age);
	printf("按年龄排序:\n");
	print_struct(arr1, num1, sizeof(arr1[0]));
	return 0;
}

 运行结果

 我们可以根据所排序的数据类型不同,调整cmp函数。

使用qsort对结构体进行比较答案公布:

#include<stdlib.h>
#include<stdio.h>
#include<string.h>
struct stu {
	char name[20];
	int age;
};

int com_stu_age(const void* p1, const void* p2)
{
	return (((struct stu*)p1)->age - ((struct stu*)p2)->age);
}

int com_stu_name(const void* p1, const void* p2)
{
	return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}

void print1(int num, int sz, struct stu* arr1)
{
	for (int i = 0; i < num; i++)
	{
		printf("%s %d\n", (arr1 + i)->name, (arr1 + i)->age);
	}
	printf("\n");
}

int main() 
{
	struct stu arr1[4] = { {"Zhanglong",16},{"Zhaohu",17},{"Wangchao",18},{"Mahan",19}};
	int sz1 = sizeof(arr1) / sizeof(arr1[0]);
	qsort(arr1, sz1, sizeof(arr1[0]), com_stu_age);
	printf("按年龄排序:\n");
	print1(sz1, sizeof(arr1[0]), arr1);
	qsort(arr1, sz1, sizeof(arr1[0]), com_stu_name);
	printf("按名字排序:\n");
	print1(sz1, sizeof(arr1[0]), arr1);
	return 0;
}

总结

本期内容到这里就要结束了,希望大家能够理解并学会使用会回调函数,回调函数是一种非常有用的编程技术,它可以帮助我们更好地组织和管理代码。通过深入理解回调函数的工作原理和应用场景,我们可以更加高效地编写代码,提高我们的开发效率。最后,感谢阅读!

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

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

相关文章

-XX:MaxDirectMemorySize

-XX:MaxDirectMemorySize最大堆外内存大小&#xff0c;此参数的含义是当Direct ByteBuffer分配的堆外内存到达指定大小后就触发Full GC。首先可以在jdk文档中找到&#xff1a;关于MaxDirectMemorySize内存的描述&#xff1a;Sets the maximum total size (in bytes) of the New…

02_06内核cpu信息及内存布局及堆管理及内存使用情况

linux_cpu_大致信息查看 因为需要知道当前linux cpu是多少位的,才能知道内核空间的分布 64位Linux-般使用48位来表示虚拟地址空间&#xff0c;45位表示物理地址。通过命令: cat/proc/cpuinfo。 查看Linux内核位数和proc文件系统输出系统软硬件信息如下: 通过cat /proc/cpuinfo…

RabbitMQ笔记--消息中间件,rabbitmq安装及简单使用

1.消息中间件 消息&#xff1a;指在应用间传送的数据。 消息队列中间件&#xff1a;指利用高效可靠的消息传递机制进行与平台无关的数据交流&#xff0c;并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型&#xff0c;可以在分布式环境下扩展进程间的通…

unittest自动化测试之unittest封装方法

目录 封装方法 完整代码 总结&#xff1a; 封装方法 在编写自动化脚本的时候&#xff0c;都要求代码简介&#xff0c;上一篇unittest---unittest断言中代码重复性比较多&#xff0c;我们进行一次简单的优化&#xff08;优化方法不同&#xff0c;请勿喷&#xff09; 我们可以…

Video4linux: cannot set V4L control... 解决方案

问题描述 最近在用USB_CAM读取摄像头的时候&#xff0c;总是出现如下问题&#xff1a; 思来想去真是难受。 问题分析 在usb_cam/ config /usb_cam.yaml文件中&#xff0c;把他报错的东西加在ignore里面即可。 首先打开camera_driver.cpp文件&#xff0c;把908行的printf语句…

ARM64学习笔记---建立异常向量表(二)

源码: #include "mm.h" #include "sysregs.h".section .rodata .align 3 .globl el_string1 el_string1:.string "Booting at EL".section ".text.boot" .globl _start _start://读取mpidr_el1寄存器的值&#xff0c;该寄存器决定了…

学无止境·MySQL(4-3)(多表查询加强版-------更新版)

比4-2表中多几个数据&#xff0c;更具备普遍性 试题2&#xff08;更新加强版&#xff09;1、创建表2、找出销售部门中年纪最大的员工的姓名3、求财务部门最低工资的员工姓名4、列出每个部门收入总和高于9000的部门名称5、求工资在7500到8500元之间&#xff0c;年龄最大的人的姓…

MySql 高级-0706

1. MySQL 架构 1.1 MySQL 简介 mysql内核 sql优化攻城狮 mysql服务器的优化 各种参数常量设定 查询语句优化 主从复制 软硬件升级 容灾备份 sql编程 1.2 MySQL逻辑架构介绍 1.2.1第1层:连接层 ** 系统(客户端)访问 MySQL 服务器前&#xff0c;做的第一件事就是建立 TCP 连接…

HTML <map> 标签

实例 带有可点击区域的图像映射: <img src="planets.jpg" border="0" usemap="#planetmap" alt="Planets" /><map name="planetmap" id="planetmap"><area shape="circle" coords=&q…

多元分类预测 | Matlab 粒子群算法(PSO)优化xgboost的分类预测,多输入单输出模型。PSO-xgboost分类预测模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元分类预测 | Matlab 粒子群算法(PSO)优化xgboost的分类预测,多输入单输出模型。PSO-xgboost分类预测模型 多特征输入单输出的二分类及多分类模型。程序内注释详细,直接替换数据就可以用。程序语言为matlab,程…

利用Python构建科学的交易系统:以趋势为例

在股票市场中&#xff0c;趋势被认为是一个非常重要的概念。趋势可以通过股票的价格、交易量等指标来确定。如果一只股票的价格在持续上涨&#xff0c;那么我们可以认为该股票处于上升趋势&#xff1b;反之&#xff0c;如果一只股票的价格在持续下跌&#xff0c;那么我们认为该…

物理机到容器的发展历程

物理机到容器的发展历程 物理主机虚拟化技术什么是虚拟化&#xff1f;虚拟化分类主流的虚拟化技术KVMLXC 容器Docker与Containerd容器编排为什么要编排工具容器编排工具有哪些&#xff1f; 物理主机 从传统的物理服务到现如今的云服务的发展离不开应用服务的拆分&#xff0c;由…

PDF怎么转长图?这四个方法免费好用!

记灵在线工具怎么转PDF为长图&#xff1f;PDF文件是一种常见的文档格式&#xff0c;它可以在不同的设备和操作系统上保持格式的一致性。然而&#xff0c;有时候我们需要将PDF文件转换成长图&#xff0c;以便于在社交媒体上分享或者在网站上展示。本文将介绍如何使用记灵在线工具…

110、基于51单片机智能浇花浇水系统土壤湿度检测温度自动灌溉报警设计(程序+原理图+PCB源文件+原理图讲解+参考论文+开题报告+程序流程图+元器件清单等)

选题背景及意义 在中国广大面积的农村&#xff0c;没有发达的工商业&#xff0c;有的只是大量闲置的田地。如果利用这些闲置的田地&#xff0c;种植美丽的花卉、树苗&#xff0c;能给当地带来一笔可观的收入。而这些花卉及树苗的种植对土壤湿度&#xff0c;温度有着极高的要求…

RTOS任务切换过程中堆栈的使用情况

我们知道 Cortex-M3 系列单片机内部有双堆栈机制。即 Cortex‐M3 拥有两个堆栈指针&#xff1a;主堆栈&#xff08;MSP&#xff09;和进程堆栈&#xff08;PSP&#xff09;。任一时刻只能使用其中的一个。通过控制寄存器 CONTROL 中的选择位进行控制。 两个堆栈指针如下&#…

CentOS环境下的Maven安装

CentOS 安装 Maven 镜像地址 镜像地址&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/apache/maven/ 下载地址&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.8.8/binaries/ 下载maven 将下载好的压缩包拷贝到根目录下 解压 tar -zxvf ap…

硬件性能 - 掌握内存知识

简介 本文章主要介绍了内存的基本知识&#xff0c;简单的判断内存是否耗尽、是否出现内存泄漏等问题。其他硬件性能分析如下&#xff1a; 1. 硬件性能 - CPU瓶颈分析 2. 硬件性能 - 磁盘瓶颈分析 3. 硬件性能 - 网络瓶颈分析 目录 1. 内存的定义 1.1. 内存的基本概念 1.2. 内…

Coggle 30 Days of ML (23年7月)任务二:数据可视化

Coggle 30 Days of ML (23年7月&#xff09;任务二&#xff1a;数据可视化 任务二&#xff1a;对数据集字符进行可视化&#xff0c;统计标签和字符分布 说明&#xff1a;在这个任务中&#xff0c;需要使用Pandas库对数据集的字符进行可视化&#xff0c;并统计数据集中的标签和…

【JavaEE】Servlet 中常用API有哪些?前后端交互方式有哪些?

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 目录 一、Servlet 运行原理 二、Servlet常用API 2.1 HttpServlet&#xff08;抽象类&#xff09; 2.1.1. init 方法 2.1.2 service方法 2.1.3 destroy方法 三、HttpServletRequest 3.1 Ht…

如何自动(定时/间隔/重复)执行 同步文件、备份打包加密压缩文件

参考下列两个教程结合使用即可&#xff1a; 快捷自由定时重启、注销、关机、文件夹同步打开程序等 如何从多个文件夹内转移全部文件&#xff08;忽略文件夹的结构&#xff09;&#xff08;进行复制&#xff09;&#xff08;再打包&#xff09; 就是先设定好 勾选对 来源路径’…