大家好,欢迎来到IT知识分享网。
用C语言实现一个简单通讯录功能:
- 命令端输入对应指令可执行相应操作
- 可以实现增、删、查、改、排序、显示、一键清除等功能
- 每次输入的信息保存到txt文件中
- 每次打开通讯录自动加载已有的txt文件中的信息
本文主要是针对指针、动态内存管理、以及文件读写操作的综合练习。
目录
DEL ALL一键删除,SaveCon保存通讯录,Distory释放内存:
声明结构体
//通讯录结构体 typedef struct Date { char name[20]; char sex[5]; int age; char tel[20]; char address[30]; }Date; typedef struct Con { Date* p; int count; int capacity; }Con;
其中:
- 结构体Date是用来存储学生信息的
- 结构体Con嵌套了一个Date结构体
- Con中count用来计数,记录当前目录存储学生的个数
- Con中capacity用来记录当前结构体内存大小(内存不够时申请扩容)
构建程序框架
声明完结构体后,先构建程序框架
//Con.h #include<stdio.h> //main.c void menu() { printf("\n"); printf(" 1.ADD 2.DEL \n"); printf(" 3.SHOW 4.MODIFY \n"); printf(" 5.CHECK 6.DEL ALL \n"); printf(" 7.SORT 0.EXIT \n"); printf("\n"); } int main() { int input = 0; Con arr; do { menu(); scanf("%d", &input); } while (input); return 0; }
其中:
- 创建一个菜单栏
- 创建一个结构体变量arr
- do while循环至少执行一次,当intput=0时循环结束
完善框架:
只针对于上述要求,我们需要创建以下函数:
InitCon:初始化通讯录,开辟内存
LoadCon:加载txt中的信息到内存中
ADD,DEL,CHECK,MODIFY:对应增、删、查、改
SHOW:显示全部信息
SORT:对某一项进行排序
DEL ALL:一键清除所有信息
EXIT:退出并保存
Distory:释放内存
完善后的主函数如下:
int main() { int input = 0; Con arr; InitCon(&arr); LoadCon(&arr); void (*pf[])(Con*) = {0,add,del,show,modify,check,del_all,sort}; do { menu(); scanf("%d", &input); if (input == 0) { SaveCon(&arr); Distory(&arr); printf("退出!\n"); } else if (input > 0 && input < 8) { pf[input](&arr); } else printf("输入错误,请重新输入!\n"); } while (input); return 0; }
其中:
- void (*pf[])(Con*)为函数指针数组,其数组中存储的类型void (*)(Con*)的函数指针,关于函数指针以及函数指针数组的知识,在回调函数中对其进行了详细讲解。
- 通过调用函数指针数组的下标,从而实现调用程序的不同功能。
- 当input=0时,需要先将内存中的信息保存到txt文件中,然后再释放内存
函数创建
为了日后的维护与管理,所有函数均存入到Con.c中,与主函数分开。
InitCon初始化函数
在程序开始时开辟指定大小的内存,在动态规划中有malloc和calloc两种方式开辟内存,这里使用calloc的形式进行开辟。
//Con.h #include<assert.h> #include<string.h> #include<errno.h> #define INIT_NUM 3 //Con.c void InitCon(Con* pc) { assert(pc); pc->p = (Date*)calloc(INIT_NUM, sizeof(Date)); if (pc->p == NULL) { printf("InitCon::%s", strerror(errno)); return; } pc->count = 0; pc->capacity = INIT_NUM; }
其中:
- 传入参数为结构体指针
- 需要判断结构体指针不能为空,assert需要包含头文件<assert.h>
- calloc使用方法:void* calloc (size_t num, size_t size);num为指定个数,size为指定大小,返回值为void*类型的指针
- 开辟完内存后,最好再对其进行检查,是否为空。
- strerror(arrno)为打印错误信息,strerror需要包含头文件<string.h>,arrno需要包含头文件<errno.h>
- 开辟内存时,同时初始化count=0,capcity等于calloc开辟的个数
LoadCon加载信息函数
这里用到了文件指针,对文件进行操作,大致思想为:
- 如果文件中没有信息则退出
- 如果文件中存有信息,按行读取
- 每读取一行,对应的count需要+1
- 如果读取的过程中,初始开辟的内存不够了,需要对内存进行加载
void LoadCon(Con* pc) { assert(pc); FILE* pfopen = fopen("con.txt", "r"); if (pfopen == NULL) { perror("LoadCon"); return; } Date tmp = { 0 }; while (fread(&tmp, sizeof(Date), 1, pfopen)==1) { AddCapacity(pc); pc->p[pc->count] = tmp; pc->count++; } printf("加载成功!\n"); fclose(pfopen); pfopen = NULL; }
其中:
- FIEL*表示为文件指针,fopen为打开文件操作,‘r’表示以只读的方式打开
- fread为二进制读取数据(前提是文件中的数据是以二进制存储的)
- fread的使用方法:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
- ptr为读取输出地址,size为一次读取大小,count为读取个数,stream为文件指针,当fread读取成功时,会返回读取个数
- AddCapacity为自定义函数,检查内存是否够用,不足时扩容
- 当读取完文件后,记得关闭文件
AddCapacity扩容函数
扩容函数需要完成两个任务:
1、检查内存是否足够
2、内存不足时进行扩容,并将capacity中存储的信息更新
void AddCapacity(Con* pc) { assert(pc); if (pc->count == pc->capacity) { Date* str = (Date*)realloc(pc->p, sizeof(Date)*(pc->capacity+ ADD_NUM)); if (pc->p == NULL) { printf("AddCapacity::%s", strerror(errno)); return; } pc->p = str; pc->capacity += ADD_NUM; printf("增容成功\n"); } }
其中:
- 当count中的个数等于capacity容量的个数时,便需要进行扩容
- realloc函数的用法如下:void* realloc (void* ptr, size_t size);ptr为需要扩容的地址,size为扩容后新的空间总大小
ADD增加信息函数
ADD函数比较简单,只需要在每次增加前对其内存进行检查,然后格式化输入信息即可,每次增加一个联系人的信息后,其所对应的count计数也需要进行+1。
void add(Con* pc) { assert(pc); AddCapacity(pc); printf("请输入名字:>\n"); scanf("%s", pc->p[pc->count].name); printf("请输入性别:>\n"); scanf("%s", pc->p[pc->count].sex); printf("请输入年龄:>\n"); scanf("%d", &(pc->p[pc->count].age)); printf("请输入电话:>\n"); scanf("%s", pc->p[pc->count].tel); printf("请输入地址:>\n"); scanf("%s", pc->p[pc->count].address); printf("输入成功\n"); pc->count++; }
DEL,CHECK,MODIFY删查改函数
以上三个函数需要用到查找函数,先假设所有人的名字不重复,根据人的姓名进行查找,返回其所属的count值。
int FindByName(const Con* pc) { assert(pc); int i = 0; char input[20] = {0}; printf("请输入名字\n"); scanf("%s", input); for (i = 0; i <pc->count; i++) { if (strcmp(input, pc->p[i].name) == 0) { return i; } } return -1; }
其中:
- 传参时const保护结构体指针pc,不能通过解引用的方式来更改内容
- 比较两个字符串用strcmp,需要包含头文件<string,h>,使用方法如下:int strcmp ( const char * str1, const char * str2 );str1和str2为相比较的两个字符串,返回值分为>0,<0 =0,相等时返回0
当查找函数完成后,剩余的删查改函数就简单许多,需要注意的是:删除函数需要更改count,并且使所有人信息往前挪一位。
删除函数
//删除 void del(Con* pc) { assert(pc); if (pc->count == 0) { printf("联系人为空\n"); return; } int i = FindByName(pc); if (i != -1) { int j = 0; for (j = i; j < pc->count; j++) { pc->p[j] = pc->p[j + 1]; } pc->count--; printf("删除成功!\n"); } else printf("没有找到!\n"); }
查找并显示函数
void check(const Con* pc) { assert(pc); int i = FindByName(pc); if (i != -1 ) { printf("%-20s%-10s%-10s%-20s%-30s\n", "姓名", "性别", "年龄", "电话", "地址"); printf("%-20s%-10s%-10d%-20s%-30s\n", pc->p[i].name, pc->p[i].sex, pc->p[i].age, pc->p[i].tel, pc->p[i].address); } else printf("没有找到!\n"); }
修改函数
void modify(Con* pc) { assert(pc); if (pc->count == 0) { printf("联系人为空\n"); return; } int i = FindByName(pc); if (i != -1) { printf("请输入名字:>\n"); scanf("%s", pc->p[i].name); printf("请输入性别:>\n"); scanf("%s", pc->p[i].sex); printf("请输入年龄:>\n"); scanf("%d", &(pc->p[i].age)); printf("请输入电话:>\n"); scanf("%s", pc->p[i].tel); printf("请输入地址:>\n"); scanf("%s", pc->p[i].address); printf("修改成功!\n"); } else printf("没有找到!\n"); }
SHOW显示信息函数
从第一行开始循环打印,以联系人数量count为限制,打印时为了保持美观,最好设计一个表头。
void show(const Con* pc) { assert(pc); if (pc->count == 0) { printf("联系人为空\n"); return; } printf("%-20s%-10s%-10s%-20s%-30s\n", "name", "sex", "age", "tel", "address"); int i = 0; for (i = 0; i < pc->count; i++) { printf("%-20s%-10s%-10d%-20s%-30s\n", pc->p[i].name, pc->p[i].sex, pc->p[i].age, pc->p[i].tel, pc->p[i].address); } }
SORT排序函数
使用qsort函数进行排序。
int cmp_by_name(const void* e1, const void* e2) { return strcmp(((Date*)e1)->name, ((Date*)e2)->name); } void sort(Con* pc) { assert(pc); qsort(pc->p, pc->count, sizeof(Date), cmp_by_name); printf("排序成功!\n"); }
其中:
sqort函数使用方法如下:
- qosrt函数需要包含头文件<stdlib.h>
- void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
- base为排序的对象,num为排序的个数,size为单个排序类型的大小,最后需要给qsort传入一个排序原则的函数,其返回值需要是个整数,根据返回值>0,<0,=0的情况进行排序。
- qsort适合所有类型的排序,所以传入给qsort的函数,其接收参数的形式必须是void*类型的。
- cmp_by_name是传入sqort的比大小函数
DEL ALL一键删除,SaveCon保存通讯录,Distory释放内存:
DEL ALL一键删除
将通讯录重新初始化,就可以完成一键删除的功能了。
void del_all(Con* pc) { assert(pc); InitCon(pc); printf("全部删除成功!\n"); }
SaveCon保存通讯录
将内存中的数据以二进制的方式写入到txt文本中。
void SaveCon(Con* pc) { assert(pc); FILE* pfwrite = fopen("con.txt", "w"); if (pfwrite == NULL) { perror("SaveCon"); return ; } int i = 0; for (i = 0; i < pc->count; i++) { fwrite(pc->p + i, sizeof(Date), 1, pfwrite); } printf("保存成功!\n"); fclose(pfwrite); pfwrite = NULL; }
其中:
- 先创建一个文件指针,以只写的方式打开文件
- 根据count的计数,按行依次将数据读写到txt文件中
- fwrite为二进制读写,fwrite使用方法如下:
- size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
- ptr为起始的指针位置,size为读取类型的大小,count为读取的个数,stream为文件指针
以二进制的读写打开txt文件后,会是一堆乱码,也可以格式化输出到txt文件中。代码如下:
//格式化输出 fprintf(pfwrite, "%10s%10s%10d%10s%10s\n", pc->p[i].name, pc->p[i].sex, pc->p[i].age, pc->p[i].tel, pc->p[i].address);
如果要使用格式化输出,在LoadCon加载函数中,信息载入的方式也需要进行修改。
Distory销毁函数
void Distory(Con* pc) { assert(pc); free(pc->p); pc->p = NULL; }
直接释放内存,并将指针置空。
在写程序时,主要思想就是:先想好大致框架,每个函数的作用、参数以及返回值,框架构建好后依次完善函数即可。在写程序中,要养成步步调试,写注释的好习惯。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/149316.html