zl程序教程

您现在的位置是:首页 >  硬件

当前栏目

【C语言进阶】字符串函数与内存函数的学习与模拟实现

内存C语言模拟学习 实现 函数 字符串 进阶
2023-09-11 14:20:02 时间

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:C语言进阶
🎯长路漫漫浩浩,万事皆有期待

1.字符串处理函数介绍

我们在C语言的从程序代码编写中,对字符和字符串的处理相当频繁,但是C语言本身并没有字符串类型。而字符串通常放在【常量字符串】或者【字符数组】中。其中,字符串常量适用于那些对它不做修改的字符串函数。

1.1 strlen 函数:

strlen 函数(string length)的作用是计算返回字符串中结束标识符 ’ \0 ’ 之前出现的的字符个数,因此,strlen函数所处理的字符串必须是以结束标识符 ’ \0 ’ 结尾的字符串。其·返回值类型为size_t 类型,该类型为无符号类型
strlen 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
 
int main()
{
    //两中可行的初始化方式:
	char arr1[]="Hello!";
	char arr2[] = { 'H','e','l','l','o','!','\0' };
	int ret1 = strlen(arr1);
	int ret2 = strlen(arr2);
	printf("The length of arr1 is %d\n", ret1);
	printf("The length of arr2 is %d\n", ret2);
	return 0;
}

在这里插入图片描述

我们使用字符型数组将字符串储存起来,接着使用 strlen 函数计算字符串 " Hello! " 中所有字符的数量使用双引号初始化字符串时,编译器将会自动在最后添加上结束标识符,并可以使用一个整型变量接收 strlen 函数的返回值,进行打印。
并且我们还说到,strlen 函数的返回类型为无符号数,因此 strlen 函数不可以直接用来比较两个字符串的大小。例如:

#include<stdio.h>
#include<string.h>
int main()
{
	const char* str1 = "abcdef";
	const char* str2 = "bbb";
if (strlen(str2) - strlen(str1) > 0)
	{
		printf("str2 > str1\n");
	}
	else
	{
		printf("srt1 < str2\n");
	}
	return 0;
}

我们可能会认为计算出的结果为 3 - 6 为 -3,结果会执行 else 语句。但事实上,因为 strlen 函数的返回类型为无符号类型,得出的结果也是无符号类型,于是原本第一位即符号位上的数字也将被作为数据的一部分,因而实际得出的结果为一个非常大的整数,执行了 if 语句。
于是如果我们想要使用 strlen 函数来判断字符串的大小,可以通过让它的返回值由无符号数变为有符号数即可,即强制类型转换来实现:

#include<stdio.h>
#include<string.h> 
int main()
{
	const char* str1 = "abcdef";
	const char* str2 = "bbb";
 
if ((int)strlen(str2) - (int)strlen(str1) > 0)
	{
		printf("str2 > str1\n");
	}
	else
	{
		printf("srt1 < str2\n");
	}
	return 0;
}

1.2 strcpy 函数:

在之前的学习中,strcpy 函数也是我们经常使用的字符串处理函数之一。
strcpy 函数(string copy)的作用是,可以将字符串从源地址复制至目的地址并且它会将源地址内的结束标识符 ’ \0 ’ 一并拷贝过去,因此源地址必须以 ’ \0 ’ 结尾,且目的地址也将以结束标识符结尾,通俗来讲就是用来实现字符串的复制和拷贝。并且,因为其作用为拷贝字符串,因此目标地址内的空间必须足够大,要有足够的空间容纳下源地址内的字符串,同时目的地址的空间必须是可变、可修改的。

strcpy 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
int main()
{
	const char arr1[] = "Hellow!";
	const char arr2[10] = { 0 };
	printf("Before copy , the char inside arr1 are %s\n", arr1);
	printf("Before copy , the char inside arr2 are %s\n", arr2);
	printf("\n");
	strcpy(arr2, arr1);
    //strcpy函数使用格式为:strcpy(目的地址, 源地址)
	printf("After copy , the char inside arr1 are %s\n", arr1);
	printf("After copy , the char inside arr2 are %s\n", arr2);
	return 0;
}

