• 【C语言】通讯录——示例以及详解


    目录

    前言

    代码部分

    功能介绍

    1.浏览信息

    联系人结构体定义

     初始化id信息函数

    初始化Name信息函数

    初始化Tel信息函数

     开辟节点空间

     添加节点函数

    信息初始化封装函数

     页数结构体定义

    计算页数函数

    展示页小菜单

     信息展示

    翻页功能

     浏览功能封装

    2.添加信息

     字符串获取函数(注意)

     手动输入函数

    3.查询信息

     4.主函数

    总结

    后记


    前言

    对于刚接触c语言的小伙伴们,通讯录是第一个综合性我们所学的知识的程序,存在一定难度,那么本篇是我写的通讯录,一共300多行,虽然功能少以及依然存在些许问题,但所用到的知识点我会为大家逐一注释并讲解,如果有写错的也欢迎各位大佬指出,我会及时改正(叠盾)

    代码部分

    1. #include
    2. #include
    3. #include
    4. #include
    5. //全局变量定义
    6. int g_Menutype;
    7. char g_Key;
    8. //节点结构体
    9. typedef struct Node
    10. {
    11. int nID;
    12. char* Name;
    13. char* Tel;
    14. struct Node* pNext;
    15. }List;
    16. //页数结构体
    17. typedef struct PAGE
    18. {
    19. int nCurrentPage; //当前页
    20. int OnePageInfo; //每页信息数量
    21. int nTotalInfo; //信息总数
    22. int nTotalPage; //总页数
    23. }Page;
    24. //字符串获取
    25. char* Getstring()
    26. {
    27. int nSize = 5;
    28. char* str = malloc(nSize);
    29. int nCount = 0;
    30. char c;
    31. scanf_s(" ");//过滤空白字符!!!
    32. if (str)
    33. {
    34. while ((c = getchar()) != '\n')
    35. {
    36. str[nCount] = c;
    37. nCount++;
    38. if (nCount == nSize)
    39. str = realloc(str, nSize += 5);
    40. }
    41. str[nCount] = '\0';
    42. }
    43. return str;
    44. }
    45. //自动获取id
    46. int GetID()
    47. {
    48. static int a = 0;//静态变量只会初始化一次
    49. a++;
    50. return a;
    51. }
    52. //自动生成测试姓名
    53. char* GetName()
    54. {
    55. char* str = malloc(sizeof(char) * 6);
    56. if (str)
    57. {
    58. for (int i = 0; i < 5; i++)
    59. str[i] = rand() % 26 + 'a';
    60. str[5] = '\0';
    61. }
    62. return str;
    63. }
    64. //自动生成电话号码
    65. char* GetTel()
    66. {
    67. char* str = malloc(12);
    68. if (str)
    69. {
    70. switch (rand() % 4)
    71. {
    72. case 0:
    73. str[0] = '1';
    74. str[1] = '3';
    75. str[2] = '3';
    76. break;//133
    77. case 1:
    78. str[0] = '1';
    79. str[1] = '5';
    80. str[2] = '5';
    81. break;//155
    82. case 2:
    83. str[0] = '1';
    84. str[1] = '7';
    85. str[2] = '7';
    86. break;//177
    87. case 3:
    88. str[0] = '1';
    89. str[1] = '8';
    90. str[2] = '8';
    91. break;//188
    92. }
    93. for (int i = 3; i < 11; i++)
    94. str[i] = rand() % 10 + '0';
    95. str[11] = '\0';
    96. }
    97. return str;
    98. }
    99. //开辟空间
    100. List* GetNode()
    101. {
    102. List* pTemp = malloc(sizeof(List));
    103. if (pTemp)
    104. {
    105. pTemp->nID = GetID();
    106. pTemp->Name = GetName();
    107. pTemp->Tel = GetTel();
    108. pTemp->pNext = NULL;
    109. }
    110. return pTemp;
    111. }
    112. //添加节点
    113. void AddNode(List** pHead, List** pEnd, List* pNode)
    114. {
    115. if (*pHead == NULL)*pHead = pNode;
    116. else (*pEnd)->pNext = pNode;
    117. *pEnd = pNode;
    118. }
    119. //信息初始化封装
    120. void InitInfo(List** pHead, List** pEnd, int n)
    121. {
    122. srand((unsigned int)time(NULL));
    123. for (int i = 0; i < n; i++)
    124. AddNode(pHead, pEnd, GetNode());
    125. }
    126. //计算页数
    127. Page* GetPage(List* pHead)
    128. {
    129. Page* pPage = malloc(sizeof(Page));
    130. if (pPage)
    131. {
    132. pPage->nCurrentPage = 0;
    133. pPage->OnePageInfo = 10;
    134. pPage->nTotalInfo = 0;
    135. List* pMark = pHead;
    136. while (pMark != NULL)
    137. {
    138. pPage->nTotalInfo++;
    139. pMark = pMark->pNext;
    140. }
    141. if (pPage->nTotalInfo % pPage->OnePageInfo == 0)
    142. pPage->nTotalPage = pPage->nTotalInfo / pPage->OnePageInfo;
    143. else pPage->nTotalPage = pPage->nTotalInfo / pPage->OnePageInfo + 1;
    144. }
    145. return pPage;
    146. }
    147. //信息展示
    148. void ShowInfo(List* pHead, Page* pPage)
    149. {
    150. int nBegin = (pPage->nCurrentPage - 1) * pPage->OnePageInfo + 1;
    151. int nEnd = pPage->nCurrentPage * pPage->OnePageInfo;
    152. int nCount = 0;
    153. while (pHead != NULL)
    154. {
    155. nCount++;
    156. if (nCount >= nBegin && nCount <= nEnd)
    157. printf("%d %s %s\n", pHead->nID, pHead->Name, pHead->Tel);
    158. pHead = pHead->pNext;
    159. }
    160. }
    161. //菜单
    162. void ShowMenu(Page* pPage)
    163. {
    164. switch (g_Menutype)
    165. {
    166. case 1:
    167. printf("\n当前第%d页 共%d页 共%d条", pPage->nCurrentPage, pPage->nTotalPage, pPage->nTotalInfo);
    168. printf("\nw上一页 s下一页 b返回\n");
    169. break;
    170. case 3:
    171. printf("\n当前第%d页 共%d页 共%d条", pPage->nCurrentPage, pPage->nTotalPage, pPage->nTotalInfo);
    172. printf("\nw上一页 s下一页 b返回 c重新查询\n");
    173. break;
    174. }
    175. }
    176. //翻页功能
    177. void TurnPage(List* pHead, Page* pPage)
    178. {
    179. char c = 's';//默认向下翻一页,因为一开始当前页为0
    180. while (1)
    181. {
    182. switch (c)
    183. {
    184. case 'w':
    185. if (pPage->nCurrentPage > 1)
    186. {
    187. system("cls");
    188. pPage->nCurrentPage--;
    189. ShowInfo(pHead, pPage);
    190. ShowMenu(pPage);
    191. }
    192. else printf("已经是第一页了...\n");
    193. break;
    194. case 's':
    195. if (pPage->nCurrentPage < pPage->nTotalPage)
    196. {
    197. system("cls");
    198. pPage->nCurrentPage++;
    199. ShowInfo(pHead, pPage);
    200. ShowMenu(pPage);
    201. }
    202. else printf("已经是最后一页了...\n");
    203. break;
    204. case 'b':
    205. return;
    206. break;
    207. case 'c':
    208. return;
    209. break;
    210. default:
    211. printf("按错了哦...\n");
    212. break;
    213. }
    214. scanf_s(" %c", &c);//多加空格为过滤空白字符如\n
    215. g_Key = c;
    216. }
    217. }
    218. //浏览封装
    219. void Browse(List* pHead)
    220. {
    221. Page* pPage = GetPage(pHead);
    222. TurnPage(pHead, pPage);
    223. free(pPage);
    224. pPage = NULL;
    225. }
    226. //手动输入
    227. List* GetNodeIn()
    228. {
    229. List* pTemp = (List*)malloc(sizeof(List));
    230. if (pTemp)
    231. {
    232. pTemp->nID = GetID();
    233. printf("请输入姓名:\n");
    234. pTemp->Name = Getstring();
    235. printf("请输入电话号:\n");
    236. pTemp->Tel = Getstring();
    237. pTemp->pNext = NULL;
    238. }
    239. return pTemp;
    240. }
    241. //查询
    242. void Search(List* pHead)
    243. {
    244. List* pMark = pHead;
    245. List* New_pHead = NULL;
    246. List* New_pEnd = NULL;
    247. while (1)
    248. {
    249. char* str = NULL;
    250. while (1)
    251. {
    252. printf("请输入关键字:\n");
    253. str = Getstring();
    254. printf("按a确认 任意键重新输入\n");
    255. char a;
    256. scanf_s("%c", &a);
    257. if (a == 'a')break;
    258. else
    259. {
    260. free(str);
    261. str = NULL;
    262. }
    263. }
    264. //匹配
    265. pHead = pMark;
    266. while (pHead != NULL)
    267. {
    268. if (strncmp(str, pHead->Name, strlen(str)) == 0 || strncmp(str, pHead->Tel, strlen(str)) == 0)
    269. {
    270. List* pTemp = malloc(sizeof(List));
    271. if (pTemp)
    272. {
    273. pTemp->nID = pHead->nID;
    274. pTemp->Name = pHead->Name;
    275. pTemp->Tel = pHead->Tel;
    276. pTemp->pNext = NULL;
    277. AddNode(&New_pHead, &New_pEnd, pTemp);
    278. }
    279. }
    280. pHead = pHead->pNext;
    281. }
    282. //显示
    283. Browse(New_pHead);
    284. //释放新链表
    285. while (New_pHead != NULL)
    286. {
    287. List* pTemp = New_pHead;
    288. if (pTemp)
    289. {
    290. New_pHead = New_pHead->pNext;
    291. free(pTemp);
    292. pTemp = NULL;
    293. }
    294. }
    295. New_pEnd = NULL;
    296. free(str);
    297. str = NULL;
    298. if (g_Key == 'b')
    299. break;
    300. }
    301. }
    302. //主函数
    303. int main()
    304. {
    305. List* pHead = NULL;
    306. List* pEnd = NULL;
    307. InitInfo(&pHead, &pEnd, 100);
    308. while (1)
    309. {
    310. system("cls");
    311. printf("1.浏览信息\n");
    312. printf("2.添加信息\n");
    313. printf("3.查询信息\n");
    314. printf("q.退出\n");
    315. char a;
    316. scanf_s(" %c", &a);
    317. switch (a)
    318. {
    319. case '1':
    320. g_Menutype = 1;
    321. Browse(pHead);
    322. break;
    323. case '2':
    324. AddNode(&pHead, &pEnd, GetNodeIn());
    325. break;
    326. case '3':
    327. g_Menutype = 3;
    328. Search(pHead);
    329. break;
    330. case 'q':
    331. return;
    332. break;
    333. }
    334. }
    335. return 0;
    336. }

    功能介绍

    根据主函数我们可以得知我们一共要实现3个大功能:

    1.浏览信息

    联系人结构体定义

    首先定义一个节点的结构体,我们把一个联系人作为一个节点,假定一个节点有id,Name,Tel以及指向下个节点的指针pNext,元素分别代表人数id需要自动生成,Name姓名,Tel电话号,并typedef节点名为List。

    1. //节点结构体
    2. typedef struct Node
    3. {
    4. int nID;
    5. char* Name;
    6. char* Tel;
    7. struct Node* pNext;
    8. }List;

     初始化id信息函数

    这里我们需要用到静态局部变量,因为静态局部变量在一次程序中只会初始化一次,故可以很方便的自动计算出人数

    1. //自动获取id
    2. int GetID()
    3. {
    4. static int a = 0;//静态变量只会初始化一次
    5. a++;
    6. return a;
    7. }

    初始化Name信息函数

    作为测试数据,人名随机生成即可,那么我们以5个字母为一个人名,每个人名随机生成,这里需要用到rand()函数,并且为了保证接近真随机需要用srand函数以time为种子,会在封装时设置srand,if(str)的作用是用于检验防止str为空,防止后续调用内存出现问题

    1. //自动生成测试姓名
    2. char* GetName()
    3. {
    4. char* str = malloc(sizeof(char) * 6);
    5. //申请6个字节(char型占一字节)堆区空间
    6. if (str) //检验申请的堆区空间是否为空,以防下边出现越界等情况
    7. {
    8. for (int i = 0; i < 5; i++)
    9. str[i] = rand() % 26 + 'a'; //对26取余,可以随机到0-25
    10. //此处字符a会转换成ASCII码值从97开始
    11. //即最小为97+0=97,代表字母a
    12. //最大为97+25=122,代表字母z
    13. str[5] = '\0'; //将下标为5的位置即第六个字节设置为'\0',代表字符串结束
    14. }
    15. return str; //返回str
    16. }

    初始化Tel信息函数

    对于自动生成的电话号码,我们假定有133、155、177、188,从而便于我们模糊搜索测试功能,此处我们要随机在这四个号码里生成,依然用到rand()函数,同时由于电话号码也是字符串,我们假设电话号码都是11位,所以定义一个长度为12的堆区空间,来保证最后一位放‘\0’

    1. //自动生成电话号码
    2. char* GetTel()
    3. {
    4. char* str = malloc(12); //同理如上
    5. if (str)
    6. {
    7. switch (rand() % 4) //随机一个0-3的数字
    8. //以便选择是那个前缀开头的电话号
    9. {
    10. case 0: //也可以用strncpy函数,但记得写头文件
    11. str[0] = '1';
    12. str[1] = '3';
    13. str[2] = '3';
    14. break; //133
    15. case 1:
    16. str[0] = '1';
    17. str[1] = '5';
    18. str[2] = '5';
    19. break; //155
    20. case 2:
    21. str[0] = '1';
    22. str[1] = '7';
    23. str[2] = '7';
    24. break; //177
    25. case 3:
    26. str[0] = '1';
    27. str[1] = '8';
    28. str[2] = '8';
    29. break; //188
    30. }
    31. for (int i = 3; i < 11; i++)
    32. //其余的从下标为3的位置,即第四个字节。开始继续随机填入数字0-10
    33. str[i] = rand() % 10 + '0';
    34. str[11] = '\0';
    35. }
    36. return str;
    37. }

     开辟节点空间

    需要测试生成用的数据的函数写完了,那么我们开始为每个节点开辟空间,如下

    1. //开辟空间
    2. List* GetNode()
    3. {
    4. List* pTemp = malloc(sizeof(List));
    5. //c语言可以不用强制类型转换,c++记得加
    6. if (pTemp)
    7. {
    8. pTemp->nID = GetID(); //分别自动通过函数获取数据
    9. pTemp->Name = GetName();
    10. pTemp->Tel = GetTel();
    11. pTemp->pNext = NULL;
    12. }
    13. return pTemp; //最终返回节点
    14. }

     添加节点函数

    此处需要重点注意,这里比较难理解,第二种情况我进行了画图解释,我们使用了二重指针,我们需要调用头指针尾指针的地址而不改变他们本身指向的数据,所以我们使用二重指针,利用地址的地址调用指针的地址

    同时,我们要考虑到节点在添加时有两种情况,一种是链表中任何数据都没有,二是链表中已经有数据了

    情况1(没有数据):让头结点等于新来节点即可,然后让尾结点也等于新来节点

    情况2:在已经存在数据的情况下如何插入结尾呢?我们在写链表时一般会保持一个原则:先连接,后断开。那么根据这个原则,我们只能把新来节点连接到尾结点后边,即(*pEnd)->pNext = pNode;为什么要这样写呢,因为我们知道  ->  符号代表 间接引用 + .  的作用,所以这行代码相当于对pEnd进行两次间接引用所以也可以写成(**pEnd)->pNext=pNode; 则直接使用的就是 4 对应的节点,那么这时完成连接之后我们可以断开pEnd与最后一个节点的联系,让pEnd指向新来节点,让其成为尾结点,而指向原来节点的指针不会因为pEnd指向改变而改变

    1. //添加节点
    2. void AddNode(List** pHead, List** pEnd, List* pNode)
    3. {
    4. if (*pHead == NULL)*pHead = pNode;
    5. else (*pEnd)->pNext = pNode;
    6. *pEnd = pNode;
    7. }

    信息初始化封装函数

    由于此处需要用到前边写的AddNode函数所以为了正常传参直接使用二重指针,则使用时参数直接写pHead,pEnd就可以,已经是需要传入的参数类型了,如果参数类型不会看,在之后的文章里会再做讲解,此处不过多赘述,至于新的节点直接开辟使用GetNode()函数,开辟多少个我们自己输入就可以,传参n即可

    此外此处还涉及到srand()函数,因为我们在开辟空间初始化信息时会用到rand函数,但是需要设置种子来保证接近真随机,则我们设置为time函数,为了防止内存泄漏,需要强制类型转换为unsigned int,对于srand函数的使用方法之后我也会在其他文章中写到,但也需要time.h函数才可使用time函数

    1. //信息初始化封装
    2. void InitInfo(List** pHead, List** pEnd, int n)
    3. {
    4. srand((unsigned int)time(NULL));
    5. for (int i = 0; i < n; i++)
    6. AddNode(pHead, pEnd, GetNode());
    7. }

     页数结构体定义

    接下来我们要制作翻页功能,首先是定义结构体如下: 

    1. //页数结构体
    2. typedef struct PAGE
    3. {
    4. int nCurrentPage; //当前页
    5. int OnePageInfo; //每页信息数量
    6. int nTotalInfo; //信息总数
    7. int nTotalPage; //总页数
    8. }Page;

    计算页数函数

    为了防止数据量过大导致输出数据的时候过于多,不清晰,我们需要进行分页展示,那我们就我们设置10名联系人为一页当前页数设置为0,随着用户操作翻页增长总信息数随着节点增加而增加总页数随着节点数量变化而变化,即总页数=总信息数/每页信息数量

    这时又有小伙伴要问了:为什么当前页设置为0呢,此处埋下一个包袱,请接下来看下去,后边我会解答,都看到这了,你肯定也很想了解在这个通讯录运行原理吧AwA

    然后我们开始遍历链表,计算有多少节点,即多少条信息,同时为了防止由于pHead向下移动而导致无法后退找不到前边节点所以我们选择在该作用域内创建一个临时的标记用指针来遍历链表,通过保存链表中pHead来实现

    最后我们计算总页数,注意除法是整除,若存在余数需要再加一页以保证展示的数据不会缺少,此处我们使用判断语句就好,如果会使用三目运算符也可以使用,只不过会有亿点点长

    此处代码不进行注释

    1. //计算页数
    2. Page* GetPage(List* pHead)
    3. {
    4. Page* pPage = malloc(sizeof(Page));
    5. if (pPage)
    6. {
    7. pPage->nCurrentPage = 0;
    8. pPage->OnePageInfo = 10;
    9. pPage->nTotalInfo = 0;
    10. List* pMark = pHead;
    11. while (pMark != NULL)
    12. {
    13. pPage->nTotalInfo++;
    14. pMark = pMark->pNext;
    15. }
    16. if (pPage->nTotalInfo % pPage->OnePageInfo == 0)
    17. pPage->nTotalPage = pPage->nTotalInfo / pPage->OnePageInfo;
    18. else pPage->nTotalPage = pPage->nTotalInfo / pPage->OnePageInfo + 1;
    19. }
    20. return pPage;
    21. }

    展示页小菜单

     此处会有一个选择函数是因为之后搜索我们会需要使用重新搜索功能,故需要另一个菜单来展示不同页面的选项(虽然现在的通讯录随时都能查询,已经不需要了,为了练习代码能力还是要写一下的)对于条件的判断,我们需要先创建一个全局变量,即g_Menutype,来保存我们在第一次主菜单的选择,在主菜单我们若是从浏览信息进入的则选择1,若是查询口进入我们则选择3,从而展示不同页面

    1. //菜单
    2. void ShowMenu(Page* pPage)
    3. {
    4. switch (g_Menutype)
    5. {
    6. case 1:
    7. printf("\n当前第%d页 共%d页 共%d条", pPage->nCurrentPage, pPage->nTotalPage, pPage->nTotalInfo);
    8. printf("\nw上一页 s下一页 b返回\n");
    9. break;
    10. case 3:
    11. printf("\n当前第%d页 共%d页 共%d条", pPage->nCurrentPage, pPage->nTotalPage, pPage->nTotalInfo);
    12. printf("\nw上一页 s下一页 b返回 c重新查询\n");
    13. break;
    14. }
    15. }

     信息展示

     下面则是正片,我们写了那么多函数,终于可以看到一个具有雏形的通讯录了

    首先我们我们既然要只展示10条信息,我们会发现一件事情,我们的当前页是不确定的,因为我们需要跟着用户翻页而展示不同信息,换句话说,我们需要获取当前页是多少来确定我们输出多少到多少条信息,而当前页是用户操作,所以我们打不过就加入的原则(梗),我们直接使用用户翻到的当前页来计算我们从哪里开始输出,也就是第几个节点开始输出,通过简单推理得出这个数学式子,即初始位置=(当前页-1)*每页信息书+1,那么下界我们更好确定,直接用当前页*10即是要输出的最后一条信息

    接下来我们选择while循环遍历链表来输出,判断头结点是否在上下界内,如果在则输出,,注意不要自作聪明填else break;会导致如果不是第一页的直接不输出任何节点,因为如果到第二页还会从头遍历,但却不在上下界内,判断条件不符合直接向下进行else break,退出循环,这是无意义的(我犯过错的说)

    1. //信息展示
    2. void ShowInfo(List* pHead, Page* pPage)
    3. {
    4. int nBegin = (pPage->nCurrentPage - 1) * pPage->OnePageInfo + 1;
    5. int nEnd = pPage->nCurrentPage * pPage->OnePageInfo;
    6. int nCount = 0;
    7. while (pHead != NULL)
    8. {
    9. nCount++;
    10. if (nCount >= nBegin && nCount <= nEnd)
    11. //判断头结点走到的位置是否在上下界内
    12. printf("%d %s %s\n", pHead->nID, pHead->Name, pHead->Tel);
    13. pHead = pHead->pNext;
    14. }
    15. }

    翻页功能

     既然写完了展示功能,光画饼的翻页功能依然要实现

    首先我们把包袱抖出来,之前我们在设置当前页为0是因为我们选择完浏览信息需要直接展示出第一页的信息,但是我们为了避免过于麻烦(如果不信可以在此基础上自己改一改看看哪个麻烦,我也没写过,跟人感觉另一种会有些麻烦),合并入翻页系统,从0页自动先翻到第一页,在进行下一步用户操作

    同时此处用到第二个全局变量g_Key,用于保存c的值,以防退出到上一级函数因为c已经是局部变量并不能使用而,使程序运行出问题(一般出问题的就在查询函数出的多一点)

    以下代码我会写注释

    1. //翻页功能
    2. void TurnPage(List* pHead, Page* pPage)
    3. {
    4. char c = 's';//默认向下翻一页,因为一开始当前页为0
    5. while (1) //保证该次翻页之后还能再次翻页
    6. {
    7. switch (c)
    8. {
    9. case 'w':
    10. if (pPage->nCurrentPage > 1) //大于第一页就可以翻页
    11. {
    12. system("cls"); //清屏指令
    13. pPage->nCurrentPage--; //向前翻当前页-1
    14. ShowInfo(pHead, pPage); //展示数据
    15. ShowMenu(pPage); //展示小菜单
    16. }
    17. else printf("已经是第一页了...\n"); //否则输出该句话,且不翻页
    18. break;
    19. case 's':
    20. if (pPage->nCurrentPage < pPage->nTotalPage)
    21. //小于最后一页就可以翻页
    22. //由于最后一页不确定是多少,所以选择直接使用总页数
    23. //以下同理向前翻页
    24. {
    25. system("cls");
    26. pPage->nCurrentPage++;
    27. ShowInfo(pHead, pPage);
    28. ShowMenu(pPage);
    29. }
    30. else printf("已经是最后一页了...\n");
    31. break;
    32. case 'b': //返回主页
    33. return;
    34. break;
    35. case 'c': //返回查询函数
    36. return;
    37. break;
    38. default: //以上选择都没有满足则默认选择运行
    39. printf("按错了哦...\n");
    40. break;
    41. }
    42. scanf_s(" %c", &c); //多加空格为过滤空白字符如\n
    43. g_Key = c; //记录c的字符,g_Key是全局变量
    44. //以防退出循环到上一级函数,由于c已经在该作用域消亡而导致运行出问题
    45. }
    46. }

     浏览功能封装

    至此,我们的浏览功能已经完成,看到这里的朋友,恭喜你,你已完成了这个小程序的80%

    这里我们传参只要传一个头结点就好,因为我们封装的功能只用得到头结点

    1. //浏览封装
    2. void Browse(List* pHead)
    3. {
    4. Page* pPage = GetPage(pHead); //定义一个pPage节点
    5. TurnPage(pHead, pPage); //可以调出第一页,直接调用该函数
    6. free(pPage); //释放开辟的pPage堆区空间
    7. pPage = NULL; //赋空
    8. }

    2.添加信息

     字符串获取函数(注意)

    到了这里一定要注意,有小伙伴会问,为什么不用scanf函数?

    因为我们下边在搜索或输入信息的时候往往长度是不固定的,而字符串的输入方式我们首先想到的是常见的两种,一是定义数组,通过循环函数不断用下标一个个输入数据,而且数据长度有限;二是使用char*指针,而我们为了防止出现内存泄露等问题,写一个可以随我们数据的长度自动增长的字符串获取函数,此处我们除了开辟堆区空间外,还需用到realloc函数,用来扩展堆区空间,realloc函数本质上是对已有的堆区空间重新开辟,但在这里我们则可以用它这个性质来扩展已有的堆区空间

    在堆区空间如何输入字符串呢?我们采取while循环,,由于输入长度并不确定,所以我们采取现将输入的字符串放进缓存区,然后从缓存区将字符在一个个拿出来放进定义的char*变量里,即c=getchar(),这里我们发现在while循环判断条件里,对于c的getchar多用了一个括号,这里要注意优先级,赋值符号优先级要低于判断符号,所以如果不加括号就会有限进行getchar与'\n'进行比较,就会导致c为乱码,自动认为判断c的值真假,从而使程序出问题

    另外scanf(“ ”);这句话作用是用来过滤空白字符,在我们重新查询信息的时候,回车会被当做字符进入缓存区,为了过滤掉整个回车我们选择这样的方式来解决

    1. //字符串获取
    2. char* Getstring()
    3. {
    4. int nSize = 5;
    5. char* str = malloc(nSize); //要输入的字符串
    6. int nCount = 0;
    7. char c;
    8. scanf_s(" "); //过滤空白字符!!!以防下次输入出现问题
    9. if (str)
    10. {
    11. while ((c = getchar()) != '\n') //注意优先级
    12. {
    13. str[nCount] = c; //将缓冲区的字符一个个放进去
    14. nCount++; //下标移动
    15. if (nCount == nSize)
    16. str = realloc(str, nSize += 5); //扩展堆区空间
    17. }
    18. str[nCount] = '\0'; //使其变成字符串
    19. }
    20. return str;
    21. }

     手动输入函数

    该函数跟初始化节点时只把初始化姓名函数GetName()和初始化电话号函数GetTel()变成了Getstring()函数,以便我们添加信息,很简单,不进行注释

    1. //手动输入
    2. List* GetNodeIn()
    3. {
    4. List* pTemp = (List*)malloc(sizeof(List));
    5. if (pTemp)
    6. {
    7. pTemp->nID = GetID();
    8. printf("请输入姓名:\n");
    9. pTemp->Name = Getstring();
    10. printf("请输入电话号:\n");
    11. pTemp->Tel = Getstring();
    12. pTemp->pNext = NULL;
    13. }
    14. return pTemp;
    15. }

    3.查询信息

    我们可以看到这个函数,对于刚学完c的我们来说它十——分——的长,且复杂

    这个函数要实现对于姓名和电话号码的模糊查询

    那么我来拆分一下该函数,一一讲解

    首先,我们函数要实现模糊查询,需要输入我们要查询的字符串,我们先定义一个char*型变量str,然后鉴于我们用户的手不是很好,有的时候可能会打错要输入的数据,要做一个保险,问用户是否确定要搜索该内容,如果否则重新输入,那么我们使用死循环while(1)来保证,如果确认按下a则退出循环,否则释放已输入的str重新输入

    其次,匹配用户输入的关键字,为了防止遍历一次之后下次遍历找不到第一个结点,依然使用标记指针pMark来存pHead,同时由于我们需要查询之后输出对应关键字的信息,我们需要重新建立一个新的链表,那么我们设置New_pHead,New_End,即新头结点,新尾结点。此外还要注意我们是要写能进行多次查询的查询功能,这才是好查询,所以每次匹配关键词我们都要重置pHead,所以要掏出我们函数开头存pHead的pMark,这回赋给已经走完的pHead,即重置完成

    再次,我们在输出形成新链表的时候,遍历链表时,需要判断是否满足与姓名或电话号其中一个的部分相同,所以我们选择使用strncmp函数,该函数如果判断相同,则得出0,判断条件则写该函数计算结果是否等于0,中间或  ||  符号连接,满足一个即可将该节点放进新的链表中

    最后,我们展示信息,调用Browse函数,展示完我们将新的链表释放掉已经没有用了,通过遍历链表将每个节点释放掉

    有人会问:为什么不直接释放掉新链表的链表头呢,这样不就都没了吗?

    解答:因为每一个节点都是一个堆区空间,所以我们只是放了头结点,其他的依然在,占用空间,一旦数据量过大,查询次数多会导致死机cpu占用率过高,所以我们常常要释放掉每一个不用的堆区空间

    同时释放掉关键词字符串str,为下一次查询做好准备以防造成内存泄露,最后在我们进行查询的是,会出现一个问题就是我们会发现我们退不出去了,卡在最外层while死循环中,然后程序卡出bug,这里g_Key的用处即是如果输入b退出,而在Browse函数中TurnPage由于无返回,c只是局部变量并不可以在外部使用,所以我们的g_Key的作用便是存下c的值如果是字符b就退出最外层循环,从而不会卡在外层循环,而且最重要的是注意好大括号范围,break只能退出一层循环

    1. //查询
    2. void Search(List* pHead)
    3. {
    4. List* pMark = pHead; //存头结点位置
    5. List* New_pHead = NULL; //新链表头尾结点定义
    6. List* New_pEnd = NULL;
    7. while (1) //死循环保证能多次查询
    8. {
    9. char* str = NULL;
    10. while (1) //死循环保证能多次输入,给用户容错
    11. {
    12. printf("请输入关键字:\n");
    13. str = Getstring(); //使用字符串获取要输入的字符串
    14. //因为模糊搜索输入长度并不固定
    15. printf("按a确认 任意键重新输入\n");
    16. char a;
    17. scanf_s("%c", &a);
    18. if (a == 'a')break;
    19. else
    20. {
    21. free(str);
    22. str = NULL;
    23. }
    24. }
    25. //匹配用户输入的关键字 依然是GetNode翻版,然后使用AddNode填进新链表
    26. pHead = pMark;
    27. while (pHead != NULL)
    28. {
    29. if (strncmp(str, pHead->Name, strlen(str)) == 0 || strncmp(str, pHead->Tel, strlen(str)) == 0)
    30. {
    31. List* pTemp = malloc(sizeof(List));
    32. if (pTemp)
    33. {
    34. pTemp->nID = pHead->nID;
    35. pTemp->Name = pHead->Name;
    36. pTemp->Tel = pHead->Tel;
    37. pTemp->pNext = NULL;
    38. AddNode(&New_pHead, &New_pEnd, pTemp);
    39. }
    40. }
    41. pHead = pHead->pNext;
    42. }
    43. //显示
    44. Browse(New_pHead);
    45. //释放新链表
    46. while (New_pHead != NULL)
    47. {
    48. List* pTemp = New_pHead;
    49. if (pTemp)
    50. {
    51. New_pHead = New_pHead->pNext;
    52. free(pTemp);
    53. pTemp = NULL;
    54. }
    55. }
    56. New_pEnd = NULL;
    57. free(str);
    58. str = NULL;
    59. if (g_Key == 'b') //用于退出外层循环
    60. break;
    61. }
    62. }

     4.主函数

     至此主函数写完,整个程序结束,感谢你看到这里,朋友,后面还有总结,如果需要可以看一下,麻烦给个赞和关注,多谢!

    1. //主函数
    2. int main()
    3. {
    4. List* pHead = NULL;
    5. List* pEnd = NULL;
    6. InitInfo(&pHead, &pEnd, 100); //自动生成数据100条
    7. while (1)
    8. {
    9. system("cls");
    10. printf("1.浏览信息\n"); //主菜单展示
    11. printf("2.添加信息\n");
    12. printf("3.查询信息\n");
    13. printf("q.退出\n");
    14. char a;
    15. scanf_s(" %c", &a); //输入选择
    16. switch (a)
    17. {
    18. case '1':
    19. g_Menutype = 1;
    20. Browse(pHead);
    21. break;
    22. case '2':
    23. AddNode(&pHead, &pEnd, GetNodeIn());
    24. break;
    25. case '3':
    26. g_Menutype = 3;
    27. Search(pHead);
    28. break;
    29. case 'q':
    30. return;
    31. break;
    32. }
    33. }
    34. return 0;
    35. }

    总结

     在这个程序中,我们能看得到功能不多,函数功能却很复杂,十分考验初学者的综合能力,在这里一些建议

    1.多写多练习这个程序

    2.Getstring函数尽量背下来

    3.要能充分理解每一块函数的作用

    4.联系各种函数的关系,要能理解透彻

    做到以上四点,你的编程能力会提升一大截,然后可以再去尝试入门其他语言,因为此时你对编程语言的体系结构已经基本了解,其余细化知识点还需自己慢慢深入学习

    后记

    给个点赞关注谢谢!!!!!爱你们!!!

  • 相关阅读:
    Go 并发编程 - 并发安全(二)
    Springboot毕设项目办公用品在线销售系统25f35(java+VUE+Mybatis+Maven+Mysql)
    操作系统--------调度算法篇
    神经网络图片怎么分类的,图神经网络怎么做分类
    搭建 3D 智慧农场可视化
    虚拟机CentOS 8 重启后不能上网
    感知+平台+应用:忽米打造4款工业智能硬件产品
    【Redis】简单动态字符串SDS
    vue-element-admin动态菜单(后台获取)
    用Python来表白,把情书写进她的照片里
  • 原文地址:https://blog.csdn.net/Bing_ning_/article/details/132248717