C项目实践之通讯录管理案例
1.功能需求分析
通讯录管理案例主要实现对联系人的信息进行添加、显示、查找、删除、更新和保存功能。主要功能需求描述如下:
(1)系统主控平台: 充许用户选择想要进行的操作,包括添加联系人信息,显示、查找、删除、更新联系人信息、保存到文件和退出系统。
(2)添加联系人信息: 用户根据提示输入联系人的姓名、性别、电话、手机、传真、地址以及邮编等。输入完一条联系人信息,提示用户是否继续输入下一条联系人信息或者继续其它操作。充许输入多条联系人的信息。输入完的联系人信息暂时保存在单链表中,等待下一步操作。
(3)显示联系人信息:将刚输入的联系人信息从单链表中调出来显示,如果没有数据,则提示无联系人信息。
(4)查找联系人信息:可以根据联系人姓名从单链表中对所有联系人的信息进行查找,如果没有查询到任何信息,则给出提示信息,否则打印找到的信息。
(5)删除联系人信息:根据输入的联系人姓名来查找是否存在该联系人,如果存在则确认是否真的需要删除,确认之后就删除,否则提示没有找到该联系人。
(6)修改联系人信息:根据输入的联系人姓名来查找是否存在该联系人,如果存在则直接调用添加函数对当前节点进行修改操作,实际上这是重新填写联系人的全部信息,而不是修改某一项或几项信息。
(7)文件保存:本模块的功能是将单链表中的信息存储到文本文件中。系统将提示用户输入文本文件名,确认后将单链表中联系人的信息存储到文本文件中。
(8)退出:退出系统。
2.总体设计
2.1功能模块设计
1.添加联系人(attnAdd()函数)
首先建立单链表,调用input_person() 函数输入联系人信息,将用户输入的联系人信息存储到单链表中,提示用户是否继续输入,用户输入“Y"或"y",则调用attnAdd()函数实现继续添加联系人操作。用户输入"N"或"n",则返回主菜单界面。(这里的返回主菜单界面实际是在main()中利用while()来做的,而不是在attnAdd()函数中实现返回主菜单界面的)。
2.显示联系人
将单链表中的联系人信息打印出来,单链表不为空时,循环调用print_person()函数打印每个联系人信息。
3.查找联系人
首先提示用户输入要查找的联系人姓名,然后根据输入的信息在单链表中利用strcmp()函数逐个比对,如果发现相同的,则调用print_person()函数显示查找到的联系人信息。如果没有找到则给出提示信息。
4.删除联系人
首先提示用户输入要删除的联系人姓名,再根据输入的姓名在单链表中查找该联系人是否存在,如果存在,调用print_person()函数显示该联系人信息,提示用户是否确认删除该联系人,如果用户输入"Y"或"y", 则删除该联系人。否则提示用户是否继续删除操作,用户输入"Y"或"y”,则再次调用attnDelete()函数进行删除操作。
5.更新联系人
首先查找联系人信息,如果查找不成功,给出提示信息,如果查找成功,先调用print_person()函数显示该联系人信息,再调用input_person()函数输入新的数据。最后提示用户是否继续更新的操作,用户输入"Y"或“y”,则再次调用attnUpdate()函数进行删除操作。
6.保存到文件
首先提示用户输入要保存的文件名,文件将保存在程序目录下。系统将单链表中的信息保存到刚才输入的中文件中,并给出“保存成功”的提示信息。
7.退出系统
将单链表中的数据全部释放掉,防止内存泄露,退出系统。
2.2程序处理流程
系统的执行应从系统菜单的选择开始,充许用户输入1-7之间的数值来选择要进行的操作,输入其它字符都无效的,系统会给出出错的提示信息。若用户输入1,则调用attnAdd()函数进行添加联系人信息操作;输入2,则调用attnShow()函数显示联系人信息;输入3,则调用attnSearch()函数,查找联系人信息;若输入4,则调用attnDelete()函数,删除联系人信息,若输入5,则调用attnUpdate()函数,更新联系人信息,若输入6,则调用attnSave()函数,将联系人信息保存到磁盘文本文件中;若输入7,则调用attnQuit()函数,退出系统。系统的处理流程图如下:
3.详细设计与程序实现
首先打开vs ,新建win32 Console Apllication , 选择Empty Proj, 点击Finished 完成项目创建
然后在头文件夹和source文件夹中分别新建AddrMain.h 和 AddrMain.cpp文件!
在要处理的函数比较多,或者说代码量比较长的时候,我们一般会把函数和变量的声明放在.h文件中,而把功能实现放到.cpp中。
首先我们先在AddrMain.h把要用到的函数和变量进行声明,因为待会要对我们自定义的一个全局变量初始化为NULL , 所以先在AddrMain.h的开始处包含头文件:
//Header Information
#include <iostream>
AddrBook主要是对联系人的信息进行处理,而联系人信息是由多个数据成员共同构成,因为这些变量在很多地方都要用到,比如main(), attnAdd()等几乎每个函数都要用到这些变量,那么将它们声明为局部变量显示是不行的,但是如果将它们一个个的声明为全局变量,显示也是不理想的,虽然解决了变量全局访问的问题,但是管理起来很麻烦,所以最好的办法是声明一个结构体统一管理这些信息,但是使用结构体需要注意一个问题,那就是结构体所占用的内存大小是它所有成员的长度的总和,注意这里要考虑对齐行为,而不是它们每个成员的长度的简单相加, 为了避免应对齐浪费的空间,我们将所有字段统一声明char类型的数组,那么这里就又有一个问题需要考虑了, 因为我们要声明的字段比较多,那么这些数组的大小是直接在声明的时候给定固定大小呢,还是声明一些宏,然后通过这些宏来维护数组的大小呢? 显然声明宏来维护数组的大小是可取的。那么到现在为止,我们知道为了便于管理,需要声明一个联系人信息的结构体,为了便于维护数组的大小要提前预定义一些常量宏,下面在AddrMain.h中声明用来方便维护数组大小的常量宏:
//Pre-processing Declarations
#define MAX_NAME 11
#define MAX_SEX 3
#define MAX_BIRTHDAY 9
#define MAX_TEL 21
#define MAX_MOBILE 21
#define MAX_FAX 21
#define MAX_ADDRESS 101
#define MAX_POSTAL_CODE 7
接下来定义操作联系人信息的结构体_person ,并用typedef 定义一个新类型person,结构体中包括联系人姓名、性别、出生日期、电话、手机、传真、地址及邮编共8个成员。
//DataType Definition
typedef struct _person
{
char name[MAX_NAME]; //姓名
char sex[MAX_SEX]; //性别
char birthday[MAX_BIRTHDAY]; //出生日期
char tel[MAX_TEL]; //电话号码
char mobile[MAX_MOBILE]; //手机号码
char fax[MAX_FAX]; //传真
char address[MAX_ADDRESS]; //地址
char postal_code[MAX_POSTAL_CODE]; //邮编
}person;
好现在联系人信息的结构体有了,我们通过这个person结构体可以方便地对联系人的信息进行操作了
但是每操作完一个person ,它都只代表某一个联系人的信息,那操作完目前这个联系人信息之后我们怎么去操作下一个指定的联系人信息呢? 这就需要定义一个带有指针域的结构体,通过这个指针域来保存person结构体的内存地址,从而我们就可以把记录了不同的联系人信息的结构体person 链接在一起了。这样也就构成了一个单链表。因此,我们还需要声明一个单链表结构体类型 _addr_book, 用typedef 定义一个新类型addr_book, 在这个结构体中包括了一个存储联系人基本信息的结构体变量per 和指向下一个联系人的指针变量。具体声明如下:
typedef struct _addr_book
{
person per; // 联系人基本信息
struct _addr_book* next;
}addr_book;
addr_book是构成单链表的一个单元元素,而单链表在没有任何值的情况下是空链表,那么它的头指针应该初始化为空:
addr_book* first = NULL; //单链表头指针,初始化为空
上面我们把要用到的用于存储联系人信息的结构体person 和用来构建单链表的基本单元add_book都声明好了之后,下面我们就需要实现主要的功能模块,通过前面的功能需求分析,很明显我们要实现的功能有添加联系人信息函数,显示联系人信息函数、查找联系人信息函数,删除联系人函数,更新联系人函数,保存到文件和退出函数8个功能函数。下面是它们对应的函数声明:
//Function and variable Declarations
bool attnAdd(); //添加联系人信息函数
void attnShow(); //显示联系人信息函数
void attnSearch(); //查找联系人函数
void attnDelete(); //删除联系人函数
void attnUpdate(); //更新联系人信息函数
void attnSave(); //保存联系人信息函数
void attnQuit(); //退出系统函数
为了使得函数实现更加简洁明了,我们把上面的一些函数可能都要用的功能作为小函数抽象来了,主要包括打印主菜单函数、取链表中的最后一个值的函数、打印联系人信息函数和输入联系人信息函数。它们对应的声明如下:
//Auxiliary Function Declarations
int print_menu(); //打印主菜单界面
addr_book* get_last(addr_book* from); //取得链表最后一个值
void print_person(person *p); //显示一个联系人信息
void input_person(person *p); //提示输入一个联系人信息
因为主菜单中包括系统功能的所有选项,但是它只是作为一个显示,为了更加美观化,因此我们特意单独定义一个常量来处理:
//constant
char menu[] =
"+================================================+\n"
"| 通讯簿管理系统 |\n"
"+------------------------------------------------+\n"
"| 1 添加联系人 |\n"
"| 2 显示所有联系人 |\n"
"| 3 查找联系人 |\n"
"| 4 删除联系人 |\n"
"| 5 更新联系人 |\n"
"| 6 保存 |\n"
"| 7 退出系统 |\n"
"+================================================+\n";
好了, 到目前为止我们已经把项目中要用到的函数和变量都做了声明,下一步就要到AddrMain.cpp文件中去实现这些功能,然后调试运行就可以了。
下面打开AddrMain.cpp, 首先把要用到的头文件包含进来:
#include <cstdlib> //标准函数库, 因为要用到strcpy()做字符串比较处理
#include <iomanip> //这里要用到setw()设置宽度显示
#include "AddrMain.h"
using namespace std;
首先来实现主函数main()
主函数中, 首先调用打印主菜单界面函数print_menu(), 打印主菜单,等待用户输入1-7中的任一数值,根据用户的输入在switch语句中选择相应的操作,如果输入的不是1-7之间的值则给出提示,并要求重新输入。在这里我们利用了while()语句来实现循环功能也就是说只要用户不输入7,则while()就循环执行一次switch()中的print_menu()函数显示主菜单界面,供用户选择不同的操作! 实现代码如下:
int main()
{
int flg = 1;
while(flg)
{
switch(print_menu()) //打印主菜单界面
{
case 1:
attnAdd(); //提示添加联系人信息
break;
case 2:
attnShow(); //显示联系人信息
break;
case 3:
attnSearch(); //查找联系人信息
break;
case 4:
attnDelete(); //删除联系人信息
break;
case 5:
attnUpdate(); //更新联系人信息
break;
case 6:
attnSave(); //保存联系人信息
break;
case 7:
attnQuit(); //退出系统
break;
}
}
return (0);
}
bool attnAdd()
{
char input = 'N'; //声明最后的输入标识,并初始化为 N 表示用户不再继续调用此函数
addr_book* last = NULL; //声明一个last指针,用来指向单链表中的最后一个元素, 初始化为NULL
// 创建一个新的结构体变量new_addr用来存放新添加联系人的基本信息,为其分配内存
addr_book* new_addr = (addr_book*)malloc(sizeof(addr_book));
if(NULL == new_addr) return false; //如果new_addr内存分配失败,则返回
memset(new_addr,0,sizeof(addr_book));//将new_addr中的前addr_book个长度的初值设置为0
new_addr->next = NULL;
if(NULL == first)
{
first = new_addr;
}
else
{
last = get_last(first);
last->next = new_addr;
}
input_person(&(new_addr->per));
cout<<">继续输入?(Y 继续, N 返回菜单)"<<endl;
getchar();
input = getchar();
if('Y' == input || 'y' == input)
{
attnAdd();
}
return true;
}
2.显示联系人信息
函数名称:attnShow()
函数功能:选择操作2时调用此函数,用来显示联系人的基本信息。
处理过程:
(1)首先定义一个指针变量p指向头节点first。
(2)在单链表未结束时(p != NULL)反复调用print_person函数,逐个打印联系人信息。
(3)如果单链表中没有数据,则给出提示信息。
(4)按任意键返回主菜单,返回主菜单。具体实现如下:
void attnShow()
{
int i = 0;
addr_book* p = first;
while (NULL != p)
{
i++;
cout<<"****** 第"<<setw(2)<<i<<" 个联系人 ************************"<<endl;
print_person(&(p->per));
p = p->next;
}
if( 0 == i)
{
cout<<"没有联系人!"<<endl;
}
cout<<"按任意键返回菜单....."<<endl;
fflush(stdin);
getchar();
}
3.查找联系人信息
函数名称:attnSearch()
函数功能:选择操作3调用此函数,根据联系人姓名查找相关信息。
处理过程:
(1)首先定义一个指针变量p指向头节点first;
(2)输入要查找的联系人姓名,根据输入的姓名在单链表中逐个查找,如果找到就调用print_person函数显示该联系人信息,否则给出提示信息。
(3)提示用户是否继续查找,如果用户输入"y”或“Y”,则调用attnSearch()继续进行下一个联系人信息的查找,否则返回主菜单界面。具体实现如下:
void attnSearch()
{
int count = 0;
char input = 'N';
char name[MAX_NAME] = {0};
addr_book* p = first;
cout<<">请输入要查找的联系人姓名(最大 "<<setw(2)<<MAX_NAME-1<<" 个字符):";
cin>>name;
while(NULL != p)
{
if(strcmp(p->per.name, name) == 0) // 对比输入的字符串和单链表中的某个字符串是否相等
{
print_person(&(p->per));
count++;
}
p = p->next;
}
if(0 == count)
cout<<"没有找到姓名为"<<setw(2)<<name<<"的人."<<endl;
cout<<"继续查找吗?(Y 继续查找, N 返回菜单)"<<endl;
getchar();
input = getchar();
if('Y'== input || 'y' == input)
{
attnSearch();
}
}
4.删除联系人信息
函数名称:attnDelete()
函数功能:选择操作4调用此函数,根据联系人姓名删除该联系人信息。
处理过程:
(1)首先定义一个指针变量p指向头节点first,在定义一个指针变量p1,将其置空。
(2)输入要删除的联系人姓名,根据姓名在单链表中逐个查找联系人信息。如果没有找到,则给出提示信息,否则进行删除。
i.如果要删除的节点p是头节点,则将p->next赋值给first, 即first = p->next,直接删除
ii.如果要删除的节点不是头节点,先设置p1指向头节点,然后在循环体中判断p1的后继节点是不是p,借助p1删除节点p, 即p1->next = p->next.
iii。释放节点p.
(3)提示用户是否继续进行删除操作。如果用户输入"y"或"Y",则再次调用attnDelete()函数,进行相应处理,否则返回主菜单界面。具体实现如下:
void attnDelete()
{
int count = 0;
char input = 'N';
char name[MAX_NAME] = {0};
addr_book* p = first;
addr_book* pl = NULL;
cout<<">请输入要删除的联系人姓名(最大 "<<setw(2)<<MAX_NAME -1<<"个字符):";
cin>>name;
while(p != NULL)
{
if(strcmp(p->per.name, name) == 0)
{
print_person(&(p->per));
count++;
break;
}
p = p->next;
}
if(0 == count)
{
cout<<"没有姓名为"<<setw(2)<<name<<"的人."<<endl;
}
else
{
cout<<"确定要删除姓名为["<<setw(2)<<name<<"]的联系人吗?(Y 确认, N 取消)"<<endl;
getchar();
input = getchar();
if('Y' == input || 'y' == input)
{
if(first == p)
{
first = p->next;
}
else
{
pl = first;
while (pl != NULL)
{
if(pl->next == p)
{
pl->next = p->next;
break;
}
pl = pl->next;
}
}
free(p);
}
}
cout<<"继续删除其它联系人吗?(Y 继续删除, N返回菜单)"<<endl;
getchar();
input = getchar();
if('Y' == input || 'y' == input)
{
attnDelete();
}
}
5.更新联系人信息
函数名称:attnUpdate()
函数功能:选择操作5就调用此函数,输入联系人姓名,更新其相关信息。
处理过程:
(1)首先定义一个指针变量p,指向头节点first.
(2)输入要更新的联系人姓名,根据姓名在目前单链表中查找。如果找到,则先调用print_person函数显示该联系人信息,然后将当前节点地址作为参数传给input_person,调用input_person函数重新输入该联系人的信息进行更新操作;如果没有输入的联系人则给出提示信息。
(3)提示用户是否继续更新的操作,
如果用户输入“y”或"Y",则再次调用attnUpdat()函数进行处理,否则返回主菜单界面。具体实现如下:
void attnUpdate()
{
int count = 0;
char input = 'N';
char name[MAX_NAME] = {0};
addr_book* p = first;
cout<<"请输入要更新的联系人姓名(最大 "<<setw(2)<<MAX_NAME-1<<"个字符):";
cin>>name;
while(NULL != p)
{
if(strcmp(p->per.name, name) == 0)
{
print_person(&(p->per));
count++;
break;
}
p = p->next;
}
if(count == 0)
{
cout<<"没有找到姓名为 "<<setw(2)<<name<<"的人."<<endl;
}
else
{
input_person(&(p->per));
}
cout<<"继续更新其它联系人吗?(Y 继续更新, N 返回菜单)"<<endl;
getchar();
input = getchar();
if('Y' == input || 'y' == input)
{
attnUpdate();
}
}
6.保存联系人信息
函数名称:attnSave()
函数功能:选择操作6调用此函数,将单链表中的信息保存到磁盘文本文件中。
处理过程:
(1)首先定义一个指针变量p, 指向头节点first.
(2) 输入文件名(把后缀一起输入, 如save.txt)在循环题体中利用函数fprintf()逐个将单链表中的联系人信息保存到文本文件中。
(3)返回主菜单。具体实现如下:
void attnSave()
{
FILE *fp;
char file[100];
addr_book* p = first;
cout<<"请输入文件名:"<<endl;
cin>>file;
fp = fopen(file,"w");
while(NULL != p)
{
fprintf(fp,"%s,%s,%s,%s,%s,%s,%s,%s\n",
p->per.name,p->per.sex,p->per.birthday,p->per.tel,
p->per.mobile,p->per.fax,p->per.address,p->per.postal_code);
p = p->next;
}
fclose(fp);
cout<<"保存成功!\n按任意键返回菜单..."<<endl;
fflush(stdin);
getchar();
}
7.退出系统
函数名称:attnQuit()
函数功能:选择操作7调用此函数, 直接退出系统。
处理过程:
(1)首先定义一个指针变量pdel 指向头节点,在定义一个指针变量p 置空;
(2)判断pedl的next是否为空,如果不为空则表示有下一条数据,此时需要通过一个循环体先将p指向pdel,释放掉pdel,再将p重复赋值给pdel,来循环删除所有的数据,直到pedl的next域为NULL 为止,然后再释放掉pdel,即可。具体实现如下:
void attnQuit()
{
addr_book* pdel = first;
addr_book* p = NULL;
if(pdel == NULL)
{
exit(0);
}
while(pdel->next != NULL)
{
p = pdel->next;
free(pdel);
pdel = p;
}
free(pdel);
exit(0);
}
上面完成了主要功能函数的实现,但是前面我们把上面的2个或多个函数可能需要共同用到的函数作为辅助函数抽象出来了,下面我们对它们逐一进行实现:
1显示主菜单界面
函数名称:print_menu()
函数功能:显示主菜单界面
处理过程:
等待用户输入1-7中的任一数值,如果输入的是1-7中的某一数值则调用相应的函数进行处理,否则给出提示信息。在每次进入打印主界面之前,都会先调用system("cls")函数对控制台清屏操作。具体实现如下:
int print_menu()
{
int selected = 0;
system("cls");
cout<<menu<<endl;
fflush(stdin); //清空缓冲区中的字符
cout<<">请选择[1-7]:";
cin>>selected;
if(selected<1 || selected > 7)
{
cout<<"错误选择!(请输入1-7),按任意键继续...."<<endl;
getchar();
getchar();
}
return selected;
}
2.输入一个联系人信息
函数名称:input_person()
函数功能:提示用户输入联系人的基本信息;具体实现如下:
void input_person(person* p)
{
cout<<"请输入联系人信息\n";
cout<<"请输入姓名(最大长度 "<<setw(2)<<MAX_NAME-1<<"个字符):";
cin>>p->name;
cout<<"请输入性别(最大长度 "<<setw(2)<<MAX_SEX - 1<<"个字符):";
cin>>p->sex;
cout<<"请输入出生日期(最大长度为 "<<setw(2)<<MAX_BIRTHDAY-1<<"个字符):";
cin>>p->birthday;
cout<<"请输入电话(最大长度为 "<<setw(2)<<MAX_TEL -1<<"个字符):";
cin>>p->tel;
cout<<"请输入手机(最大长度 "<<setw(2)<<MAX_MOBILE-1<<"个字符):";
cin>>p->mobile;
cout<<"请输入传真(最大长度为 "<<setw(2)<<MAX_FAX-1<<"个字符):";
cin>>p->fax;
cout<<"请输入地址(最大长度为"<<setw(2)<<MAX_ADDRESS-1<<"个字符):";
cin>>p->address;
cout<<"请输入邮编(最大长度为 "<<setw(2)<<MAX_POSTAL_CODE -1<<"个字符):";
cin>>p->postal_code;
}
3.显示一个联系人信息
函数名称:print_person()
函数功能:显示联系人信息,具体实现如下:
void print_person(person* p)
{
cout<<"姓名:"<<setw(1)<<p->name<<" 性别:"<<setw(1)<<p->sex<<" 生日:"<<p->birthday<<endl;
cout<<"电话:"<<setw(1)<<p->tel<<endl;
cout<<"手机: "<<setw(1)<<p->mobile<<endl;
cout<<"传真:"<<setw(1)<<p->fax<<endl;
cout<<"地址:"<<setw(1)<<p->address<<endl;
cout<<"邮编:"<<setw(1)<<p->postal_code<<endl;
}
4.取得链表中最后一个节点指针
函数名称:get_last()
函数功能:参数传过来一个节点from , 如果from不为空,则顺序找该链表的最后一个节点,如果为空,则from就是最后一个节点,最后返回最后节点的指针。具体实现如下:
addr_book* get_last(addr_book* from)
{
addr_book* p = from;
while(NULL != p->next)
{
p = p->next;
}
return p;
}
好了现在整个系统实现完毕,F5运行,下面是测试结果:
1.主菜单界面
F5系统运行,进入主菜单界面:
2.添加联系人
选择操作1,进入添加联系人界面,用户可以根据提示信息完成联系人信息的添加操作,输入完一条信息之后,系统会提示用户是否继续输入下一条联系人信息,如果用户输入"y"或"Y"则继续调用attnAdd()进行相关操作,否则返回主界面:
这里要注意:性别那里是最大长度是2个字符,所以如果输入Male就会出错,因为数组越界了,输入'男' 则刚好占用这个两个字符,没有引起数组越界,所以不会出错,下面的生日也是一样,总共只能输入8个字符,如果输入1990-01-10 则会出错,因为造成了数组越界, 那么如果要充许输入Male 或 1990-01-10不会出错,则只需要修改MAX_SEX和 MAX_BIRTHDAY这两个宏的值即可。
3.显示联系人信息
4.查找联系人信息
5.更新联系人信息
6.保存联系人
7.删除联系人
8退出系统