在这里插入图片描述

我们要特别注意的是 strcpy 函数返回的是目标空间的起始地址,该函数设置返回值值类型的目的是为了实现链式访问

1.3 strcat 函数:

strcat 函数(string catenate)的作用是,将源地址的字符串追加补充至目的地址处。与字符串拷贝函数相同,它在进行补充追加时是从目的地址的结束标识符处 ’ \0 ’ 开始追加的,追加至源地址的结束标识符处停止。且它同样要求目标地址内的空间必须足够大,要有足够的空间容纳下源地址内的字符串,同时目的地址的空间必须是可变、可修改的。

strcat 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
int main()
{
	const char arr1[20] = "Hello!";
	const char arr2[20] = "Welcome!";
	printf("Before catenate , the char inside arr1 are %s\n", arr1);
	printf("Before catenate , the char inside arr2 are %s\n", arr2);
	printf("\n");
	strcat(arr2, arr1);
	printf("After catenate , the char inside arr1 are %s\n", arr1);
	printf("After catenate , the char inside arr2 are %s\n", arr2);
	return 0;
}

在这里插入图片描述

注意 strcat 函数无法追加自己。原因很好理解,我们在定义时就已经固定了字符数组的储存空间了,当追加自己时,相当于将自己与和与自己相同大小的字符数组,即两倍自身大小的数据放入自己的储存空间中,可想而知一定是不可行的。

1.4 strcmp 函数:

strcmp 函数(string compare)的作用为按照顺序依次比较两字符串对应位置字符的 ASCII 码值(注意不是比较两字符串的长度),直到结束标识符 ’ \0 ’ 或对应位置的字符不同。若比较至结束标识符都没有不同则两字符串相等,若两字符串对应位置字符有不同,对应位置上字符 ASCII 码值小的字符串小于另一个字符串。
我们来看 strcmp 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
int main()
{
	const char arr1[] = "abcd";
	const char arr2[] = "abz";
	int ret = strcmp(arr1, arr2);
	//若arr1大于arr2,则返回大于零的数
	//若arr1等于arr2,则返回等于零的数
	//若arr1小于arr2,则返回小于零的数
	if (ret > 0)
	{
		printf("arr1 > arr2\n");
	}
	else if (ret = 0)
	{
		printf("arr1 = arr2\n");
	}
	else
	{
		printf("arr1 < arr2\n");
	}
	return 0;
}

在这里插入图片描述

定义并初始化两个字符数组,接着对两个数组进行比较,根据 strcmp 函数的比较结果得到返回值,再根据返回值反馈我们想要的字符串比较结果。
注意并不是比较字符串的长短,而是比较对应位置字符 ASCII 码值的大小

1.5 strncpy 函数:

strncpy函数(string number copy)的作用为将指定长度的字符串复制到字符数组中,即表示把源地址中字符串开始的前n个字符拷贝到目的地址中。与 strcpy 相同,它同样会将源地址内的结束标识符 ’ \0 ’ 一并拷贝过去,因此源地址必须以 ’ \0 ’ 结尾,且目的地址也将以结束标识符结尾。并且,因为其作用为拷贝字符串,因此目标地址内的空间必须足够大,要有足够的空间容纳下源地址内的字符串,同时目的地址的空间必须是可变、可修改的。

strncpy 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
 //strncpy函数的使用:
int main()
{
	const char arr1[] = "Hellow!Welcome!";
	const char arr2[10] = { 0 };
	printf("Before copy , the char inside arr1 are %s\n", arr1);
	printf("Before copy , the char inside arr2 are %s\n", arr2);
	strncpy(arr2, arr1, 7);
	printf("After catenate , the char inside arr1 are %s\n", arr1);
	printf("After catenate , the char inside arr2 are %s\n", arr2);
 return 0;
}

