• C语言实现——简易通讯录


    前言:小伙伴们又见面啦!这几天通过我们对自定义数据类型的学习,我们已经掌握了如何同时对多种数据类型进行管理,那么今天这篇文章,我们就来干一件大事——实现简易的通讯录


    一.思路分析

    先来想想通讯录有哪些功能:

    添加联系人信息

    删除联系人信息

    查找联系人信息

    修改联系人信息

    显示联系人信息

    排序联系人信息

    清空所有联系人

    基本上都会有以上的7种功能,而且我们需要一个菜单来让用户进行选择。

    制作一个大的项目,往往都要将代码写得规范整洁

    所以我们要跟之前写三子棋和扫雷游戏一样,将宏定义常量,函数声明与定义,以及主函数分开来写,方便日后对代码的维护

    Contact即为联系人,通讯录。

    Contact.h        用来管理宏定义常量、函数的声明。

    Contact.c        是管理函数的定义实现。

    test.c               是用来进行测试代码的功能。


     二.基本框架实现

    下面我们就先来一步一步的实现通讯录的基本框架

    1.目录

    1. #include "Contact.h"
    2. void menu()
    3. {
    4. printf("*********************************\n");
    5. printf("***** 1.add 2.del *****\n");
    6. printf("***** 3.search 4.revize *****\n");
    7. printf("***** 5.show 6.sort *****\n");
    8. printf("***** 7.empty 0.exit *****\n");
    9. printf("*********************************\n");
    10. }
    11. int main()
    12. {
    13. int input;
    14. do {
    15. menu();
    16. printf("请选择->:");
    17. scanf("%d", &input);
    18. switch (input)
    19. {
    20. case 0:
    21. printf("退出通讯录\n");
    22. break;
    23. case 1:
    24. break;
    25. case 2:
    26. break;
    27. case 3:
    28. break;
    29. case 4:
    30. break;
    31. case 5:
    32. break;
    33. case 6:
    34. break;
    35. case 7:
    36. break;
    37. default:
    38. printf("输入错误,请重新输入\n");
    39. }
    40. } while (input);
    41. return 0;
    42. }

    目录的做法对于现在的我们来说应该算是手到擒来,但是现在我们想要进行一个小小的改进:

    如果我们做这个代码要交给其他的程序员看的话,他可能看到Switch-case语句时会很难分辨出各个case语句的数字都代表的是哪一项功能,还得不停地回到菜单函数去查看。

    所以我们希望用各项功能的名字取代数字,这样就会更加方便。

    那么这时候,就要用到枚举啦:

    1. #include "Contact.h"
    2. void menu()
    3. {
    4. printf("*********************************\n");
    5. printf("***** 1.add 2.del *****\n");
    6. printf("***** 3.search 4.revize *****\n");
    7. printf("***** 5.show 6.sort *****\n");
    8. printf("***** 7.empty 0.exit *****\n");
    9. printf("*********************************\n");
    10. }
    11. enum Function
    12. {
    13.     EXIT,//默认从0开始
    14.     ADD,
    15.     DEL,
    16.     SEARCH,
    17.     REVIZE,
    18.     SHOW,
    19.     SORT,
    20.     EMPTY,
    21. };
    22. int main()
    23. {
    24. int input;
    25. do {
    26. menu();
    27. printf("请选择->:");
    28. scanf("%d", &input);
    29. switch (input)
    30. {
    31. case EXIT:
    32. printf("退出通讯录\n");
    33. break;
    34. case ADD:
    35. break;
    36. case DEL:
    37. break;
    38. case SEARCH:
    39. break;
    40. case REVIZE:
    41. break;
    42. case SHOW:
    43. break;
    44. case SORT:
    45. break;
    46. case EMPTY:
    47. break;
    48. default:
    49. printf("输入错误,请重新输入\n");
    50. }
    51. } while (input);
    52. return 0;
    53. }

    枚举常量默认从0开始,所以将exit放在第一位,而后逐个递增。

    最后再将数字进行替换就好啦。


    2.创建联系人

    通讯录里会保存联系人的哪些信息呢???

    名字、电话、住址等等,这些显然不会是一种数据类型

    所以创建联系人变量,就要用到结构体啦。

    1. struct Peomessage
    2. {
    3. char name[20];
    4. int tele[12];
    5. char addr[30];
    6. };

    简单创建一个联系人信息的结构体,这时候又会有问题:

    一个人的电话的不会超过12个数字,但是名字不会超过20个字符吗???

    某一天,我认识了一个外国朋友,它的名字特别长,就要修改name数组的大小,地址同样如此

    那么为了方便高效,我们将这两个数组的大小进行宏定义

    1. #define NAME_MAX 20
    2. #define ADDR_MAX 30
    3. typedef struct Peomessage
    4. {
    5. char name[NAME_MAX];
    6. int tele[12];
    7. char addr[ADDR_MAX];
    8. }Peomessage;

     同时我们使用typedef类型重定义关键字将此结构体类型的名字定义为Peomessage,方便我们后续的使用。

    创建完联系人类型之后呢,我们就要开始创建联系人列表,这显然需要一个结构体类型的数组

    那么数组的大小是多少呢???这个又是一个无法估量的问题,所以我们仍然使用宏定义常量

    先默认能够存放100个联系人。

    Peomessage data[DATA_MAX];

    int sz;

     我们建立了这样一个数组。除此之外,我们还需要一个整型变量来帮助我统计通讯录里有多少个联系人了,所以我们定义sz

    不难看出,这两者我们也需要同时管理,所以干脆就将它们两个也用一个结构体来管理

    1. typedef struct Contact
    2. {
    3. Peomessage data[DATA_MAX];
    4. int sz;
    5. }Contect;

    3.初始化数据

    我们创建了Contect结构体之后,还需要将其内部数据初始化为0,以免出现异常情况。

    1. void InitContact(Contact* pc)
    2. {
    3. assert(pc);
    4. pc->sz = 0;
    5. memset(pc->data, 0, sizeof(pc->data));
    6. }

    memset是我们之前讲过的内存函数,可以将任意位置的任意数量的数据设置成我们想要的值

    这里要讲的一点是assert函数,这个函数的作用就是帮助我们检查代码是否有错,有错误则会立即终止程序并返回错误信息。

    这里主要是帮助我们判断pc指针的可用性,如果pc指针不可用,那么就会造成巨大的问题。

    通过上述的步骤,我们已经实现了通讯录的基本框架下面我们开始实现各种功能。 


    三.功能实现

    每一个功能的实现,必然少不了对于函数的运用。

    1.添加联系人信息

    再添加信息之前,有一点非常值得注意:那就是我们的通讯录有没有存满

    所以我们得先进行一个判断。

    1. void AddContact(Contact* pc)
    2. {
    3. assert(pc);
    4. //判断是否已存满
    5. if (pc->sz == DATA_MAX)
    6. {
    7. printf("通讯录已满,无法添加\n");
    8. return;
    9. }
    10. //没有存满则进行存放
    11. printf("请输入名字:");
    12. scanf("%s", pc->data[pc->sz].name);
    13. printf("请输入电话:");
    14. scanf("%s", pc->data[pc->sz].tele);
    15. printf("请输入住址:");
    16. scanf("%s", pc->data[pc->sz].addr);
    17. pc->sz++;
    18. printf("添加成功\n");
    19. }

    添加过一个联系人之后,我们的sz就需要+1,用来记录我们已经存放了多少个联系人的信息。

    来看实践:

    添加完联系人之后,我们想马上看一下我们到底有没有存进去

    下面我们就实现显示联系人信息


    2.显示联系人信息

    我们这里要解释一点,显示联系人信息是显示当前已经存放过的所有联系人的信息

    而之后要讲的查找联系人信息才是针对某个联系人来显示

    那么既然要打印所有人的信息,就必然少不了对于循环的运用。

    同样的,在显示之前,我们还要判断一下通讯录是否为空

    1. void ShowContact(Contact* pc)
    2. {
    3. assert(pc);
    4. if (pc->sz == 0)
    5. {
    6. printf("通讯录为空\n");
    7. return;
    8. }
    9. int i = 0;
    10. for (i = 0; i < pc->sz; i++)
    11. {
    12. printf("%s %s %s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
    13. }
    14. }

    这样我们就可以显示出我们的联系人信息啦。

    但是发现我们的信息上边没有像名字,电话,住址这样的列表名,而且我们每一行的信息排列并不整齐

    所以我们进行几处修改和补充:

    1. void ShowContact(Contact* pc)
    2. {
    3. assert(pc);
    4. if (pc->sz == 0)
    5. {
    6. printf("通讯录为空\n");
    7. return;
    8. }
    9. printf("%-20s%-12s%-30s\n","姓名","电话","住址");
    10. int i = 0;
    11. for (i = 0; i < pc->sz; i++)
    12. {
    13. printf("%-20s%-12s%-30s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
    14. }
    15. }

    这种打印方式不知道小伙伴们了不了解:

    %s是我们常规的方式,那么%20s就是指在打印的数据之前先打印20个空格

    %-20s则代表着先打印数据,在打印20个空格,这样我们就能实现对齐啦。


    3.查找联系人信息

    这里我们先讲查找联系人信息

    因为删除联系人信息、修改联系人信息实现的前期是:我们必须得先找到这个人才行。

     那么我们该怎么查找呢???显然通过名字是最直接的方法。而且我们需要用到循环

    既然使用名字查找这种方式到处都要用到,所以我们干脆给它也写成一个函数

    1. int FindByName(Contact* pc, char* name)
    2. {
    3. assert(pc && name);
    4. int i = 0;
    5. for (i = 0; i < pc->sz; i++)
    6. {
    7. if (strcmp(name, pc->data[i].name) == 0)
    8. {
    9. return i;
    10. }
    11. }
    12. return -1;
    13. }

    这里我们这个函数的返回值为什么要是整型呢???

    而且要是找到的话还返回它在通讯录列表里的位置,往下看就知道啦。

    1. void SearchContact(Contact* pc)
    2. {
    3. assert(pc);
    4. char name[NAME_MAX];
    5. printf("请输入要查找人的名字:");
    6. scanf("%s", name);
    7. int ret = FindByName(pc, name);
    8. if (ret == -1)
    9. {
    10. printf("找不到该联系人\n");
    11. return;
    12. }
    13. printf("%-20s%-12s%-30s\n", "姓名", "电话", "住址");
    14. printf("%-20s%-12s%-30s\n", pc->data[ret].name, pc->data[ret].tele, pc->data[ret].addr);
    15. }

     通过定义ret来接收FindByName函数的返回值,这样我们就能得到要查找的联系人的位置,能够轻松的打印出他的信息啦。

     


    4.删除联系人信息

    删除某个联系人信息,必然也是要先查找一下这个联系人存不存在

    同时想要删除一个联系人信息,必然是通过他的名字来删除

    1. void DelContact(Contact* pc)
    2. {
    3. assert(pc);
    4. char name[NAME_MAX];
    5. printf("请输入要查找人的名字:");
    6. scanf("%s", name);
    7. int ret = FindByName(pc, name);
    8. if (ret == -1)
    9. {
    10. printf("找不到该联系人\n");
    11. return;
    12. }
    13. }

    接下来就是要思考怎么来删除了。

    我们使用的通讯录本质上是一个数组,想要删除某个位置的信息,首先想到的就是把它置为0

    但是这样一来我们就会浪费一个空间因为你不知道这个位置的数组下标,也不可能把一个新的联系人信息补充到这个位置上来

    所以我们采取逐个覆盖的方法,用后边的信息逐一向前一位进行覆盖

    1. void DelContact(Contact* pc)
    2. {
    3. assert(pc);
    4. char name[NAME_MAX];
    5. printf("请输入要删除联系人的名字:");
    6. scanf("%s", name);
    7. int ret = FindByName(pc, name);
    8. if (ret == -1)
    9. {
    10. printf("该联系人信息不存在\n");
    11. return;
    12. }
    13. //删除联系人
    14. int i = 0;
    15. for (i = ret;i < pc->sz - 1; i++)
    16. {
    17. pc->data[i] = pc->data[i + 1];
    18. }
    19. pc->sz--;
    20. printf("删除成功\n");
    21. }

    用 i 接收 FindByName 的返回值 ret ,这样我们就得到了要删除的联系人的位置,从这里开始我们通过循环进行逐一覆盖。

    删除一个联系人之后,不要忘记我们的sz要-1

     


    5.修改联系人信息

    修改联系人信息之前,我们还是要先查找一下这个联系人存不存在,如果存在则进行修改。

    修改联系人信息则需要让用户选择出要具体修改哪个信息

    所以我们还需要一个小目录来供用户选择,这里的操作就和刚开始的制作目录的方法一样啦。

    1. void menu2()
    2. {
    3. printf("*******************************\n");
    4. printf("***** 0.exit 1.name *****\n");
    5. printf("***** 2.tele 3.addr *****\n");
    6. printf("*******************************\n");
    7. }
    1. enum Type
    2. {
    3. QUIT,
    4. NAME,
    5. TELE,
    6. ADDR
    7. };

    这里为了不发生类型多次重定义,我们将“退出”用它的另一个英文“quit”来定义。

    1. void ReviseContact(Contact* pc)
    2. {
    3. assert(pc);
    4. char name[NAME_MAX];
    5. printf("请输入要修改联系人的名字:");
    6. scanf("%s", name);
    7. int ret = FindByName(pc, name);
    8. if (ret == -1)
    9. {
    10. printf("该联系人信息不存在\n");
    11. return;
    12. }
    13. int input;
    14. do
    15. {
    16. menu2();
    17. printf("请选择要修改的信息类型:");
    18. scanf("%d", &input);
    19. switch (input)
    20. {
    21. case QUIT:
    22. printf("退出修改\n");
    23. break;
    24. case NAME:
    25. printf("请输入名字:");
    26. scanf("%s", pc->data[ret].name);
    27. printf("修改成功\n");
    28. break;
    29. case TELE:
    30. printf("请输入电话:");
    31. scanf("%s", pc->data[ret].tele);
    32. printf("修改成功\n");
    33. break;
    34. case ADDR:
    35. printf("请输入地址:");
    36. scanf("%s", pc->data[ret].addr);
    37. printf("修改成功\n");
    38. break;
    39. default:
    40. printf("选择错误,请重新选择\n");
    41. break;
    42. }
    43. } while (input);
    44. }

     来看实战:


     6.排序联系人信息

    排序联系人信息我们这里需要用到一个函数——qsort

    使用这个函数需要头文件:#include

     qsort函数有4个参数,分别是:

    base        排序的首地址

    num         排序的数据数量

    size          排序的数据字节大小

    compar    排序的方式函数  

     通讯录的排序都是通过名字的首字母来排序的,所以这里我们要比较的是两个名字的字符串

    所以我们强制类型转换之后要调用name成员来比较。

    1. int compare(const void* a, const void* b)
    2. {
    3. return strcmp(((Peomessage*)a)->name, ((Peomessage*)b)->name);
    4. }

    这里的强制类型转换用法我们在之前的文章——内存函数中已经讲到,不熟悉的小伙伴建议再去补习补习哦。

    1. void SortContact(Contact* pc)
    2. {
    3. assert(pc);
    4. qsort(pc->data, pc->sz, sizeof(pc->data[0]), compare);
    5. printf("排序成功\n");
    6. }

    我们要排序的是data这个结构体类型的数组,所以要传入它的首地址、大小、以及每个结构体数据的字节大小,最后传入我们的比较函数compare

    来看实战:


    7.清空所有联系人

    那么最后,如何清空所有的联系人呢???

    其实这个超级简单,所谓清空,不就是再初始化一次吗,我们一开始就已经搞定啦。

    1. void EmptyContact(Contact* pc)
    2. {
    3. assert(pc);
    4. pc->sz = 0;
    5. memset(pc->data, 0, sizeof(pc->data));
    6. }


     四.完整代码展示

    1.Contact.h

    1. #include
    2. #include
    3. #include
    4. #include
    5. #define NAME_MAX 20
    6. #define ADDR_MAX 30
    7. #define DATA_MAX 100
    8. //选项
    9. enum Function
    10. {
    11. EXIT,//默认从0开始
    12. ADD,
    13. DEL,
    14. SEARCH,
    15. REVISE,
    16. SHOW,
    17. SORT,
    18. EMPTY
    19. };
    20. enum Type
    21. {
    22. QUIT,
    23. NAME,
    24. TELE,
    25. ADDR
    26. };
    27. //联系人信息
    28. typedef struct Peomessage
    29. {
    30. char name[NAME_MAX];
    31. char tele[12];
    32. char addr[ADDR_MAX];
    33. }Peomessage;
    34. typedef struct Contact
    35. {
    36. Peomessage data[DATA_MAX];
    37. int sz;
    38. }Contact;
    39. //初始化通讯录
    40. void InitContact(Contact* pc);
    41. //增加联系人信息
    42. void AddContact(Contact* pc);
    43. //显示联系人信息
    44. void ShowContact(Contact* pc);
    45. //查找联系人信息
    46. void SearchContact(Contact* pc);
    47. //删除联系人信息
    48. void DelContact(Contact* pc);
    49. //修改联系人信息
    50. void ReviseContact(Contact* pc);
    51. //排序联系人信息
    52. void SortContact(Contact* pc);
    53. //清空所有联系人
    54. void EmptyContact(Contact* pc);

    2.Contact.c

    1. #include"Contact.h"
    2. //初始化通讯录
    3. void InitContact(Contact* pc)
    4. {
    5. assert(pc);
    6. pc->sz = 0;
    7. memset(pc->data, 0, sizeof(pc->data));
    8. }
    9. //增加联系人信息
    10. void AddContact(Contact* pc)
    11. {
    12. assert(pc);
    13. //判断是否已存满
    14. if (pc->sz == DATA_MAX)
    15. {
    16. printf("通讯录已满,无法添加\n");
    17. return;
    18. }
    19. //没有存满则进行存放
    20. printf("请输入名字:");
    21. scanf("%s", pc->data[pc->sz].name);
    22. printf("请输入电话:");
    23. scanf("%s", pc->data[pc->sz].tele);
    24. printf("请输入住址:");
    25. scanf("%s", pc->data[pc->sz].addr);
    26. pc->sz++;
    27. printf("添加成功\n");
    28. }
    29. //显示联系人信息
    30. void ShowContact(Contact* pc)
    31. {
    32. assert(pc);
    33. if (pc->sz == 0)
    34. {
    35. printf("通讯录为空\n");
    36. return;
    37. }
    38. printf("%-20s%-12s%-30s\n","姓名","电话","住址");
    39. int i = 0;
    40. for (i = 0; i < pc->sz; i++)
    41. {
    42. printf("%-20s%-12s%-30s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
    43. }
    44. }
    45. //通过名字查找
    46. int FindByName(Contact* pc, char* name)
    47. {
    48. assert(pc && name);
    49. int i = 0;
    50. for (i = 0; i < pc->sz; i++)
    51. {
    52. if (strcmp(name, pc->data[i].name) == 0)
    53. {
    54. return i;
    55. }
    56. }
    57. return -1;
    58. }
    59. //查找联系人信息
    60. void SearchContact(Contact* pc)
    61. {
    62. assert(pc);
    63. char name[NAME_MAX];
    64. printf("请输入要查找人的名字:");
    65. scanf("%s", name);
    66. int ret = FindByName(pc, name);
    67. if (ret == -1)
    68. {
    69. printf("找不到该联系人\n");
    70. return;
    71. }
    72. printf("%-20s%-12s%-30s\n", "姓名", "电话", "住址");
    73. printf("%-20s%-12s%-30s\n", pc->data[ret].name, pc->data[ret].tele, pc->data[ret].addr);
    74. }
    75. //删除联系人信息
    76. void DelContact(Contact* pc)
    77. {
    78. assert(pc);
    79. char name[NAME_MAX];
    80. printf("请输入要删除联系人的名字:");
    81. scanf("%s", name);
    82. int ret = FindByName(pc, name);
    83. if (ret == -1)
    84. {
    85. printf("该联系人信息不存在\n");
    86. return;
    87. }
    88. //删除联系人
    89. int i = 0;
    90. for (i = ret;i < pc->sz - 1; i++)
    91. {
    92. pc->data[i] = pc->data[i + 1];
    93. }
    94. pc->sz--;
    95. printf("删除成功\n");
    96. }
    97. void menu2()
    98. {
    99. printf("*******************************\n");
    100. printf("***** 0.exit 1.name *****\n");
    101. printf("***** 2.tele 3.addr *****\n");
    102. printf("*******************************\n");
    103. }
    104. //修改联系人信息
    105. void ReviseContact(Contact* pc)
    106. {
    107. assert(pc);
    108. char name[NAME_MAX];
    109. printf("请输入要修改联系人的名字:");
    110. scanf("%s", name);
    111. int ret = FindByName(pc, name);
    112. if (ret == -1)
    113. {
    114. printf("该联系人信息不存在\n");
    115. return;
    116. }
    117. int input;
    118. do
    119. {
    120. menu2();
    121. printf("请选择要修改的信息类型:");
    122. scanf("%d", &input);
    123. switch (input)
    124. {
    125. case QUIT:
    126. printf("退出修改\n");
    127. break;
    128. case NAME:
    129. printf("请输入名字:");
    130. scanf("%s", pc->data[ret].name);
    131. printf("修改成功\n");
    132. break;
    133. case TELE:
    134. printf("请输入电话:");
    135. scanf("%s", pc->data[ret].tele);
    136. printf("修改成功\n");
    137. break;
    138. case ADDR:
    139. printf("请输入地址:");
    140. scanf("%s", pc->data[ret].addr);
    141. printf("修改成功\n");
    142. break;
    143. default:
    144. printf("选择错误,请重新选择\n");
    145. break;
    146. }
    147. } while (input);
    148. }
    149. //排序联系人信息
    150. int compare(const void* a, const void* b)
    151. {
    152. return strcmp(((Peomessage*)a)->name, ((Peomessage*)b)->name);
    153. }
    154. void SortContact(Contact* pc)
    155. {
    156. assert(pc);
    157. qsort(pc->data, pc->sz, sizeof(pc->data[0]), compare);
    158. printf("排序成功\n");
    159. }
    160. //清空所有联系人
    161. void EmptyContact(Contact* pc)
    162. {
    163. assert(pc);
    164. pc->sz = 0;
    165. memset(pc->data, 0, sizeof(pc->data));
    166. printf("已清空通讯录\n");
    167. }

    3.test.c

    1. #include "Contact.h"
    2. void menu1()//菜单
    3. {
    4. printf("*********************************\n");
    5. printf("***** 1.add 2.del *****\n");
    6. printf("***** 3.search 4.revise *****\n");
    7. printf("***** 5.show 6.sort *****\n");
    8. printf("***** 7.empty 0.exit *****\n");
    9. printf("*********************************\n");
    10. }
    11. int main()
    12. {
    13. Contact con;
    14. //初始化通讯录
    15. InitContact(&con);
    16. int input;
    17. do {
    18. menu1();
    19. printf("请选择->:");
    20. scanf("%d", &input);
    21. switch (input)
    22. {
    23. case EXIT:
    24. printf("退出通讯录\n");
    25. break;
    26. case ADD:
    27. AddContact(&con);
    28. break;
    29. case DEL:
    30. DelContact(&con);
    31. break;
    32. case SEARCH:
    33. SearchContact(&con);
    34. break;
    35. case REVISE:
    36. ReviseContact(&con);
    37. break;
    38. case SHOW:
    39. ShowContact(&con);
    40. break;
    41. case SORT:
    42. SortContact(&con);
    43. break;
    44. case EMPTY:
    45. EmptyContact(&con);
    46. break;
    47. default:
    48. printf("输入错误,请重新输入\n");
    49. }
    50. } while (input);
    51. return 0;
    52. }

    五.总结

    到这里,对于如何用C语言实现简易通讯录的讲解终于完结撒花啦!!!

    喜欢博主文章的小伙伴一定要点个关注不迷路哦。

    最后还是要记得一键三连呀!

    祝大家中秋节快乐,我们下期再见啦!

  • 相关阅读:
    Jenkins部署以及基本使用
    PyTorch ReLU6网络层
    一文拿捏Spring事务之、ACID、隔离级别、失效场景
    【工具使用】卸载VS(Visual Studio)
    SQL语句书写规范
    C++ Reference: Standard C++ Library reference: C Library: cwchar: wprintf
    好兄弟一口气拿下了8个测试岗的大厂offer,面子给我赚回来了
    HTTP/1.1和HTTP/2的区别
    计算机操作系统面试题库和答案
    一些 Conda 的常用命令
  • 原文地址:https://blog.csdn.net/2303_78442132/article/details/133311612