目录
🐳前言🐳
在前面一篇博客中,我们讲解了关于C语言中文件操作的使用和一些注意事项,那么这和我们今天要讲解的第三个版本的通讯录有什么关系呢?接下来就让我们一起来探讨一下其中的奥妙吧!
一.🐳文件版通讯录的基本实现思路🐳
温馨提示:本版本通讯录是在文件操作的基础上进行的该井,因此一定要熟悉文件操作,才能看明白本篇博客(关于文件操作的博客链接:http://t.csdn.cn/H7CtJ)
在上一个版本的通讯录(动态版)中,我们虽然在第一个版本的通讯录(静态版)的基础上实现了改进,节省了很大的一部分空间,但是动态版的通讯录同样也还存在缺陷。在生活中,我们使用的通讯录存储好之后会一直存在,但是在动态版以及静态的通讯录中我们只要关闭程序,之前在通讯录中保存的联系人就会消失。这是为什么呢?因为我们编写的程序是在内存上运行的,而内存有两个特点就是掉电会丢失数据并且在程序结束后,操作系统会收回之前开辟的内存空间。我们之前的两个版本通讯录保存联系人数据都是在内存上操作的,所以,一般程序结束或是电脑关机都会使其数据丢失。这个时候文件的作用就体现出来了,我们知道文件是存储在磁盘(外存)上的,而磁盘
有一个特点就是,数据掉电不丢失,可以使数据更具持久性。因此我们想到,另外创建一个文件存放通讯录中的联系人数据,这样当我们在此运行程序,或是关机重启后联系人信息依旧存在。
二.🐳通讯录各功能的实现🐳
该版本的通讯录是上一个版本通讯录(通讯录---动态版)的基础上进行的改进,与上个版本通讯大部分的功能都相同,不同的是该版本增加了加载通讯录与保存通讯录两个模块,因此如果对之前动态版的通讯录理解的小伙伴可以直接跳到loadconatct与SaveContact
1.🐬定义联系人🐬
//定义一个联系人的信息
typedef struct PeoInfo
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char tele[TELE_MAX];
char addr[ADDE_MAX];
}PeoInfo;
联系人信息包括,姓名,年龄。性别,电话,地址。结构体类型为struct PeoInfo,typedef将结构体类型重定义为了PeoInfo(这里可千万不能把PeoInfo看做一个结构体变量,要注意区分两者,不明白的小伙伴同样可以参考 <结构体---超详解>篇,链接在此:http://t.csdn.cn/tEdOh,里面有详细介绍结构体),NAME_MAX,SEX_MAX,TELE_MAX,ADDR_MAX都是#define定义的标识符常量,这样写有助于数据的修改
#define DEFAULT_SZ 3//设定通讯录容量大小
#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 12
#define ADDR_MAX 20
2.🐬创建定义通讯录的结构体🐬
//创建定义通讯录的结构体
typedef struct contact
{
PeoInfo* data;
int sz;//记录已存放的联系人个数
int capacity;//记录通讯录的容量大小
}contact;
3.🐬加载通讯录🐬
//加载文件
void LoadContact(contact* pc)
{
assert(pc);
//打开文件
FILE* pf = fopen("D:\\code\\test-a\\text_8_21_contact_text_version\\contact.txt", "rb");
//判空
if (pf == NULL)
{
perror("LoadContact::fopen");
return;
}
//读文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
CheakCapacity(pc);
pc->data[pc->sz] = tmp;
pc ->sz++;
}
//关闭文件
fclose(pf);
pf = NULL;
}
fopen先以绝对路径的方式打开创建好的文件(可以在创建.c源文件的同级目录下创建文件---文本文件,以文件名的方式打开文件,也可以在任意磁盘目录下创建文件,以绝对路径的方式打开文件),然后用fread函数从文件内读取一个联系人信息放入tmp结构体数组中,再将tmp数组内容给通讯录,fread函数读取成功会返回读取的总元素个数,我们一个一个的读,当读取到‘\0’时,循环结束,文件中读取的联系人也读取完毕,最后关闭文件,通讯录就加载完成了。
4.🐬初始化通讯录🐬
(1)🦈memset函数初始化通讯录🦈
//初始化通讯录
//文件版本
void InitContact(contact* pc)
{
assert(pc != NULL);
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
pc->data = (PeoInfo*)malloc(pc->capacity * sizeof(PeoInfo));
if (pc->data == NULL)
{
perror("InitContact malloc");
return;//同主函数中return 1;一样,不一样的地方在于,主函数中的返回值是int型,而这里是void型
}
memset(pc->data, 0, (pc->capacity * sizeof(PeoInfo)));
LoadContact(pc);
}
DEFAULT_SZ是#define定义的标识符常量,大小为3。malloc函数动态开辟了3个保存联系人的空间,当然,既然是使用动态内存函数,那就少不了判断空指针一步,因为如果malloc函数开辟空间失败会返回一个空指针,而末尾没有对开辟的空间进行释放是因为后续还需要对这片空间进行使用。memset函数同静态版本的通讯录一样,都是将开辟的通讯录内存初始化为0.(malloc为动态内存开辟函数,memset函数为内存函数,对动态内存函数不够清楚的可以看一下<动态内存管理--超详解>,链接:http://t.csdn.cn/XjDHW;对于内存函数不清楚的可以看一下<内存函数的使用及模拟>,链接:http://t.csdn.cn/Fep29)
该版本的通讯录与动态版的通讯录唯一不同的地方就是末尾多了一个加载通讯录函数。
(2)🦈calloc函数初始化通讯录🦈
//初始化通讯录
void InitContact(contact* pc)
{
assert(pc);
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
PeoInfo* ret = (PeoInfo*)calloc(pc->capacity ,sizeof(PeoInfo));
pc->data = ret;
if (pc->data == NULL)
{
perror("InitContact::malloc");
return;
}
}
calloc函数与malloc函数的功能相差无几,都是动态开辟一块内存空间,唯一不同的地方在于calloc函数会将开辟出的空间内容全部初始化为0。这种初始化的写法的优点在于内存空间的开辟和初始化一步就能完成,而不需要另再初始化通讯录。
5.🐬保存联系人🐬
void SaveContact(const contact* pc)
{
assert(pc);
//打开文件
FILE* pf = fopen("D:\\code\\test-a\\text_8_21_contact_text_version\\contact.txt", "wb");
if (pf == NULL)
{
perror("SaveContact::fopen");
return;
}
//写文件--输出操作
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
}
保存联系人其实很简单,与加载通讯录一样,打开文件,然后用一个循环和fwrite函数将通讯录中的每一个联系人信息都存进之前的文件中,这样下次运行程序,只需在初始化通讯录时加载一下通讯录,联系人信息就会都被加载进通讯录中。
6.🐬增加联系人🐬
void CheakCapacity(contact* pc)
{
//容量已存放满,进行扩容
if (pc->sz == pc->capacity)
{
PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
if (tmp == NULL)
{
perror("CheakCapacity::realloc");
return;
}
else
{
pc->data = tmp;
}
pc->capacity += 2;
printf("增容成功\n");
}
}
void AddContact(contact* pc)
{
assert(pc);
//判断通讯录是否存满
CheakCapacity(pc);
printf("请输入联系人的姓名:");
scanf("%s",pc->data[pc->sz].name);
printf("请输入联系人的年龄:");
scanf("%d",&(pc->data[pc->sz].age));
printf("请输入联系人的性别:");
scanf("%s",pc->data[pc->sz].sex);
printf("请输入联系人的电话:");
scanf("%s",pc->data[pc->sz].tele);
printf("请输入联系人的地址:");
scanf("%s",pc->data[pc->sz].addr);
pc->sz++;
printf("增加成功!\n");
}
增加联系人与静态版的不同之处就在于动态版在通讯录满后会用realloc函数进行容量开辟,通讯录每满一次,就多增加两个容量,这样就能合理利用内存,而不造成空间的浪费。
7.🐬销毁通讯录(释放内存)🐬
//销毁通讯录(释放内存)
void DestroyContact(contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
printf("销毁成功!\n");
}
通讯录的销毁其实很简单,学过动态内存管理的小伙伴肯定一眼就能明白,通讯录的销毁其实就是释放动态开辟的空间,空间释放了,通讯录自然也就销毁了。
8.🐬打印通讯录🐬
//打印通讯录
void PrintContact(contact* pc)
{
assert(pc);
int i = 0;
printf("%-10s %-5s %-10s %-12s %-20s\n", "姓名", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++)
{
printf("%-10s %-5d %-10s %-12s %-20s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex,
pc->data[i].tele, pc->data[i].addr);
}
}
打印通讯录时需要注意的是,通讯录虽然有存储1000个人位置,但是打印时不用把所有1000个元素都打印出来,我们只用打印出已保存的联系人即可,所以循环遍历数组时,i小于sz就行。%-10s中符号表示左对齐,10表示10个字符大小空间,%s表示打印字符串,其余几个与之同理。
9.寻找联系人🐬
//寻找联系人
int FindByName(contact* pc, char name[])
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(name, pc->data[i].name) == 0)
{
return i;//找到了,返回该联系人下标
}
}
return -1;//找不到
}
这个函数至关重要,因为通讯录中的删除,查找,修改等功能都需要先找到指定的联系人才能够对其进行后续操作。这里遍历同上面一样,我们只需要遍历已保存的联系人即可,这样可以一定程度上提高代码的运行效率。查找联系人时用到了strcmp(字符比较函数),因为名字是字符串,不能用一般的大于,小于符号来进行比较。找不到时返回-1是为了避免与数组元素下标混淆。(对字符串函数不太了解,或是对其感兴趣的小伙伴,可以去博主主页考一下古,<各字符串函数的使用---超详解>,链接:http://t.csdn.cn/l8fXS)
10.删除指定联系人🐬
//删除指定联系人
void DelContact(contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入你要删除的联系人姓名:");
scanf("%s", name);
//1.找到要删除的联系人
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("通讯录中没有该联系人\n");
return;
}
//2.找到后删除联系人
memmove(&(pc->data[ret]), &(pc->data[ret + 1]), ((pc->sz) - (ret + 1)) * sizeof(pc->data[0]));
printf("该联系人已删除\n");
pc->sz--;
}
删除联系人的函数需要注意的有两点,其一是若没有找到联系人要立刻return结束函数,否则继续执行下次逻辑就会出错,return;这条语句表示程序非正常结束。另外需要说明的是删除联系人的方法----覆盖,没错,这里采用删除方法就是覆盖,即把要删除的联系人的后面的所有联系人都往前覆盖一个元素,并把sz减1,这样就实现联系人的删除。而这里使用的是内存移动函数memmove进行覆盖,第一个参数是目标空间地址,第二个参数是源空间地址,而第三个参数是操作的总字节数(对于此函数的具体讲解以及使用在 <内存函数的使用>这片博客中博主也有详细讲解,链接在此:http://t.csdn.cn/3rFqQ )。当然,这里也可以用循环的方法来做,如下:
//删除指定联系人
void DelContact(contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入你要删除的联系人姓名:");
scanf("%s", name);
//1.找到要删除的联系人
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("通讯录中没有该联系人\n");
return;
}
//2.找到后删除联系人
int j = 0;
for (j = ret; j < pc->sz - 1; j++)
{
pc->data[j] = pc->data[j + 1];
}
printf("该联系人已删除\n");
pc->sz--;
}
11.修改联系人信息🐬
//修改联系人信息
void ModifyContact(contact* pc)
{
//1.找到要修改信息的联系人
char name[NAME_MAX];
printf("请输入所要修改信息的联系人姓名:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (-1 == ret)
{
printf("通讯录中没有该联系人\n");
return;
}
//2.修改联系人信息
printf("请输入需要修改的信息\n");
printf("请输入姓名:");
scanf("%s",pc->data[ret].name);
printf("请输入年龄:");
scanf("%d",&(pc->data[ret].age));
printf("请输入性别:");
scanf("%s",pc->data[ret].sex);
printf("请输入电话:");
scanf("%s",pc->data[ret].tele);
printf("请输入地址:");
scanf("%s",pc->data[ret].addr);
printf("信息修改成功\n");
}
修改联系人信息函数的实现比较简单,简单来说,就是找到联系人,然后再把该联系人的信息再输入一遍,需要注意的是,如果写代码时你是复制前面增加联系人时的代码,一定要记得更改下标!
12.搜索联系人🐬
//搜索联系人
void SearchContact(contact* pc)
{
char name[NAME_MAX];
printf("请输入要查找的联系人姓名:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (-1 == ret)
{
printf("通讯录中没有该联系人\n");
return;
}
printf("%-10s %-5s %-10s %-12s %-20s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-10s %-5d %-10s %-12s %-20s\n", pc->data[ret].name, pc->data[ret].age, pc->data[ret].sex,
pc->data[ret].tele, pc->data[ret].addr);
}
搜索联系人函数也比较简单,只需要找到联系人然后打印出来即可。现在是不是越发的体会到了寻找联系人函数的重要性,我们将其封装成了一个函数,极大程度上减小了代码的冗余程度。
13.排序🐬
int compare_name(const void* e1, const void* e2)
{
//return strcmp( ((contact*)e1)->data->name, ((contact*)e2)->data->name);
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
//升序排序
void SortContact(contact* pc)
{
qsort(pc->data, pc->sz, sizeof(pc->data[0]), compare_name);
}
qsort函数可以实现对任意类型数据的排序,这里对结构排序,冒泡排序是排不了的,所以qsort函数正好可以解决这一问题,qsort包含于<stdlib.h>头文件下,下面是对qsort函数的使用说明:
函数声明:
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));
//sqort是一个库函数,是基于快速排序算法实现的函数。
//sqort函数可以排序任意类型的数据。
//void sqort(void* base, //待排序数据的起始位置
// size_t num, //数组的元素个数
// size_t width, //一个元素的字节大小
// int(*cmpar)(const void* e1, const void* e2)//void* 是一种无确切类型的指针,不能够直接解引用
// //cmpar是一个比较函数,e1,e2是带比较的两个元素的地址
// //cmpar的返回值是>0,<0,或是==0
// //sqort函数要求使用者,自定义一个比较函数!即使用者根据实际情况,
// //提供一个函数实现两个数据的比较。
三.🐳文件版通讯录的整体模块化实现🐳
1.test.c🐬
测试通讯录逻辑
#define _CRT_SECURE_NO_WARNINGS
//test.c专用于测试通讯录的功能
//通讯录
#include"contact.h"
void menu()
{
printf("********************************\n");
printf("***** 1.add 2.del ******\n");
printf("***** 3.modify 4.searh ******\n");
printf("***** 5.sort 6.print ******\n");
printf("***** 0.exit ******\n");
printf("********************************\n");
}
void test()
{
int input = 0;
contact con;//创建一个通讯录
InitContact(&con);
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch(input)
{
case EXIT:
SaveContact(&con);
DistroyContact(&con);
printf("退出通讯录!\n");
break;
case ADD:
AddContact(&con);
break;
case DEL:
DelContact(&con);
break;
case MODIFY:
ModifyContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case PRINT:
PrintContact(&con);
break;
case SORT:
SortContact(&con);
break;
default:
printf("输入错误!\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
2.contact.h🐬
函数,头文件及类型声明
#pragma once
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
//#define MAX 1000
#define NAME_MAX 20
#define SEX_MAX 10
#define TELE_MAX 12
#define ADDE_MAX 20
#define DEFAULT_SZ 3
//类型定义
enum
{
EXIT,
ADD,
DEL,
MODIFY,
SEARCH,
SORT,
PRINT
};
//类型声明
//定义一个联系人的信息
typedef struct PeoInfo
{
char name[NAME_MAX];
int age;
char sex[SEX_MAX];
char tele[TELE_MAX];
char addr[ADDE_MAX];
}PeoInfo;
//动态版本
typedef struct contact
{
PeoInfo* data;
int sz;//通讯录中已经保存的信息个数
int capacity;//记录可以存储的空间的最大容量
}contact;
//函数声明
//初始化通讯录
void InitContact(contact* pc);
//销毁通讯录
void DistroyContact(contact* pc);
//增加联系人
void AddContact(contact* pc);
//打印通讯录
void PrintContact(contact* pc);
//删除指定联系人
void DelContact(contact* pc);
//修改联系人信息
void ModifyContact(contact* pc);
//查找联系人
void SearchContact(contact* pc);
//排序
void SortContact(contact* pc);
//保存联系人
void SaveContact(const contact* pc);
3.contact.c🐬
通讯录功能实现
#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"
void CheakCapacity(contact* pc);
//加载文件
void LoadContact(contact* pc)
{
assert(pc);
//打开文件
FILE* pf = fopen("D:\\code\\test-a\\text_8_21_contact_text_version\\contact.txt", "rb");
//判空
if (pf == NULL)
{
perror("LoadContact::fopen");
return;
}
//读文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
CheakCapacity(pc);
pc->data[pc->sz] = tmp;
pc ->sz++;
}
//关闭文件
fclose(pf);
pf = NULL;
}
//初始化通讯录
//文件版本
void InitContact(contact* pc)
{
assert(pc != NULL);
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
pc->data = (PeoInfo*)malloc(pc->capacity * sizeof(PeoInfo));
if (pc->data == NULL)
{
perror("InitContact malloc");
return;//同主函数中return 1;一样,不一样的地方在于,主函数中的返回值是int型,而这里是void型
}
memset(pc->data, 0, (pc->capacity * sizeof(PeoInfo)));
LoadContact(pc);
}
//销毁通讯录
void DistroyContact(contact* pc)
{
assert(pc);
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->capacity = 0;
printf("销毁成功\n");
}
//增加联系人
void CheakCapacity(contact* pc)
{
assert(pc);
if (pc->sz == pc->capacity)
{
PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
if (tmp == NULL)//增容失败
{
perror("CheakCapacoty::realloc");
return;
}
else//增容成功
{
pc->data = tmp;
}
pc->capacity += 2;
//printf("增容成功\n");
}
}
void AddContact(contact* pc)
{
assert(pc);
//联系人存满了,考虑增容,每次增加两个联系人的存储空间
CheakCapacity(pc);
//录入信息
printf("请输入姓名:");
scanf("%s", pc->data[pc->sz].name);
printf("请输入年龄:");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入电话:");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("录入成功\n");
}
//打印通讯录
void PrintContact(contact* pc)
{
assert(pc);
int i = 0;
printf("%-10s %-5s %-10s %-12s %-20s\n", "姓名", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++)
{
printf("%-10s %-5d %-10s %-12s %-20s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex,
pc->data[i].tele, pc->data[i].addr);
}
}
//寻找联系人
int FindByName(contact* pc, char name[])
{
assert(pc);
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(name, pc->data[i].name) == 0)
{
return i;//找到了,返回该联系人下标
}
}
return -1;//找不到
}
//删除指定联系人
void DelContact(contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入你要删除的联系人姓名:");
scanf("%s", name);
//1.找到要删除的联系人
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("通讯录中没有该联系人\n");
return;
}
//2.找到后删除联系人
/*int j = 0;
for (j = ret; j < pc->sz - 1; j++)
{
pc->data[j] = pc->data[j + 1];
}*/
memmove(&(pc->data[ret]), &(pc->data[ret + 1]), (ret + 1) * sizeof(pc->data[0]));
printf("该联系人已删除\n");
pc->sz--;
}
//修改联系人信息
void ModifyContact(contact* pc)
{
assert(pc);
//1.找到要修改信息的联系人
char name[NAME_MAX];
printf("请输入所要修改信息的联系人姓名:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (-1 == ret)
{
printf("通讯录中没有该联系人\n");
return;
}
//2.修改联系人信息
printf("请输入需要修改的信息\n");
printf("请输入姓名:");
scanf("%s", pc->data[ret].name);
printf("请输入年龄:");
scanf("%d", &(pc->data[ret].age));
printf("请输入性别:");
scanf("%s", pc->data[ret].sex);
printf("请输入电话:");
scanf("%s", pc->data[ret].tele);
printf("请输入地址:");
scanf("%s", pc->data[ret].addr);
printf("信息修改成功\n");
}
//查找联系人
void SearchContact(contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要查找的联系人姓名:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (-1 == ret)
{
printf("通讯录中没有该联系人\n");
return;
}
printf("%-10s %-5s %-10s %-12s %-20s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-10s %-5d %-10s %-12s %-20s\n", pc->data[ret].name, pc->data[ret].age, pc->data[ret].sex,
pc->data[ret].tele, pc->data[ret].addr);
}
int compare_name(const void* e1, const void* e2)
{
//return strcmp( ((contact*)e1)->data->name, ((contact*)e2)->data->name);
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
//int cmp_name(const void* e1, const void* e2)
//{
// return strcmp( ((struct Stu*)e1)->name,((struct Stu*)e2)->name);
//}
//升序排序
void SortContact(contact* pc)
{
assert(pc);
qsort(pc->data, pc->sz, sizeof(pc->data[0]), compare_name);
}
void SaveContact(const contact* pc)
{
assert(pc);
//打开文件
FILE* pf = fopen("D:\\code\\test-a\\text_8_21_contact_text_version\\contact.txt", "wb");
if (pf == NULL)
{
perror("SaveContact::fopen");
return;
}
//写文件--输出操作
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
}
本篇以通讯录文件版的实现为主题的博客到此就要结束了,希望本篇博客可以帮助到各位小伙伴,博主码字不易,如果本篇博客对你有所帮助,还望各位小伙伴点赞👍,收藏⭐+关注,感谢各位的支持!如果有什么不明白的地方或是另有其他的高见,也可以在评论区留言,博主也会及时回复或进行更改,欢迎指正,谢谢!