在这里插入图片描述

我们看到,使用该函数,我们成功的将字符数组 arr1 中的前七个字符拷贝到了字符数组 arr2 中。
注意:若源字符串的长度小于我们传递过去的参数,则拷贝完源字符串之后,将会在目标后追加字符 ’ 0 ',直到拷贝至参数规定个数。

1.6 strncat 函数:

strncat 函数(string num catenate)的作用为从源地址处将指定长度的字符串追加补充到目的地址中与 strcat 函数类似,它在进行补充追加时也是从目的地址的结束标识符处 ’ \0 ’ 开始追加的,不同的是追加至参数限制的字符数处停止。但它同样要求目标地址内的空间必须足够大,要有足够的空间容纳下出家补充的字符串 ,同时目的地址的空间必须是可变、可修改的。
strncat 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
 
int main()
{
	const char arr1[] = "Hellow!Welcome!";
	const char arr2[20] = "Welcome!";
 	printf("Before copy , the char inside arr1 are %s\n", arr1);
	printf("Before copy , the char inside arr2 are %s\n", arr2);
	strncat(arr2, arr1, 7);
	printf("After catenate , the char inside arr1 are %s\n", arr1);
	printf("After catenate , the char inside arr2 are %s\n", arr2);
	return 0;
}

在这里插入图片描述
我们可以看到,首先定义并初始化两个字符数组,接着打印追加补充之前它们各自空间内的内容进行确认,然后我们使用了 strncat 函数有限制的从数组 arr1 向 arr2 中追加补充了七个字符。
通过限制长度我们就可以实现字符数组对自己的追加补充了。

1.7 strncmp 函数:

strncmp 函数(string number compare)的作用为有限制的按照顺序依次比较两字符串对应位置字符的 ASCII 码值(注意不是比较两字符串的长度),直到参数限制位数位置上全部比较结束或对应位置的字符不同。若参数限制位数位置上的字符都比较结束且都没有不同则两字符串相等,若两字符串对应位置字符有不同,对应位置上字符 ASCII 码值小的字符串小于另一个字符串。
strncmp 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
int main()
{
	const char arr1[] = "abcd";
	const char arr2[] = "abz";
	int ret = strncmp(arr1, arr2, 3);
    //比较前 3 个字符
	//若arr1大于arr2,则返回大于零的数
	//若arr1等于arr2,则返回等于零的数
	//若arr1小于arr2,则返回小于零的数
 if (ret > 0)
	{
		printf("arr1 > arr2\n");
	}
	else if (ret = 0)
	{
		printf("arr1 = arr2\n");
	}
	else
	{
		printf("arr1 < arr2\n");
	}
 return 0;
}

在这里插入图片描述

其作用原理与作用过程,与 strncmp 函数十分类似,唯一不同便是限制了字符比较的位数
注意该函数的作用也不是比较字符串的长短,而是比较对应位置字符 ASCII 码值的大小

1.8 strstr 函数:

strstr 函数(string string)的作用为从一个字符串中寻找其字串,通俗来讲就是从一个字符串中寻找另一个字符串若找到目标字串则返回指向目标字串的指针,若没有找到目标字串则返回空指针(不返回)
strstr 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
int main()
{
	const char arr1[] = "abcdefg";
	const char arr2[] = "cde";
 char* ret = strstr(arr1, arr2);
	//从ar1中寻找arr2
	if (ret == NULL)
	{
		printf("找不到该字符串!\n");
	}
	else
	{
		printf("成功找到该字符串'%s'!\n", ret);
	}
 return 0;
}

