• 【C语言】可保存的动态通讯录的实现



    一、Contact.h

     二、Contact.c

    1、判断是否增容

    2、初始化通讯录

    3、打印

    4、增加联系人信息

    5、通过名字查找

    6、删除联系人信息

    7、查找信息

    8、修改信息

    9、排序

    10、清空通讯录

    11、保存通讯录为文件

    三、text.c

    四、错误写法分享

    五、动图展示


    一、Contact.h

    1. #pragma once
    2. #define _CRT_SECURE_NO_WARNINGS 1
    3. #include
    4. #include
    5. #include
    6. #include
    7. #define MAX_NAME 20
    8. #define MAX_SEX 10
    9. #define MAX_TELE 12
    10. #define MAX_ADDR 30
    11. #define INITIAL 3
    12. typedef struct PeoInfo
    13. {
    14. char name[MAX_NAME];
    15. char sex[MAX_SEX];
    16. char tele[MAX_TELE];
    17. char addr[MAX_ADDR];
    18. int age;
    19. }PeoInfo;
    20. typedef struct Contact
    21. {
    22. PeoInfo* arr;//PeoInfo类型的指针,用于指向动态开辟的空间
    23. int size;//数据个数
    24. int capacity;//总容量
    25. }Contact;
    26. void ContactInit(Contact* pc);//初始化通讯录
    27. void ContactPrint(const Contact* pc);//打印
    28. void ContactAdd(Contact* pc);//增加联系人信息
    29. void ContactDel(Contact* pc);//删除联系人信息
    30. void ContactFind(const Contact* pc);//查找信息
    31. void ContactModify(Contact* pc);//修改信息
    32. void ContactSortByName(Contact* pc);//排序
    33. void ContactDestroy(Contact* pc);//清空通讯录
    34. void ContactSave(const Contact* pc);//保存通讯录为文件

     二、Contact.c

    1、判断是否增容

    1. void ContactCapacityIncrease(Contact* pc)//判断是否增容
    2. {
    3. assert(pc);
    4. if (pc->size == pc->capacity)
    5. {
    6. //需要增容
    7. int newcapacity = pc->capacity == 0 ? INITIAL : INITIAL + pc->capacity;
    8. PeoInfo* tmp = (PeoInfo*)realloc(pc->arr, newcapacity * sizeof(PeoInfo));
    9. if (tmp == NULL)
    10. {
    11. printf("%s\n", strerror(errno));
    12. exit(-1);
    13. }
    14. pc->arr = tmp;
    15. pc->capacity = newcapacity;
    16. printf("增容成功!\n");
    17. }
    18. }

    没有空间时,为arr开辟3个空间,有空间且空间满出时,每次为arr扩容3个空间。

    2、初始化通讯录

    1. void ContactInit(Contact* pc)//初始化通讯录
    2. {
    3. assert(pc);
    4. pc->size = 0;
    5. pc->capacity = 0;
    6. pc->arr = NULL;
    7. //加载文件的信息到通讯录
    8. FILE* pf = fopen("Contact.txt", "rb");
    9. if (pf == NULL)
    10. {
    11. perror("ContactInit:");
    12. exit(-1);
    13. }
    14. ContactCapacityIncrease(pc);
    15. PeoInfo tmp = { 0 };
    16. while (fread(&tmp, sizeof(pc->arr[0]), 1, pf) == 1)
    17. {
    18. ContactCapacityIncrease(pc);
    19. pc->arr[pc->size] = tmp;
    20. pc->size++;
    21. }
    22. fclose(pf);
    23. pf = NULL;
    24. }

    初始化通讯录并在每次运行程序时把工程目录下的Contact.txt文本文件加载出来。实现通讯录信息的读取。

    3、打印

    1. void ContactPrint(const Contact* pc)//打印
    2. {
    3. assert(pc);
    4. printf("姓名\t性别\t电话\t地址\t年龄\n");
    5. for (int i = 0; i < pc->size; i++)
    6. {
    7. printf("%s\t%s\t%s\t%s\t%d\n",
    8. pc->arr[i].name,
    9. pc->arr[i].sex,
    10. pc->arr[i].tele,
    11. pc->arr[i].addr,
    12. pc->arr[i].age);
    13. }
    14. }

    for循环遍历打印pc->date中的结构体成员 

    4、增加联系人信息

    1. void ContactAdd(Contact* pc)//增加联系人信息
    2. {
    3. ContactCapacityIncrease(pc);
    4. printf("请输入姓名:\n");
    5. scanf("%s", pc->arr[pc->size].name);
    6. printf("请输入性别:\n");
    7. scanf("%s", pc->arr[pc->size].sex);
    8. printf("请输入电话:\n");
    9. scanf("%s", pc->arr[pc->size].tele);
    10. printf("请输入地址:\n");
    11. scanf("%s", pc->arr[pc->size].addr);
    12. printf("请输入年龄:\n");
    13. scanf("%d", &(pc->arr[pc->size].age));
    14. pc->size++;
    15. }

    注意增加联系人后pc->size++ 

    5、通过名字查找

    1. static int FindByName(const Contact* pc, const char arr[])//通过名字查找
    2. {
    3. assert(pc && arr);
    4. for (int i = 0; i < pc->size; i++)
    5. {
    6. if (strcmp(pc->arr[i].name, arr) == 0)
    7. {
    8. return i;
    9. }
    10. }
    11. return -1;
    12. }

    可以通过名字查找通讯录中是否存在联系人,存在返回下标,不存在返回-1

    6、删除联系人信息

    1. void ContactDel(Contact* pc)//删除联系人信息
    2. {
    3. assert(pc);
    4. printf("请输入姓名查找:");
    5. char arr[20] = { 0 };
    6. scanf("%s", arr);
    7. int pos = FindByName(pc, arr);//记录size的位置
    8. if (pos == -1)
    9. {
    10. printf("通讯录没有该信息\n");
    11. return;
    12. }
    13. for (int i = pos; i < pc->size - 1; i++)//移动元素
    14. {
    15. pc->arr[i] = pc->arr[i + 1];
    16. }
    17. pc->size--;
    18. printf("删除成功!\n");
    19. }

    通过静态函数FindByName返回的下标,通过for循环将后续元素逐个进行覆盖。 

    7、查找信息

    1. void ContactFind(const Contact* pc)//查找信息
    2. {
    3. assert(pc);
    4. printf("请输入姓名查找:");
    5. char arr[20] = { 0 };
    6. scanf("%s", arr);
    7. int pos = FindByName(pc, arr);
    8. if (pos != -1)
    9. {
    10. printf("查找到如下信息:\n");
    11. printf("姓名\t性别\t电话\t地址\t年龄\n");
    12. printf("%s\t%s\t%s\t%s\t%d\n",
    13. pc->arr[pos].name,
    14. pc->arr[pos].sex,
    15. pc->arr[pos].tele,
    16. pc->arr[pos].addr,
    17. pc->arr[pos].age);
    18. }
    19. else
    20. printf("通讯录查无此人!\n");
    21. }

    先判断查找的信息是否在通讯录中,再打印该下标的信息。 

    8、修改信息

    1. void ContactModify(Contact* pc)//修改信息
    2. {
    3. assert(pc);
    4. printf("请输入姓名查找:");
    5. char arr[20] = { 0 };
    6. scanf("%s", arr);
    7. int pos = FindByName(pc, arr);
    8. if (pos == -1)
    9. {
    10. printf("找不到\n");
    11. return;
    12. }
    13. else
    14. {
    15. printf("请输入更改后的姓名:\n");
    16. scanf("%s", pc->arr[pos].name);
    17. printf("请输入更改后的性别:\n");
    18. scanf("%s", pc->arr[pos].sex);
    19. printf("请输入更改后的电话:\n");
    20. scanf("%s", pc->arr[pos].tele);
    21. printf("请输入更改后的地址:\n");
    22. scanf("%s", pc->arr[pos].addr);
    23. printf("请输入更改后的年龄:\n");
    24. scanf("%d", &(pc->arr[pos].age));
    25. }
    26. }

    先判断查找的信息是否在通讯录中,再修改该下标的信息。 

    9、排序

    1. int name_cmp(const void* e1, const void* e2)
    2. {
    3. return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
    4. }
    5. void ContactSortByName(Contact* pc)//排序
    6. {
    7. assert(pc && pc->size != 0);
    8. qsort(pc->arr, pc->size, sizeof(PeoInfo), name_cmp);
    9. printf("排序完成\n");
    10. }

    使用qsort函数排序通讯录,可参照本文学习qsort排序结构体的方法 

    10、清空通讯录

    1. void ContactDestroy(Contact* pc)//清空通讯录
    2. {
    3. assert(pc);
    4. free(pc->arr);
    5. pc->arr = NULL;
    6. pc->size = 0;
    7. pc->capacity = 0;
    8. printf("通讯录已清空\n");
    9. }

    free动态开辟的空间,将指针置空,pc->size和pc->capacity 置为0

    11、保存通讯录为文件

    1. void ContactSave(const Contact* pc)//保存通讯录为文件
    2. {
    3. assert(pc);
    4. FILE* pf = fopen("Contact.txt", "wb");
    5. if (pf == NULL)
    6. {
    7. perror("ContactSave:");
    8. exit(-1);
    9. }
    10. for (int i = 0; i < pc->size; i++)
    11. {
    12. fwrite(pc->arr + i, sizeof(pc->arr[0]), 1, pf);
    13. }
    14. fclose(pf);
    15. pf = NULL;
    16. }

    将通讯录中的信息以文件的形式保存起来,下次运行可以读取这部分信息 

    三、text.c

    1. #include "contact.h"
    2. void menu()
    3. {
    4. printf("###########################\n");
    5. printf("#####1、add 2、del######\n");
    6. printf("#####3、find 4、modify###\n");
    7. printf("#####5、print 6、sort#####\n");
    8. printf("#####7、empty 8、exit#####\n");
    9. printf("###########################\n");
    10. }
    11. enum option
    12. {
    13. ADD = 1,
    14. DEL,
    15. FIND,
    16. MODIFY,
    17. PRINT,
    18. SORT,
    19. EMPTY,
    20. EXIT
    21. };
    22. int main()
    23. {
    24. int input = 0;
    25. Contact c;//创建一个通讯录
    26. ContactInit(&c);//初始化通讯录
    27. while (1)
    28. {
    29. menu();
    30. printf("请输入选项:\n");
    31. scanf("%d", &input);
    32. if (input == ADD)
    33. ContactAdd(&c);//增加联系人信息
    34. else if (input == DEL)
    35. ContactDel(&c);//删除联系人信息
    36. else if (input == FIND)
    37. ContactFind(&c);//查找联系人信息
    38. else if (input == MODIFY)
    39. ContactModify(&c);//修改联系人信息
    40. else if (input == PRINT)
    41. ContactPrint(&c);//打印
    42. else if (input == SORT)
    43. ContactSortByName(&c);//排序
    44. else if (input == EMPTY)
    45. ContactDestroy(&c);//清空通讯录
    46. else if (input == EXIT)
    47. {
    48. ContactSave(&c);//保存通讯录为文件
    49. printf("通讯录已保存至文件!\n");
    50. ContactDestroy(&c);//清空通讯录
    51. break;//退出
    52. }
    53. else
    54. printf("输入错误!请重新输入!\n");
    55. }
    56. return 0;
    57. }

    四、错误写法分享

    图中绿色波浪线部分有两个错误

    1、pc->size在while循环判断处,导致最后一次判断时(文件已读取完毕),pc->size也会++,导致程序运行时越界打印数据

    2、那么是不是把pc->size++放在while循环里面就行了呢?不是的,虽然这样pc->size的大小是正确的,但是当通讯录的容量已满,下一次循环进来也是先写入数据,再进行扩容,越界访问触发断点。、

    正确的写法参照本文初始化通讯录部分。

    五、动图展示

    注意第一次运行时,需要在工程目录下新建一个Contact.txt文件。

  • 相关阅读:
    网站TDK采集工具-网站的TDK设置方法
    Windows取证——隐藏术
    ssm+vue的课程网络学习平台管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。
    Go解密之路——GPM
    系统学习Java语言的15个网站
    Self-Attention和Multi-Head Attention的详细代码内容(没有原理)
    LC 239.滑动窗口最大值
    dubbo源码解析之服务发布与注册
    沉睡者IT - 如何识别NFT“洗盘交易”?
    Midjourney AI绘画中文教程详解(完整版)模型、命令、参数与各种高级用法
  • 原文地址:https://blog.csdn.net/gfdxx/article/details/125904291