这一篇中,我们继续丰富前面的代码。Record类型已经定义完成,为了方便赋值我们还需要添加一些小功能。
Record数据类型修改
-
Record.h文件
#ifndef __RECORD_H__ #define __RECORD_H__ #include "String.h" typedef struct _tagRecord { String* _pStrName; String* _pStrTel; String* _pStrPS; }Record; void RecordInit(Record** pR); void RecordDestroy(Record* pR); void RecordSetName(Record* pR, char* pBuf); void RecordSetTel(Record* pR, char* pBuf); void RecordSetPS(Record* pR, char* pBuf); void RecordPrint(Record* pR); #endif
-
Record.c文件
#include "Record.h" void RecordInit(Record** pR) { *pR = (Record*)malloc(sizeof(Record)); (*pR)->_pStrName = StringCreate(); (*pR)->_pStrTel = StringCreate(); (*pR)->_pStrPS = StringCreate(); } void RecordDestroy(Record* pR) { if (pR != NULL) { StringDestroy(pR->_pStrName); StringDestroy(pR->_pStrTel); StringDestroy(pR->_pStrPS); free(pR); } } void RecordSetName(Record* pR, char* pBuf) { StringSet(pR->_pStrName, pBuf); } void RecordSetTel(Record* pR, char* pBuf) { StringSet(pR->_pStrTel, pBuf); } void RecordSetPS(Record* pR, char* pBuf) { StringSet(pR->_pStrPS, pBuf); } void RecordPrint(Record* pR) { printf("----------------\n"); printf("通讯录\n"); printf("姓名:%s\n", StringGetBuffer(pR->_pStrName)); printf("电话:%s\n", StringGetBuffer(pR->_pStrTel)); printf("备注:%s\n", StringGetBuffer(pR->_pStrPS)); printf("----------------\n"); }
新添加的三个函数是为了方便给Record类型的三个成员变量赋值。有了它们,赋值语句就简化为:
RecordSetName(pR, str);
结构简单,意思明确。如今的程序设计中,大家更推崇写出像注释一样意义明确的代码,就是这个意思。
函数RecordPrint()的功能是打印一条完整的通讯录,这种功能会出现在很多不同的地方,如果每次都把这六个printf敲一遍会非常复杂,而且每次修改都要同时修改很多处,很容易出错。我们现在把它写成一个独立的函数,这样如果需要修改输出格式,只要修改这个函数所有调用的地方就都跟着修改了。是不是很方便呢?
Record类型的使用
通过我们之前的修改,Record类型已经基本符合我们的需求了,接下来我们看看这个类型能够帮助我们做些什么。
下面是我们修改过的main.c文件:
#include <stdio.h>
#include "Record.h"
void CleanScreen();
void CreateRecord();
void SearchRecord();
int main()
{
int nMenu = 0;
while (1)
{
printf("==== 通讯录 ====\n");
printf("1. 录入\n");
printf("2. 查询\n");
printf("0. 退出\n");
printf("----------------\n");
printf("请输入要使用的功能:");
scanf("%d", &nMenu);
switch (nMenu)
{
case 0:
// 退出
exit(1);
break;
case 1:
// 新增
CreateRecord();
break;
case 2:
// 查询
SearchRecord();
break;
default:
break;
}
CleanScreen();
}
return 0;
}
// 新增记录
void CreateRecord()
{
char str[50];
int loop = 1;
int t;
while (loop)
{
gets(str); // Remove '\t'
CleanScreen();
Record* pR = NULL;
RecordInit(&pR);
printf("==== 新增条目 ====\n\n");
printf("请输入下面的信息:\n");
printf("姓名:");
gets(str);
RecordSetName(pR, str);
printf("电话:");
gets(str);
RecordSetTel(pR, str);
printf("备注:");
gets(str);
RecordSetPS(pR, str);
printf("\n\n");
printf("-------------------\n");
printf("输入内容:\n");
printf("-------------------\n");
printf("姓名:%s\n", StringGetBuffer(pR->_pStrName));
printf("电话:%s\n", StringGetBuffer(pR->_pStrTel));
printf("备注:%s\n", StringGetBuffer(pR->_pStrPS));
printf("是否保存(y/n):");
if (getchar() == 'y')
{
// 保存记录
}
////
printf("\n下一步\n");
printf("1. 继续录入\n");
printf("0. 返回\n");
printf("----------------\n");
printf("请输入要使用的功能:");
scanf("%d", &t);
if (t == 1)
{
// Do nothing
}
else
{
loop = 0;
}
}
}
// 查询记录
void SearchRecord()
{
// 现实全部
// 查找具体条目
}
void CleanScreen()
{
system("cls");
}
我们实现了录入功能,通过命令行输入一条完整的记录。
这里我们在打印时没有用RecordPrint()这个函数,请大家想想如何在main函数中使用RecordPrint()函数?
此时,我们录入的记录被保存在pR中,在一次录入循环结束之前,我们需要通过一个全局的数据结构把它保存起来,下面我们看看如何实现。
通讯录数据结构
1.分析
仔细分析一下我们的需求,我们需要的是一个能够保存N个Record的数据结构,这个数据结构要能够支持任意位置的增、删、改、查。我们很容易想到的是动态数组和链表。
有数据结构基础的朋友一定会知道,如果用动态数组,数据删除时的搬移会非常麻烦,因此我们这里通过实现一个链表来完成通讯录的存储。
2. 结构体定义
要实现链表,首先需要做的是实现一个Node的数据结构。我们目前有两个选择:
-
方案1: 在现有的结构体Record中加两个指针来做节点数据结构。定义如下:
typedef struct _tagRecord { String* _pStrName; String* _pStrTel; String* _pStrPS; struct _tagRecord *_pPre; struct _tagRecord *_pNext; }Record;
这样做的好处显而易见,就是方便。但为了我们的代码显得更有逻辑性,我们还有另一个选择。
-
方案2:重新实现一个结构体。新建文件ListNode.h,加入下面代码:
#ifndef __LIST_NODE_H__ #define __LIST_NODE_H__ #include "Record.h" typedef struct _tagListNode { Record* _pR; struct _tagListNode* _pPre; struct _tagListNode* _pNext; }ListNode; ListNode* g_pL; ListNode* g_pCur; int g_ListCnt; void ListsInit(); void ListDestroy(); void ListAdd(Record* pR); void ListDel(ListNode* pNode); int ListCnt(); void ListTraverShow(); #endif
这个头文件中定义了一个双向循环列表的节点结构体,同时还给出了操作函数的定义。这样我们有通过这个头文件定义了一个相对独立的链表节点。
练习题
今天的练习就是将ListNode.h中定义的几个函数自己实现在ListNode.c文件中。
下一篇中,我们会详细介绍这个双向链表的实现方法。
我是天花板,让我们一起在软件开发中自我迭代。
如有任何问题,欢迎与我联系。
上一篇:21天C语言代码训练营(第十一天)
下一篇:21天C语言代码训练营(第十三天)