在这里插入图片描述
我们定义并初始化了两个字符数组,并使用 strstr 函数进行字串寻找处理,并使用字符型指针接收汉函数的返回值,最后,对接收了返回值的指针 ret 进行判断,若为空则确定为没有从数组 arr1 中找到字串 arr2,否则便通过返回值,使用指针对字串地址内的数据进行访问。
该函数数有返回值,支持进行函数的链式访问

1.9 strtok 函数:

strtok 函数(string token)的作用为将字符串分解为一组字符串。该函数有两个数组作为参数,它的实际作用便是将其中一个数组为分割数组,在另一个数组中寻找这些“分割符”,并在分割符处将这个数组内的字符串加上结束标识符 ’ \0 ’ ,将其分割成一组(多个)字符串若第一个参数不为 NULL ,将找到字符数组中的第一个标记并保存它在字符串中的位置;若第一个参数为 NULL ,将在同一个字符串中被保存的位置开始,查找下一个标记
strtok 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "1254594572@QQ.COM";
	char arr2[30] = { 0 };
	strcpy(arr2, arr1);
	const char* arr3 = "@.";
	printf("账号:%s\n", strtok(arr2, arr3));
	//找到第一个标记停止
	printf("服务商:%s\n", strtok(NULL, arr3));
	//从保存好的位置开始往后找
	printf("网址后缀:%s\n", strtok(NULL, arr3));
	//从保存好的位置开始往后找
	return 0;
}

在这里插入图片描述

1.strtok 函数是会对数组本身进行操作的,所以我们为了保护原始数据,在定义并初始化好字符数组之后,又定义了一个新的数组并将原始数据拷贝过去,作为临时拷贝供我们进行操作。
2.接着我们定义并初始化了分割符数组,函数将根据分割符数组 arr3 对 临时拷贝 arr2 进行分割。第一次执行函数时之前没有标记,于是直接进行操作找到第一个标记并分割打印。
3.此时就已经存在标记了,再连续两次找到前一次执行作下的标记按照分割符将数组分割完毕并打印。

我们可以将上面段代码优化为:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "1254594572@QQ.COM";
	char arr2[30] = { 0 };
	strcpy(arr2, arr1);
	const char* arr3 = "@.";
	char* str = NULL;
	for (str = strtok(arr2, arr3); str != NULL; str = strtok(NULL, arr3))
	{
		printf("%s\n", str);
	}
	return 0;
}

注意:若字符串中不存在更多的标记,则返回 NULL 指针。

1.10 strerror 函数:

strerror函数(string error)的作用为返回错误码对应的信息。即根据接收到的错误码(错误码 errno 为全局变量),返回错误原因的详细信息
我们来看 strerror 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
int main()
{
	int i = 0;
	for (i = 0; i <= 4; i++)
	{
		printf("错误原因为:%s\n", strerror(i));
	}
	return 0;
}

在这里插入图片描述

2.内存函数介绍

2.1 memcpy 函数:

memcpy函数(memory copy)的作用为从源内存空间向目的内存空间拷贝限制数量(单位是字节)的数据。它与 strcpy 函数类似,作用均为拷贝数据,不同的是 strcpy 仅仅只操作字符串故会在结束标识符 ’ \0 ’ 处停止,而 memcpy 函数操作的是内存,内存中的数据是相邻的,故不会在结束标识符处停止
memcpy 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9 };
	int arr2[5] = { 0 };
	printf("Before copy , the char inside arr2 are :>");
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf(" %d", arr2[i]);
	}
	printf("\n");
	memcpy(arr2, arr1, 20);
	printf("After copy , the char inside arr2 are :>");
	for (i = 0; i < 5; i++)
	{
		printf(" %d", arr2[i]);
	}
	printf("\n");
	return 0;
}

在这里插入图片描述

首先创建并初始化了两个整形数组,接着打印出数组 arr2 内数据的存放情况。通过内存拷贝函数,将数组 arr1 内的前20个字节的数据拷贝给了 arr2数组。整型数组内每个数据元素所占的内存空间为4个字节,20个字节即将数组 arr1 中的前五个数据元素拷贝给了数组 arr2。最后再次打印数组 arr2 中的数据,验证拷贝结果。

