【问题详述】
实验室的仪器由专人维护管理以保证实验时能够正常使用。仪器的管理实行使用登记制度,仪器使用(借出)达到一定次数,管理人员就要对仪器进行调校、维护、保养或者报废处理。请编写应用程序,实现实验仪器的借、还管理;统计仪器的使用次数,提醒管理人员进行仪器的维护保养。
实验室的仪器分成三个类别,每类仪器需要进行维护的频率与正常使用的年限分别遵循不同的时间规定。A类仪器每使用(借出)2次就需要调校维护;使用满5年就要报废处理。B类仪器至多使用(借出)20次后,需要调校维护;使用期限为15年。C类仪器至多使用100次后要进行维护;使用期限为20年。仪器的维护可以提前进行,例如某种C类仪器使用95次就进行维护。
每台仪器都有详细的记录资料,登记了该仪器的编号、名称、购置日期、类别、状态、使用次数。仪器的状态有“可借、已借出、待维护、报废”4种;使用次数是指自最近的一次维护后,已经借出(使用)的次数。应当报废的仪器或者该维护而尚未维护时(其状态是“待维护”),不允许借出。
【程序要求】
实验仪器管理系统代替手工操作,实现仪器的借、还管理:自动统计仪器的使用次数,提醒管理人员进行仪器的维护保养。其主要功能是:
运行系统时,输入当商日期,读取实验仪器登记数据文件,处理数据文件内容,自动查找达到报废时间的仪器,将其状态修改为报废,在用户主菜单中提供借出、归还、使用次数统计、维护登记、仪器报废预警等菜单选项;系统运行结束时,挑出当天已报废仪器的数据添加保存到报废仪器文件末尾,将正常使用的实验仪器登记数据重新保存到实验仪器登记数据文件中。
主菜单功能具体为:
仪器借出:根据输入仪器的名称,如果该仪器为“可借”状态,则办理借出(修改状态、使用次数);如果不可借,则显示相应的状态提示。
仪器归还:输入仪器的编号、名称办理归还手续(修改状态、使用次数);如果该仪器达到应该维护的程度,则显示需要维护的提醒。
使用次数统计:对于B类和C类仪器,显示最多使用n次就需要维护的仪器编号和名称。其中n为输入的一个整数。例如,输入3,统计使用次数达到或超过17次的B类和97次的C类仪器的编号和名称。该操作是对仪器进行维护的一个提前预计,可以在实验仪器借出不频繁时,提前维护仪器,调节工作的忙与闲的节奏。
维护登记:该操作是在对仪器进行了维护之后进行的。输入仪器的编号、名称,将状态修改为可借,将使用次数修改为0。
仪器报废预警;显示距离报废日期不超过一个月的仪器的编号及名称。
只能使用c++语言完成 要符合问题条件 最好说明总体设计,详细设计以讲清流程和算法
可以私下聊!
包含程序源码以及思维导图的,请看:
1.
https://www.ssfiction.com/c-cyuyankaifa/1238158.html
2.
https://www.ssfiction.com/c-cyuyankaifa/1238153.html
可以在标准的进销存系统上稍加修改即可,建议连接数据库进行处理,方便快捷安全
C/C++ 实验设备管理系统
https://blog.csdn.net/az9996/article/details/85331953
这是相对来讲较为完整的代码参考
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<Windows.h>//增加Windows头文件为使界面更加美观
const char* file_name = "record2.txt";
//实验设备结构体信息
struct Information{
long int id;//仪器id
char name[200];//姓名
char type[200];//类名
double price;//价格
unsigned int number;
char company[200];//出产公司
char comment[400];//备注
};
//链表结构体——将设备放入链表(此处增添文件读取功能:将文件中信息输出到链表)
struct Info_list{
struct Information*node;
struct Info_list *next;//链接下处结点
};
//由于含有多个功能简化书写,使用枚举enum
enum InfoType{
NONE = 0,
ID =1,
NAME = 2,
TYPE = 3,
PRICE = 4,
NUMBER = 5,
COMPANY = 6,
COMMENT = 7,
ALL = 8
};
char* trimmed(char * c)
{
char* end = NULL;
if (NULL == c)
return c;
end = c + strlen(c) - 1;
while (*c && (*c == ' ' || *c == '\t' || *c == '\n')) {
c++;
}
while (*end && end >= c && end >= c && (*end == ' ' || *end == '\t' || *end == '\n')) {
*end-- = '\0';
}
return c;
}
void show_help()
{
printf("\n\n");
printf("\t\t\t YouYoung---实验室管理系统V7.28.1\n");
printf("\t\t\t*********************************************************************************\n");
// printf("\t\t\t\t\t\t-----ENTER THE FIRST CHAR-----\n");
printf("\t\t\t* -----ENTER THE FIRST CHAR----- *\n");
printf("\t\t\t* *\n");
printf("\t\t\t*\t 1. q\t\t退出\t\t\t\t\t*\n");
printf("\t\t\t*\t 2. i\t\t增添设备信息\t\t\t\t*\n");
printf("\t\t\t*\t 3. d\t\t删除设备\t\t\t\t*\n");
printf("\t\t\t*\t 4. m\t\t设备排序\t\t\t\t*\n");
printf("\t\t\t*\t 5. l\t\t按顺序输出\t\t\t\t*\n");
printf("\t\t\t*\t 6. lid\t\t按id输出\t\t\t\t*\n");
printf("\t\t\t*\t 7. ltype\t按type输出\t\t\t\t*\n");
printf("\t\t\t*\t 8. lprice\t按price输出\t\t\t\t*\n");
printf("\t\t\t*\t 9. lnumber\t按number输出\t\t\t\t*\n");
printf("\t\t\t*\t 10. lcompany\t按company输出\t\t\t\t*\n");
printf("\t\t\t*\t 11. lcomment\t按comment输出\t\t\t\t*\n");
printf("\t\t\t* *\n");
printf("\t\t\t*********************************************************************************\n");
}
//以下属于链表的文件操作 包含链表在文件中的保存和删除
//向文件末尾添加结构体信息
int write_info_to_file(struct Information* info){
FILE *file;
file = fopen(file_name,"a");
if(!file){
printf("\nError! Cannot Open File %s\n", file_name);
return 0;
}
fputs("\n",file);
fprintf(file, "%ld\n", info->id);
fprintf(file, "%s\n", info->name);
fprintf(file, "%s\n", info->type);
fprintf(file, "%f\n", info->price);
fprintf(file, "%d\n", info->number);
fprintf(file, "%s\n", info->company);
fprintf(file, "%s\n", info->comment);
fputs("\n",file);
fclose(file);
return 1;
}
//链表的文件保存 -wirte操作
int write_list_to_file(struct Info_list* list_head)
{
struct Info_list *list_iter = list_head->next;//类似于迭代器iterator
//文件内容的删除操作,先将原文件中内容删除再重新保存,类似于Python的w操作
FILE *file;
file = fopen(file_name,"w");
if(!file){
printf("\nError! Cannot Open File %s\n", file_name);
return 0;
}
fclose(file);
while(list_iter && list_iter->node){
write_info_to_file(list_iter->node);
list_iter = list_iter->next;
}
return 1;
}
//去除两边空白
//文件的读取操作,保存到链表
struct Info_list* read_from_file(struct Info_list* list_first)
{
FILE *file;
struct Information *info = NULL;
struct Info_list *list_next = NULL;
struct Info_list *list_temp = NULL;
char str[400];
int flag = 0;
file = fopen(file_name,"r");
if(!file){
printf("\nError! Cannot Open File %s\n",file_name);
return NULL;
}
list_temp = list_first;
while((fgets(str, 400, file))!=NULL)//从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内
{
if(strlen(str)>1){
++flag;
}
else{
continue;
}
if(flag > 7){
flag = 1;
}
switch(flag)
{
case 0:
break;
case 1:
list_next = (struct Info_list*)malloc(sizeof(struct Info_list));
info = (struct Information*)malloc(sizeof(struct Information));
if(NULL == info||NULL == list_first){
printf("\nError! Cannot malloc memory\n");
return NULL;
}
list_next->next = NULL;
list_next->node = NULL;
list_temp->node = info;
list_temp->next = list_next;
list_temp = list_next;
info->id = atol(str);//字符串转换成长整型
break;
case 2:
strncpy(info->name,str,200);//char*strncpy(char*dest,const char*src,size_t n)把 src 所指向的字符串复制到dest,最多复制 n 个字符。
trimmed(info->name);//trim()函数用于去除字符串两端的空白字符
break;
case 3:
strncpy(info->type, str,200);
trimmed(info->type);
break;
case 4:
info->price = atof(str);
break;//将字符串转成float型
case 5:
info->number = atoi(str);//转成int型
break;
case 6:
strncpy(info->company,str,200);
trimmed(info->company);
break;
case 7:
strncpy(info->comment,str,400);
trimmed(info->comment);
break;
default:
break;
}
}
fclose(file);
return list_first;
}
//插入数据命令
void insert_information(struct Info_list *list_head)
{
char id_char[200];
char price_char[200];
char number_char[200];
struct Information *info = NULL;
struct Info_list* list_next = NULL;
struct Info_list* list_temp = NULL;
/*
if (!list_head) {
list_head = (struct Info_list*)malloc(sizeof(struct Info_list));
if (NULL == list_head) {
printf("\nError! Cannot malloc memory\n");
return ;
}
list_head->next = NULL;
list_head->node = NULL;
}
*/
list_temp = list_head->next;
while(list_temp != NULL && list_temp->node != NULL){
list_temp = list_temp->next;
}
list_next = (struct Info_list*)malloc(sizeof(struct Info_list));
info = (struct Information*)malloc(sizeof(struct Information));
if(NULL == info || NULL == list_next){
printf("\nError! Cannot malloc memory\n");
return;
}
list_next->next = NULL;
list_next->node = NULL;
list_temp->next=list_next;
list_temp->node = info;
printf("\nNow Insert a New Data of Devcie.\n");
printf("Please Enter ID, only numeral support, for example: \"123456\"\n");
printf(">>>> ");
fgets(id_char,200,stdin);
info->id = atol(id_char);
printf("You Typed %ld\n",info->id);
printf("Please Enter Name, for example: \"Kobe Bryant\"\n");
printf(">>>> ");
fgets(info->name, 200, stdin);
trimmed(info->name);
printf("You Typed %s\n",info->name);
printf("Please Enter Type, for example: \"A\"\n");
printf(">>>> ");
fgets(info->type, 200, stdin);
trimmed(info->type);
printf("You Typed %s\n",info->type);
printf("Please Enter Price, only numeral support, example: \"123.456\"\n");
printf(">>>>");
fgets(price_char,200,stdin);
info->price = atof(price_char);
printf("You Typed %f\n", info->price);
printf("Please Enter Number, only numeral support, example: \"543210\"\n");
printf(">>>>");
fgets(number_char,200,stdin);
info->number = atof(number_char);
printf("You Typed %u\n", info->number);
printf("Please Enter Company, for example: \"Red Had\"\n");
printf(">>>>");
fgets(info->company,200,stdin);
trimmed(info->company);
printf("You Typed %s\n",info->company);
printf("Please Enter Comment,for example: \"This is Comment\"\n");
printf(">>>> ");
fgets(info->comment, 400,stdin);
printf("You Typed %s\n",info->comment);
trimmed(info->comment);
write_info_to_file(info);
}
void list_information(struct Info_list *list_head)
{
struct Info_list *list_temp = list_head->next;
struct Information *temp = NULL;
while(list_temp !=NULL && list_temp->node !=NULL){
temp = list_temp->node;
printf("\n ID: %ld", temp->id);
printf("\t Name: %s", temp->name);
printf("\t Type %s",temp->type);
printf("\t Price: %.2f", temp->price);
printf("\t Number: %u",temp->number);
printf("\t Company: %s", temp->company);
printf("\t Comment: %s\n",temp->comment);
list_temp = list_temp->next;
}
}
//按相应的排序方法显示数据
void list_information_order(struct Info_list *list_head,const char* which)
{
struct Info_list *list_temp = list_head->next;
struct Info_list *swap_temp = NULL;
struct SortHelp
{
struct Info_list* node;
int flag;
struct SortHelp *next;
};
struct SortHelp *sort_head = NULL;
struct SortHelp *sort_iter = NULL;
struct SortHelp *sort_one = NULL;
struct SortHelp *sort_min = NULL;
struct SortHelp *sort_temp = NULL;
struct Information * info_temp = NULL;
int sort_num = 0;
int i = 0;
int j = 0;
//新建链表保存原链表的信息
//copy Info_list to SortHelp
while(list_temp&&list_temp->node!=NULL){
sort_one = (struct SortHelp*)malloc(sizeof(struct SortHelp));
if(NULL == sort_one){
printf("\nError! Cannot malloc memory\n");
sort_iter = sort_head;
while(sort_iter) {
sort_temp = sort_iter;
sort_iter = sort_iter->next;
free(sort_temp);
}
return;
}
sort_one->node = list_temp;
sort_one->flag = 0;
sort_one->next = NULL;
if(NULL == sort_head){
sort_head = sort_one;
sort_iter = sort_head;
} else{
sort_iter->next = sort_one;
sort_iter = sort_iter->next;
}
list_temp = list_temp->next;
++sort_num;
}
//冒泡排序
for(i = sort_num; i >0; --i){
sort_iter = sort_head;
//跳过之前已经排好序的结点
for(j=0;j<sort_num - i;++j){
sort_iter = sort_iter->next;
}
sort_temp = sort_iter;
//找到数值最小的结点
sort_min = sort_iter;
while(sort_iter) {
if (0 == sort_iter->flag) {
if (0 == strcmp(which, "id")) {
if (sort_iter->node->node->id < sort_min->node->node->id)
sort_min = sort_iter;
} else if (0 == strcmp(which, "name")) {
if (strcmp(sort_iter->node->node->name , sort_min->node->node->name) < 0)
sort_min = sort_iter;
} else if (0 == strcmp(which, "price")) {
if (sort_iter->node->node->price < sort_min->node->node->price)
sort_min = sort_iter;
} else if (0 == strcmp(which, "type")) {
if (strcmp(sort_iter->node->node->type , sort_min->node->node->type) < 0)
sort_min = sort_iter;
} else if (0 == strcmp(which, "company")) {
if (strcmp(sort_iter->node->node->company , sort_min->node->node->company) < 0)
sort_min = sort_iter;
} else if (0 == strcmp(which, "comment")) {
if (strcmp(sort_iter->node->node->comment , sort_min->node->node->comment) < 0)
sort_min = sort_iter;
} else if (0 == strcmp(which, "number")) {
if (sort_iter->node->node->number < sort_min->node->node->number)
sort_min = sort_iter;
}
}
sort_iter = sort_iter->next;
}
// (sort_min->flag)++;
swap_temp = sort_min->node;
sort_min->node = sort_temp->node;
sort_temp->node = swap_temp;
}
//将排序后的链表中得数据输出,并释放内存,防止内存泄漏
sort_iter = sort_head;
while(sort_iter){
info_temp = sort_iter->node->node;
printf("\n id: %ld",info_temp->id);
printf("\t name: %s",info_temp->name);
printf("\t type: %s", info_temp->type);
printf("\t price: %.2f", info_temp->price);
printf("\t Number: %u",info_temp->number);
printf("\n Company: %s",info_temp->company);
printf(" Comment: %s\n",info_temp->comment);
sort_temp = sort_iter;
sort_iter = sort_iter->next;
free(sort_temp);
}
}
//从链表中删除指定ID记录
void delete_id_in_list(struct Info_list *list_head, long int id)
{
struct Info_list * list_iter = list_head->next;
struct Info_list * list_pre = list_head;
struct Information * info = NULL;
while(list_iter && list_iter->node){
if(info->id == id){
free(info);
list_iter->node=NULL;
//释放当前内存,指针回退
list_pre->next = list_iter->next;
free(list_iter);
list_iter=list_pre;
}
list_pre= list_iter;//初始化
list_iter = list_iter->next;
}
}
//删除指定记录
void delete_information(struct Info_list * list_head)
{
char condition[100];
char * trimmed_condition = NULL;
long int id;
printf("Enter ID which you want delete. \n>>>> ");
fgets(condition,100,stdin);
trimmed_condition = trimmed(condition);
id = atol(trimmed_condition);
delete_id_in_list(list_head,id);
write_list_to_file(list_head);//保存
}
//查找数据
void search_information(struct Info_list * list_head,enum InfoType type)
{
enum HaveInfo{
HAVE = 1,
UNHAVE = 2
};
struct Info_list * list_iter = list_head->next;
char condition[100];
char char_id[100];
char char_price[100];
char char_number[100];
char * trimmed_condition = NULL;
struct Information * info = NULL;
enum HaveInfo is_have = UNHAVE;
if(ALL == type){
printf("Enter your condition below. (support fuzzy search) \n>>>>");
}else{
printf("Enter your condition below. \n>>>>");
}
fgets(condition, 100,stdin);
trimmed_condition = trimmed(condition);
while(list_iter && list_iter->node){
info = list_iter->node;
is_have =UNHAVE;
sprintf(char_id,"%ld",info->id);
sprintf(char_price, "%f",info->price);
sprintf(char_number,"%u",info->number);
switch(type){
case ALL:
if(strstr(char_id,trimmed_condition)
|| strstr(info->name, trimmed_condition)
|| strstr(info->type, trimmed_condition)
|| strstr(char_price, trimmed_condition)
|| strstr(info->company,trimmed_condition)
|| strstr(info->comment, trimmed_condition)
)
is_have = HAVE;
break;
case ID:
if(strstr(char_id,trimmed_condition)){
is_have = HAVE;
}
break;
case NAME:
if(strstr(info->name,trimmed_condition)){
is_have = HAVE;
}
break;
case TYPE:
if(strstr(info->type,trimmed_condition)){
is_have = HAVE;
}
break;
case PRICE:
if(strstr(char_price, trimmed_condition)){
is_have = HAVE;
}
break;
case COMPANY:
if(strstr(info->company, trimmed_condition)){
is_have = HAVE;
}
break;
case COMMENT:
if(strstr(info->comment, trimmed_condition)){
is_have = HAVE;
}
break;
dafault:
is_have = UNHAVE;
break;
}
if(HAVE == is_have){
printf("\n ID: %ld", info->id);
printf("\t Name: %s", info->name);
printf("\t Type %s", info->type);
printf("\t Price: %.2f", info->price);
printf("\t Number: %u", info->number);
printf("\t Company: %s", info->company);
printf("\t Comment: %s\n", info->comment);
}
list_iter = list_iter->next;
}
}
void modify_information(struct Info_list *list_head)
{
struct Info_list * list_iter = list_head->next;
char condition[100];
char * trimmed_condition = NULL;
long int id;
struct Information * info_temp = NULL;
struct Information * info = NULL;
char id_char[200];
char price_char[200];
char number_char[200];
printf("Enter ID which you want modify. \n>>>> ");
fgets(condition, 100, stdin);
trimmed_condition = trimmed(condition);
id = atol(trimmed_condition);
while(list_iter && list_iter->node){
if(list_iter->node->id == id){
break;
}
list_iter = list_iter->next;
}
info_temp = list_iter->node;
if(!info_temp){
return;
}
printf("\n id: %ld", info_temp->id);
printf("\t name: %s", info_temp->name);
printf("\t type: %s", info_temp->type);
printf("\t price: %.2f", info_temp->price);
printf("\t Number: %u", info_temp->number);
printf("\n Company: %s", info_temp->company);
printf(" Comment: %s\n", info_temp->comment);
info = info_temp;
printf("\nNow Enter New Data of This Information.\n");
printf("Plase Enter ID, only numeral support, for example: \"123456\"\n");
printf(">>>> ");
fgets(id_char, 200, stdin);
info->id = atol(id_char);
printf("You Typed %ld\n", info->id);
printf("Plase Enter Name, for example: \"Kobe Bryant\"\n");
printf(">>>> ");
fgets(info->name, 200, stdin);
trimmed(info->name);
printf("You Typed %s\n", info->name);
printf("Plase Enter Type, for example: \"A\"\n");
printf(">>>> ");
fgets(info->type, 200, stdin);
trimmed(info->type);
printf("You Typed %s\n", info->type);
printf("Plase Enter Price, only numeral support, example: \"123.456\"\n");
printf(">>>> ");
fgets(price_char, 200, stdin);
info->price = atof(price_char);
printf("You Typed %f\n", info->price);
printf("Plase Enter Number, only numeral support, example: \"543210\"\n");
printf(">>>> ");
fgets(number_char, 200, stdin);
info->number = atoi(number_char);
printf("You Typed %u\n", info->number);
printf("Plase Enter Company, for example: \"Red Had\"\n");
printf(">>>> ");
fgets(info->company, 200, stdin);
trimmed(info->company);
printf("You Typed %s\n", info->company);
printf("Plase Enter Comment, for example: \"This is Comment\"\n");
printf(">>>> ");
fgets(info->comment, 400, stdin);
printf("You Typed %s\n", info->comment);
trimmed(info->comment);
//保存文件
write_list_to_file(list_head);
}
void select_operator(char operator[100],struct Info_list * list_head)
{
if( 0== strcmp(operator,"help")){
system("cls");
show_help();
}
/* 插入新数据 */
else if (0 == strcmp(operator, "i"))
insert_information(list_head);
/* 显示数据,按插入时间输出,或按某个字段排序后输出 */
else if (0 == strcmp(operator, "l"))
list_information(list_head);
else if (0 == strcmp(operator, "lid"))
list_information_order(list_head, "id");
else if (0 == strcmp(operator, "lname"))
list_information_order(list_head, "name");
else if (0 == strcmp(operator, "lprice"))
list_information_order(list_head, "price");
else if (0 == strcmp(operator, "lnumber"))
list_information_order(list_head, "number");
else if (0 == strcmp(operator, "ltype"))
list_information_order(list_head, "type");
else if (0 == strcmp(operator, "lcompany"))
list_information_order(list_head, "company");
else if (0 == strcmp(operator, "lcomment"))
list_information_order(list_head, "comment");
/* 删除 */
else if (0 == strcmp(operator, "d"))
delete_information(list_head);
/* 查找数据,全文模糊查找或按字段模糊查找 */
else if (0 == strcmp(operator, "s"))
search_information(list_head , ALL);
else if (0 == strcmp(operator, "sid"))
search_information(list_head, ID);
else if (0 == strcmp(operator, "sname"))
search_information(list_head, NAME);
else if (0 == strcmp(operator, "sprice"))
search_information(list_head, PRICE);
else if (0 == strcmp(operator, "stype"))
search_information(list_head, TYPE);
else if (0 == strcmp(operator, "snumber"))
search_information(list_head, NUMBER);
else if (0 == strcmp(operator, "scompany"))
search_information(list_head, COMPANY);
else if (0 == strcmp(operator, "scomment"))
search_information(list_head, COMMENT);
/* 修改 */
else if (0 == strcmp(operator, "m"))
modify_information(list_head);
}
void freeMem(struct Info_list* list_head)
{
struct Info_list * list_temp = list_head;
struct Info_list * list_next = NULL;
while(list_temp){
if(list_temp->node){
free(list_temp->node);
}
if(list_temp->next){
list_next = list_temp->next;
free(list_temp);
list_temp = list_next;
}else{
break;
}
}
}
int main(){
system("title YouYoung--实验室管理系统V7.28.1");
system("color 3");
show_help();
char operator[100];
char * trimmed_operator = NULL;
struct Info_list * list_first = NULL;
/* 给链表的头结点分配内存,并初始化数据为NULL */
struct Info_list * list_head = (struct Info_list*)malloc(sizeof(struct Info_list));
if (NULL == list_head) {
printf("\nError! Cannot malloc memory\n");
return 0;
}
list_head->next = NULL;
list_head->node = NULL;
/* 给链表的第一个结点分配内存,并初始化数据为NULL */
list_first = (struct Info_list*)malloc(sizeof(struct Info_list));
if (NULL == list_first) {
printf("\nError! Cannot malloc memory\n");
return 0;
}
list_first->next = NULL;
list_first->node = NULL;
list_head->next = list_first;
/* 从保存的文件读取数据,并将数据保存到链表当中 */
read_from_file(list_first);
/* 循环执行用户输入的指令,直到输入退出指令 "q" */
do{
printf("\n________________________________________________________________________\n");
printf("Plese Select Your Operator! ");
printf("Type \"help\", for More Information.\n");
printf("------------------------------------------------------------------------\n");
printf(">>>> ");
fgets(operator, 100, stdin);
trimmed_operator = trimmed(operator);
select_operator(trimmed_operator, list_head);
} while (strcmp(operator, "q") != 0);
/* 释放链表所申请的内存,防止内存泄露 */
freeMem(list_head);
return 0;
}
参考自链接:https://blog.csdn.net/MDeleter/article/details/121256408
可以参考一下:
https://blog.csdn.net/az9996/article/details/85331953