我想先简单说明一下,本文是按照本人的思路来走的,不是像那些参考书那样直接给出整个答案。所以我会慢慢引导读者搭构出思路,然后一步一步逻辑清晰地写出来.
1. 代码整体框架
2. 功能函数的实现
2.1 添加人员信息函数AddContact()-附带基本框架的搭构
上述代码是放在Contact.h 头文件中的 你现在如果搞清楚上面的步骤后,需要拉回自己的思维,现在我们是为了实现AddContact函数的功能。 但是还有一点工作要提前准备:我们的程序什么时候才能走到这个函数?
AddContact函数的参数是什么也需要考虑? 考虑到其功能——添加信息,那么就肯定涉及修改结构体对象,那么就需要传址。 所以其参数部分应该是Contact结构体类型所创建的对象的地址。 那么接下来根据上述内容就可以写出main函数的基本框架了。
改良的部分有:将switch……case分支语句中的数字,如case 1、case 2 之类的case后面的数字,改成枚举常量,当然,各个枚举常量对应的值要正好对应用户输入的选项。然后这个自定义的枚举类型可以放到Contact.h 头文件中,还有menu函数因为封装的缘故,还是需要把定义放在Contact.c中,函数声明放在Contact.h中。
对于其他功能函数的参数,尽管有些功能函数是不需要对结构体对象进行修改,但是我依旧选择了传址,这是由于传值(没打错字!)会产生的临时拷贝而造成的压栈开销过大,内存利用率降低的原因。
所以搞定了整个main函数的框架后终于可以开始逐个击破了,虽然我觉得上述部分应该写在第一个部分,但是这是为了引导读者一步一步来,所产生的必然的逻辑。 如此看来,还需要一件最重要的事,那就是——初始化 这里仍然可以封装一个函数InitContact,其参数仍然为con的地址,实现的功能是需要把sz即代表当前信息个数赋值为0,把整个通讯录中用于存放信息的空间也都赋值为0。 上述操作可以使用memset函数,头文件为string.h,用于将指定内存块区域设置为某一值,有三个参数,第一个为所要修改内存的首地址,第二个是所要修改的值,第三个是所要修改的字节数。
void InitContact(Contact* pc)
{
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
接下来我们就正式开始完成Addcontact函数 功能如下:
以下为AddContact函数的定义,放在Contact.c文件中,其函数声明仍需要放在Contact.h文件中。 关于录入信息的一些难点,首先是字符串的录入,双引号里一定是%s,逗号后的参数部分是一个地址,也就是数组首元素地址,即数组名,所以我们只需要在逗号后写上数组名即可。但是在结构体中就比较复杂了,就拿录入姓名举例,pc->data 代表main函数中所创建的Contact结构体对象con中的成员data的地址,那么在其后面加上[]就可以代表data数组的第几个元素了,那么[]中的pc->sz就是代表con中的成员sz的值了(注意这里是值而不是地址),那么pc->data[pc->sz]就表示在PeoInfo结构体类型所创建的结构体数组对象data的第sz+1个元素,那么录入姓名,就需要加上.name,最终pc->data[pc->sz].name就代表名字数组的首元素地址。 然后是数字的录入,比如年龄,scanf双引号内的参数为%d,逗号后的参数为对应整形的地址,那么需要在在完成找到这个整形后加上&,具体步骤只是和上述录入字符串的步骤增加了最后一步。
2.2 删除联系人的信息
以下是DelContact函数的定义,同样需要把定义放于Contact.c中,函数声明放置于Contact.h中,接下来的函数就不再赘述有关代码放置于某某文件的详情了。 可以看到,宏定义的常量又一次起到了作用,MAX_NAME代表名字的字符所占最大的长度,当然也包括了\0。 根据输入的名字来查找通讯录中是否包含这个名字的这个功能,也依旧被封装为了一个函数,这个函数有些不同,是static修饰的静态函数,只能在当前头文件中使用,所以无需再把函数声明写在Contact.h的头文件中。这样写的目的也是为了防止函数的随意调用所导致的污染。 查找字符串是遍历了所有的含有有效信息的数组元素,利用strcmp函数进行比较。
2.3 搜索联系人信息
(1)根据姓名来查找 (2)打印出标题来:姓名 年龄 性别 电话 住址 (3)在标题下打印对应此人的信息
需要简单提及的知识点:printf函数双引号内的参数部分可以在%和字母之间加上数字,-20代表左对齐且占20长度,正数代表右对齐。 匹配名字依然用上一个功能函数DelContact所用的FindByName函数 还有注意标题的打印都是打印字符串,即%s,而信息打印中的数字是%d
2.4 修改联系人信息
(1)输入要修改者的名字 (2)查找该名字是否存在 (3)利用返回的下标进行直接scanf录入修改即可 同样需要注意录入年龄时需要去地址,即加上&
2.5 根据姓名排序整个通讯录
算法等同与冒泡排序,需要注意两个由相同结构体所创建的对象,是可以进行直接赋值的,比如PeoInfo创建的tmp与data数组中的元素可以进行直接赋值,尽管每个对象都有多个成员(姓名、年龄、性别等)。
2.6 清除整个通讯录等信息
清除整个通讯录当然需要两次确认,否则一不小心手滑给全删了,当然还可以改进加上保护密码。 清除整个通讯录,本质上就是把整个通讯录重新初始化一遍。 用InitContact函数
2.7 打印整个通讯录
和查找通讯录等功能类似,不过不需要输入名字,而是直接打印。 首先还是需要打印标题,然后再依次打印通讯录中所储存的信息。
3. 代码的优化
3.1 动态开辟空间
改进缘由:一次性就开辟了1000个空间,但是可能一开始就用不了这么多空间,那么我们就可以改进为先开辟三个空间,如果空间满了,再开辟2个空间,这样也可以避免1000个空间也不够存放的情况。
动态开辟利用malloc函数,需要引用头文件#include<stdlib.h> 需要新增一个变量capacity来记录当前通讯录的容量,这个变量放置于Contact类型的结构体中。同时Contact结构体中的数组data[MAX]需要改为一个PeoInfo类型的指针变量,因为不必再局限于固定的数组元素个数了。 新增了动态开辟的功能后,自然某些功能函数就需要发生变化了,即那些修改了空间的函数。
初始化通讯录函数InitContact中需要将capacity初始化为0。 动态开辟会在新加入信息时发生,所以malloc的调用是在AddContact函数中,因为是一次性开辟三个空间,满了后再开辟两个空间,依然可以用宏定义3和2,分别为DEFAULT_SZ和INC_SZ,表示初始空间和增加空间数,这样也是利于代码的可读性。
这个CheckCapacity函数是在AddContact函数中调用的,当然也可以定义为静态函数,不过这里就没有那样了,所以还需要在Contact.h中声明一下。
DestoryContact的调用是在switch case EXIT分支中,即程序退出时。 动态开辟的内存需要由程序员手动释放,否则会造成内存泄漏的危机。 第一步free代表将pc->data所指向的空间全部清除,但由于这个指针仍然可以找到这块已经被释放的内存,所以还要把这个指针赋值为NULL即空指针。 最后还需要把容量和有效信息数都置为0
3.2 将信息储存在文件中
我们想要实现的基本功能为: 1、程序开始时加载已经储存好的信息到通讯录中。 2、在程序结束时更新信息到文件中。 因为是先有保存信息到文件中,才有加载文件到程序中,所以先说一下如何保存信息到文件中。 封装一个函数SaveContact,调用是在main函数的switch case EXIT分支语句中,即程序结束时调用。 fopen函数中的第一个参数为文件的相对路径(很短,就是下面的示例)或者绝对路径(特别长,而且使用的时候需要注意’ \ '是不能单独使用的,因为是转义字符,所以要改成‘ \ \ ’才能表示一个反斜杠 ),当这个文件不存在时,程序会帮助你自动创建一个这样的文件在当前的目录下,你也可以指定你所想要录入的文件位置。
那么接下来就是将文件加载到程序中的功能了,封装一个函数LoadContact,在InitContact函数中调用,用于将二进制文件信息转化到程序中来。
首先是打开文件,利用fopen函数。
再是加载文件,创建一个PeoInfo类型的结构体变量,用于暂时存储从文件中读取的信息,fread函数是从指定文件中读取二进制信息到某一地址上。
读取文件类似于增加信息的操作,所以首先需要检测是否有足够空间,再对应地录入信息,最后将有效信息变量sz自增。
最后一步是关闭文件。
这个函数是在InitContact函数中调用。
4. 代码的不足之处
随便举几个吧~ 比如名字如果相同,那么查找的时候却只能出现一个人的信息,值得修改这个BUG。 又比如只有单调的界面,暑假小学期的时候恰好又学了点EasyX的图形库,可以试着把它美观一点。 还有就是,查找算法和排序算法效率比较低,比如排序算法可以用qsort快排函数代替。
|