注意:如果源内存空间和目标内存空间有任何的重叠,复制的结果都是未定义的。

2.2 memmove 函数:

memmove函数(memory move)的作用为弥补 memcpy 函数的不足,主要用于处理内存的重叠部分。即如果源空间和目标空间出现重叠,就得使用 memmove 函数来进行处理。
memcpy 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
 
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	
printf("Before copy , the char inside arr are :>");
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf(" %d", arr[i]);
	}
	printf("\n");
	memmove(arr+2, arr, 20);
	printf("After copy , the char inside arr are :>");
	for (i = 0; i < 5; i++)
	{
		printf(" %d", arr[i]);
	}
	printf("\n");
 return 0;
}

在这里插入图片描述

2.3 memcmp 函数:

memcmp 函数(memory compare)的作用与 strcmp 函数的作用类似,不过 memcmp 函数是从内存的角度以字节为单位进行处理,故 memcmp 函数同样需要第三个参数进行限制,而不会在结束标识符 ’ \0 ’ 处停止比较。
memcmp 函数的基本使用方式:

#include<stdio.h>
#include<string.h>
 
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9 };
	int arr2[] = { 1,2,3,4,5,9,6,7,8 };
 int ret = memcmp(arr2, arr1, 24);
 	if (ret > 0)
	{
		printf("arr1 < arr2\n");
	}
	else if (ret == 0)
	{
		printf("arr1 = arr2\n");
	}
	else
	{
		printf("arr1 > arr2");
	}
 return 0;
}

在这里插入图片描述

3.模拟库函数的实现

3.1 模拟实现strlen 函数:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
 
size_t my_strlen(const char* p)
{
	int count = 0;
	assert(p != NULL);
	//进行空指针校验,防止出现空指针
	while (*p != '\0')
	{
		count++;
		p++;
	}
	return count;
}
 
int main()
{
	const char arr[] = "Welcome!";
	int ret = my_strlen(arr);
	printf("The length of arr is %d\n", ret);
	return 0;
}

3.2 模拟实现strcpy 函数:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
 
char* my_strcpy(char* p2, const char* p1)
{
	assert(p2 != NULL);
	assert(p1 != NULL);
	//等价于assert(p2 && p1);
	char* ret = p2;
    //将目的地址作为返回值
	while (*p2++ = *p1++)
	{
		;
	}
 //返回目的地址的作用是为了实现链式访问
    return ret;
}
 
int main()
{
	const char arr1[] = "Welcome!";
	const char arr2[10] = { 0 };
	printf("Before copy , the char inside arr1 are %s\n", arr1);
	printf("Before copy , the char inside arr2 are %s\n", arr2);
	printf("\n");
	my_strcpy(arr2, arr1);
	printf("After copy , the char inside arr1 are %s\n", arr1);
	printf("After copy , the char inside arr2 are %s\n", arr2);
	return 0;
}

3.3 模拟实现strcat 函数:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
 
char* my_strcat(char* p2, char* p1)
{
	assert(p2 && p1);
	char* ret = p2;
	//查找目标空间中的结束标识符,找到后停止
	while (*p2)
	{
		p2++;
	}
	//从结束标识符开始追加
	//追加:
	while (*p2++ = *p1++)
	{
		;
	}
	return ret;
} 
int main()
{
	const char arr1[20] = "Hellow!";
	const char arr2[20] = "Welcome!";
	printf("Before catenate , the char inside arr1 are %s\n", arr1);
	printf("Before catenate , the char inside arr2 are %s\n", arr2);
	printf("\n");
	my_strcat(arr2, arr1);
	printf("After catenate , the char inside arr1 are %s\n", arr1);
	printf("After catenate , the char inside arr2 are %s\n", arr2);
	return 0;
}

