• C语言动态规划和文件操作练习——通讯录


    用C语言实现一个简单通讯录功能:

    • 命令端输入对应指令可执行相应操作
    • 可以实现增、删、查、改、排序、显示、一键清除等功能
    • 每次输入的信息保存到txt文件中
    • 每次打开通讯录自动加载已有的txt文件中的信息

    本文主要是针对指针动态内存管理、以及文件读写操作的综合练习。

    目录

    声明结构体

    构建程序框架

    函数创建

    InitCon初始化函数

    LoadCon加载信息函数

    AddCapacity扩容函数

    ADD增加信息函数

    DEL,CHECK,MODIFY删查改函数

    SHOW显示信息函数 

    DEL ALL一键删除,SaveCon保存通讯录,Distory释放内存:


    声明结构体

    1. //通讯录结构体
    2. typedef struct Date
    3. {
    4. char name[20];
    5. char sex[5];
    6. int age;
    7. char tel[20];
    8. char address[30];
    9. }Date;
    10. typedef struct Con
    11. {
    12. Date* p;
    13. int count;
    14. int capacity;
    15. }Con;

    其中:

    • 结构体Date是用来存储学生信息的
    • 结构体Con嵌套了一个Date结构体
    • Con中count用来计数,记录当前目录存储学生的个数
    • Con中capacity用来记录当前结构体内存大小(内存不够时申请扩容)

    构建程序框架

    声明完结构体后,先构建程序框架

    1. //Con.h
    2. #include
    3. //main.c
    4. void menu()
    5. {
    6. printf("******************************************\n");
    7. printf("******** 1.ADD 2.DEL ******\n");
    8. printf("******** 3.SHOW 4.MODIFY ******\n");
    9. printf("******** 5.CHECK 6.DEL ALL ******\n");
    10. printf("******** 7.SORT 0.EXIT ******\n");
    11. printf("******************************************\n");
    12. }
    13. int main()
    14. {
    15. int input = 0;
    16. Con arr;
    17. do
    18. {
    19. menu();
    20. scanf("%d", &input);
    21. } while (input);
    22. return 0;
    23. }

    其中:

    • 创建一个菜单栏
    • 创建一个结构体变量arr
    • do while循环至少执行一次,当intput=0时循环结束

    完善框架: 

    只针对于上述要求,我们需要创建以下函数:

    InitCon:初始化通讯录,开辟内存

    LoadCon:加载txt中的信息到内存中

    ADD,DEL,CHECK,MODIFY:对应增、删、查、改

    SHOW:显示全部信息

    SORT:对某一项进行排序

    DEL ALL:一键清除所有信息

    EXIT:退出并保存

    Distory:释放内存

    完善后的主函数如下: 

    1. int main()
    2. {
    3. int input = 0;
    4. Con arr;
    5. InitCon(&arr);
    6. LoadCon(&arr);
    7. void (*pf[])(Con*) = {0,add,del,show,modify,check,del_all,sort};
    8. do
    9. {
    10. menu();
    11. scanf("%d", &input);
    12. if (input == 0)
    13. {
    14. SaveCon(&arr);
    15. Distory(&arr);
    16. printf("退出!\n");
    17. }
    18. else if (input > 0 && input < 8)
    19. {
    20. pf[input](&arr);
    21. }
    22. else
    23. printf("输入错误,请重新输入!\n");
    24. } while (input);
    25. return 0;
    26. }

    其中:

    • void (*pf[])(Con*)为函数指针数组,其数组中存储的类型void (*)(Con*)的函数指针,关于函数指针以及函数指针数组的知识,在回调函数中对其进行了详细讲解。
    • 通过调用函数指针数组的下标,从而实现调用程序的不同功能。
    • 当input=0时,需要先将内存中的信息保存到txt文件中,然后再释放内存

    函数创建

    为了日后的维护与管理,所有函数均存入到Con.c中,与主函数分开。 

    InitCon初始化函数

    在程序开始时开辟指定大小的内存,在动态规划中有malloc和calloc两种方式开辟内存,这里使用calloc的形式进行开辟。

    1. //Con.h
    2. #include
    3. #include
    4. #include
    5. #define INIT_NUM 3
    6. //Con.c
    7. void InitCon(Con* pc)
    8. {
    9. assert(pc);
    10. pc->p = (Date*)calloc(INIT_NUM, sizeof(Date));
    11. if (pc->p == NULL)
    12. {
    13. printf("InitCon::%s", strerror(errno));
    14. return;
    15. }
    16. pc->count = 0;
    17. pc->capacity = INIT_NUM;
    18. }

    其中:

    • 传入参数为结构体指针
    • 需要判断结构体指针不能为空,assert需要包含头文件
    • calloc使用方法:void* calloc (size_t num, size_t size);num为指定个数,size为指定大小,返回值为void*类型的指针
    • 开辟完内存后,最好再对其进行检查,是否为空。
    • strerror(arrno)为打印错误信息,strerror需要包含头文件,arrno需要包含头文件
    • 开辟内存时,同时初始化count=0,capcity等于calloc开辟的个数

    LoadCon加载信息函数

    这里用到了文件指针,对文件进行操作,大致思想为:

    • 如果文件中没有信息则退出
    • 如果文件中存有信息,按行读取
    • 每读取一行,对应的count需要+1
    • 如果读取的过程中,初始开辟的内存不够了,需要对内存进行加载
    1. void LoadCon(Con* pc)
    2. {
    3. assert(pc);
    4. FILE* pfopen = fopen("con.txt", "r");
    5. if (pfopen == NULL)
    6. {
    7. perror("LoadCon");
    8. return;
    9. }
    10. Date tmp = { 0 };
    11. while (fread(&tmp, sizeof(Date), 1, pfopen)==1)
    12. {
    13. AddCapacity(pc);
    14. pc->p[pc->count] = tmp;
    15. pc->count++;
    16. }
    17. printf("加载成功!\n");
    18. fclose(pfopen);
    19. pfopen = NULL;
    20. }

    其中:

    • 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中存储的信息更新 

    1. void AddCapacity(Con* pc)
    2. {
    3. assert(pc);
    4. if (pc->count == pc->capacity)
    5. {
    6. Date* str = (Date*)realloc(pc->p, sizeof(Date)*(pc->capacity+ ADD_NUM));
    7. if (pc->p == NULL)
    8. {
    9. printf("AddCapacity::%s", strerror(errno));
    10. return;
    11. }
    12. pc->p = str;
    13. pc->capacity += ADD_NUM;
    14. printf("增容成功\n");
    15. }
    16. }

    其中:

    • 当count中的个数等于capacity容量的个数时,便需要进行扩容
    • realloc函数的用法如下:void* realloc (void* ptr, size_t size);ptr为需要扩容的地址,size为扩容后新的空间总大小

    ADD增加信息函数

    ADD函数比较简单,只需要在每次增加前对其内存进行检查,然后格式化输入信息即可,每次增加一个联系人的信息后,其所对应的count计数也需要进行+1。

    1. void add(Con* pc)
    2. {
    3. assert(pc);
    4. AddCapacity(pc);
    5. printf("请输入名字:>\n");
    6. scanf("%s", pc->p[pc->count].name);
    7. printf("请输入性别:>\n");
    8. scanf("%s", pc->p[pc->count].sex);
    9. printf("请输入年龄:>\n");
    10. scanf("%d", &(pc->p[pc->count].age));
    11. printf("请输入电话:>\n");
    12. scanf("%s", pc->p[pc->count].tel);
    13. printf("请输入地址:>\n");
    14. scanf("%s", pc->p[pc->count].address);
    15. printf("输入成功\n");
    16. pc->count++;
    17. }

    DEL,CHECK,MODIFY删查改函数

    以上三个函数需要用到查找函数,先假设所有人的名字不重复,根据人的姓名进行查找,返回其所属的count值。

    1. int FindByName(const Con* pc)
    2. {
    3. assert(pc);
    4. int i = 0;
    5. char input[20] = {0};
    6. printf("请输入名字\n");
    7. scanf("%s", input);
    8. for (i = 0; i count; i++)
    9. {
    10. if (strcmp(input, pc->p[i].name) == 0)
    11. {
    12. return i;
    13. }
    14. }
    15. return -1;
    16. }

    其中:

    • 传参时const保护结构体指针pc,不能通过解引用的方式来更改内容
    • 比较两个字符串用strcmp,需要包含头文件,使用方法如下:int strcmp ( const char * str1, const char * str2 );str1和str2为相比较的两个字符串,返回值分为>0,<0 =0,相等时返回0

    当查找函数完成后,剩余的删查改函数就简单许多,需要注意的是:删除函数需要更改count,并且使所有人信息往前挪一位。

    删除函数

    1. //删除
    2. void del(Con* pc)
    3. {
    4. assert(pc);
    5. if (pc->count == 0)
    6. {
    7. printf("联系人为空\n");
    8. return;
    9. }
    10. int i = FindByName(pc);
    11. if (i != -1)
    12. {
    13. int j = 0;
    14. for (j = i; j < pc->count; j++)
    15. {
    16. pc->p[j] = pc->p[j + 1];
    17. }
    18. pc->count--;
    19. printf("删除成功!\n");
    20. }
    21. else
    22. printf("没有找到!\n");
    23. }

    查找并显示函数

    1. void check(const Con* pc)
    2. {
    3. assert(pc);
    4. int i = FindByName(pc);
    5. if (i != -1 )
    6. {
    7. printf("%-20s%-10s%-10s%-20s%-30s\n", "姓名", "性别", "年龄", "电话", "地址");
    8. printf("%-20s%-10s%-10d%-20s%-30s\n",
    9. pc->p[i].name,
    10. pc->p[i].sex,
    11. pc->p[i].age,
    12. pc->p[i].tel,
    13. pc->p[i].address);
    14. }
    15. else
    16. printf("没有找到!\n");
    17. }

    修改函数 

    1. void modify(Con* pc)
    2. {
    3. assert(pc);
    4. if (pc->count == 0)
    5. {
    6. printf("联系人为空\n");
    7. return;
    8. }
    9. int i = FindByName(pc);
    10. if (i != -1)
    11. {
    12. printf("请输入名字:>\n");
    13. scanf("%s", pc->p[i].name);
    14. printf("请输入性别:>\n");
    15. scanf("%s", pc->p[i].sex);
    16. printf("请输入年龄:>\n");
    17. scanf("%d", &(pc->p[i].age));
    18. printf("请输入电话:>\n");
    19. scanf("%s", pc->p[i].tel);
    20. printf("请输入地址:>\n");
    21. scanf("%s", pc->p[i].address);
    22. printf("修改成功!\n");
    23. }
    24. else
    25. printf("没有找到!\n");
    26. }

    SHOW显示信息函数 

    从第一行开始循环打印,以联系人数量count为限制,打印时为了保持美观,最好设计一个表头。

    1. void show(const Con* pc)
    2. {
    3. assert(pc);
    4. if (pc->count == 0)
    5. {
    6. printf("联系人为空\n");
    7. return;
    8. }
    9. printf("%-20s%-10s%-10s%-20s%-30s\n", "name", "sex", "age", "tel", "address");
    10. int i = 0;
    11. for (i = 0; i < pc->count; i++)
    12. {
    13. printf("%-20s%-10s%-10d%-20s%-30s\n",
    14. pc->p[i].name,
    15. pc->p[i].sex,
    16. pc->p[i].age,
    17. pc->p[i].tel,
    18. pc->p[i].address);
    19. }
    20. }

    SORT排序函数

    使用qsort函数进行排序。

    1. int cmp_by_name(const void* e1, const void* e2)
    2. {
    3. return strcmp(((Date*)e1)->name, ((Date*)e2)->name);
    4. }
    5. void sort(Con* pc)
    6. {
    7. assert(pc);
    8. qsort(pc->p, pc->count, sizeof(Date), cmp_by_name);
    9. printf("排序成功!\n");
    10. }

    其中:

    sqort函数使用方法如下:

    • qosrt函数需要包含头文件
    • 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一键删除

    将通讯录重新初始化,就可以完成一键删除的功能了。

    1. void del_all(Con* pc)
    2. {
    3. assert(pc);
    4. InitCon(pc);
    5. printf("全部删除成功!\n");
    6. }

    SaveCon保存通讯录

    将内存中的数据以二进制的方式写入到txt文本中。

    1. void SaveCon(Con* pc)
    2. {
    3. assert(pc);
    4. FILE* pfwrite = fopen("con.txt", "w");
    5. if (pfwrite == NULL)
    6. {
    7. perror("SaveCon");
    8. return ;
    9. }
    10. int i = 0;
    11. for (i = 0; i < pc->count; i++)
    12. {
    13. fwrite(pc->p + i, sizeof(Date), 1, pfwrite);
    14. }
    15. printf("保存成功!\n");
    16. fclose(pfwrite);
    17. pfwrite = NULL;
    18. }

    其中:

    • 先创建一个文件指针,以只写的方式打开文件
    • 根据count的计数,按行依次将数据读写到txt文件中
    • fwrite为二进制读写,fwrite使用方法如下:
    • size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
    • ptr为起始的指针位置,size为读取类型的大小,count为读取的个数,stream为文件指针

    以二进制的读写打开txt文件后,会是一堆乱码,也可以格式化输出到txt文件中。代码如下:

    1. //格式化输出
    2. fprintf(pfwrite, "%10s%10s%10d%10s%10s\n",
    3. pc->p[i].name, pc->p[i].sex, pc->p[i].age, pc->p[i].tel, pc->p[i].address);

    如果要使用格式化输出,在LoadCon加载函数中,信息载入的方式也需要进行修改。

    Distory销毁函数

    1. void Distory(Con* pc)
    2. {
    3. assert(pc);
    4. free(pc->p);
    5. pc->p = NULL;
    6. }

    直接释放内存,并将指针置空。

    在写程序时,主要思想就是:先想好大致框架,每个函数的作用、参数以及返回值,框架构建好后依次完善函数即可。在写程序中,要养成步步调试,写注释的好习惯。

  • 相关阅读:
    三、appender分析
    第3章:运行时数据区概述及线程 详细详解
    每天五分钟机器学习:对于分类问题,支持向量机和逻辑回归哪个好
    什么情况?吉利减持沃尔沃股份套现3.5亿美元
    第三次线上面试总结(2022.9.15 二面)
    计算机毕业设计Java游戏资讯网站(系统+程序+mysql数据库+Lw文档)
    计算机网络期末98+冲刺笔记
    注意前方,有月亮出现
    手撕红黑树 | 变色+旋转你真的明白了吗?【超用心超详细图文解释 | 一篇学会Red_Black_Tree】
    CPU
  • 原文地址:https://blog.csdn.net/why1472587/article/details/127540771