3.4 模拟实现strcmp 函数:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
//模拟实现strcmp函数:
int my_strcmp(const char* p1, const char* p2)
{
	assert(p1 && p2);
	//找到字符不同的对应位:
	while (*p1 == *p2)
	{
		if (*p1 == '\0')
		{
			return 0;
		}
		p1++;
		p2++;
	}
	if (*p1 > *p2)
	{
		return 1;
	}
	else
	{
		return -1;
	}
}
 
int main()
{
	const char arr1[] = "abcd";
	const char arr2[] = "abz";
	int ret = my_strcmp(arr1, arr2);
	//若arr1大于arr2,则返回大于零的数
	//若arr1等于arr2,则返回等于零的数
	//若arr1小于arr2,则返回小于零的数
	if (ret = 1)
	{
		printf("arr1 > arr2\n");
	}
	else if (ret = 0)
	{
		printf("arr1 = arr2\n");
	}
	else
	{
		printf("arr1 < arr2\n");
	}
	return 0;
}

3.5 模拟实现strstr 函数:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
 
char* my_strstr(char* p1, char* p2)
{
	assert(p1 && p2); 
	char* pp1 = p1;
	char* pp2 = p2;
	char* cur = p1;
	while (*cur)
	{
		pp1 = cur;
		pp2 = p2;
		while (*pp1 && *pp2 && (*pp1 == *pp2))
		{
			pp1++;
			pp2++;
		}
		if (*pp2 == '\0')
		{
			return cur;
		}
		cur++;
	}
	return NULL;
}
	//模拟strstr函数的实现:
int main()
{
	const char arr1[] = "abcdefg";
	const char arr2[] = "cde";
 	char* ret = my_strstr(arr1, arr2);
	//从arr1中寻找arr2
	if (ret == NULL)
	{
		printf("找不到该字符串!\n");
	}
	else
	{
		printf("成功找到该字符串'%s'!\n", ret);
	}
 	return 0;
}

3.6 模拟实现memcpy 函数:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
 
void* my_memcpy(void* p2, const void* p1, size_t count)
{
	assert(p2 && p1);
    void* ret = p2; 
	while (count--)
	{
		*(char*)p2 = *(char*)p1;
		p2 = (char*)p2 + 1;
		p1 = (char*)p1 + 1;
	}
    return ret;
}
 
int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9 };
	int arr2[5] = { 0 };
	printf("Before copy , the char inside arr2 are :>");
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf(" %d", arr2[i]);
	}
	printf("\n");
 	my_memcpy(arr2, arr1, 20);
 	printf("After copy , the char inside arr2 are :>");
	for (i = 0; i < 5; i++)
	{
		printf(" %d", arr2[i]);
	}
	printf("\n");
 return 0;
}

3.7 模拟实现memmove 函数:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
 
void* my_memmove(void* p2, const void* p1, size_t count)
{
	assert(p2 && p1);
	void* ret = p2;
	if (p2 < p1)
	{
		while (count--)
		{
			*(char*)p2 = *(char*)p1;
			p2 = (char*)p2 + 1;
			p1 = (char*)p1 + 1;
		}
	}
	else
	{
		while (count--)
		{
			*((char*)p2 + count) = *((char*)p1 + count);
		}
	}
	return ret;
}
 
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
 printf("Before copy , the char inside arr are :>");
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf(" %d", arr[i]);
	}
	printf("\n");
 my_memmove(arr + 2, arr, 20);
 printf("After copy , the char inside arr are :>");
	for (i = 0; i < 5; i++)
	{
		printf(" %d", arr[i]);
	}
	printf("\n");
 return 0;
}

4.总结:

今天我们对字符串函数与内存函数的相关知识又有了新的了解,学习了10个常用字符串操作函数以及3个常用内存函数的相关知识,完成了其中部分库函数的自定义模拟实现,希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述