引言
(一)程序名称:
通讯录小项目设计DOS版V1.0(小钱版)
(二)开发环境:
电脑型号: 惠普 HP Pavilion g4 Notebook PC 笔记本电脑
操作系统: Microsoft Windows 7 旗舰版 (6.1.7601 Service Pack 1)
处理器: 英特尔 Core i3 M 390 @ 2.67GHz 双核笔记本处理器
主板: 惠普 1667 (英特尔 HM55 芯片组)
内存: 4 GB ( 尔必达 DDR3 1333MHz / 金士顿 DDR3 1333MHz )
主硬盘: 希捷 ST9640320AS ( 640 GB / 5400 转/分 )
显卡: ATI Radeon HD 6470M ( 1 GB / 惠普 )
编译调试软件:Microsoft Visual Studio 2010
(三)主要功能:
1. 添加用户名及电话号码。
2. 删除用户名及电话号码。
2. 查找用户名及相关资料,可以输入用户名的关键字完成查找操作。
3. 查询电话簿中已存的用户名及电话号码。
4. 保存通讯录的所有数据至多个文件中。
5. 打开通讯录时,选择读取通讯录的所有数据。
6. 未开发功能:操作过程中,选择加载电话簿的功能,删除时候先显示原有电话簿等。
(四)程序说明:
1. 运行环境:
Windows NT / 2000 / XP / VISTA / 7
2. 文件说明:
① 程序运行文件夹: 01_可直接执行文件_通讯录小项目设计DOS版V1.0(小钱版)
② 程序说明书: 02_说明书_通讯录小项目设计DOS版V1.0(小钱版)说明书.pdf
③ 源程序文件夹: 03_项目文件_通讯录小项目设计DOS版V1.0(小钱版)
④ 程序制作相关: 04_项目相关_通讯录小项目设计DOS版V1.0(小钱版)
3. 参考资料:
① 《C++ Primer Plus(第五版)》 Stephen Prata著 孙建春、韦强译
② 《C++ Primer(中文版)(第四版)》 Stanley B.Lippman Josee Lajoie 著
☞ 程序结构
(一) 整个程序中:
1. 包含一个头文件:messageuser.h
2. 包含两个源文件:messageuser.cpp、main.cpp
(二)主函数main中:
1. 包含头文件:
#include <iostream>
#include <cstdlib>
#include <conio.h>
#include "messageuser.h"
2. 包含功能函数
char enterchar(char, char);
void next();
char showmenu();
(三)类messageuser中:
1. 包含头文件:
#include <iostream>
#include <string>
#include <utility>
#include <map>
#include <fstream>
#include <set>
2. 包含功能函数:
void messageuser::insertin();
void messageuser::find();
void messageuser::del();
void messageuser::printout();
void messageuser::save();
void messageuser::load();
☞ 程序流程图
☞ 程序源程序模块设计说明
(一)模块1:主函数
※ 函数:
① char enterchar(char, char); // 要求正确输入指定范围内的字符
② void next(); // 进行下一步时的提醒操作
③ char showmenu(); // 显示主菜单
主函数中只包含以上三个函数,分别实现以上注释所描述功能,每个函数间都有一定的关系,详细见下面结构设计的说明。
※ 结构设计:
在主函数中,没有使用过多的函数,主要使用了一个do-while和一个swith语句控制整个程序操作流程,如下所示:
int main()
{
messageuser mes;
mes.load();
next();
char choice;
do
{
choice = showmenu();
switch(choice)
{
case '1': // 1. 添加新用户
case '2': // 2. 查找用户资料
case '3': // 3. 删除用户及相关资料
case '4': // 4. 保存现有的全部用户资料
case '5': // 5. 显示通讯录中全部用户资料
case '6': // 6. 退出通讯录管理软件
}
}while(choice != '6');
return 0;
}
程序运行时,先声明一个mesasgeuser类的对象用以实现switch中的全部功能,然后,载入上次程序运行的数据,之后,声明一个choice的char 型对象,目的是控制switch语句,根据choice的不同大小选择不同选项,而大小方面,也由showmenu函数控制好,所以就不必担心有错误选择导致程序崩溃的现象出现,之后,再通过choice字符是否等于6来判断是否退出主函数。
① char enterchar(char min, char max) 函数
※ 函数类型及参数传递:
这个函数的返回类型为字符形,为了返回一个可以供主函数的switch语句使用的变量,然后,这个函数具有两个形参,两个形参均为char型,用以在函数体类控制输入一个范围内的字符,主要采用的是左闭区间的范围控制。
※ 函数分析:
申请了一个char型的choice变量,以及一个用以循环控制的bool型circulate变量,使用了两do-while语句,第一个do-while(circulate)控制的是当其输入超出范围时,重新输入,而其中一个do-while(circulate)主要控制错误字符的输入,如下面代码所示:
do
{
cin >> choice;
circulate = false;
if (!cin)
{
std::cerr << "\n您的输入有误,请重新输入:";
circulate = true;
cin.sync();
cin.clear();
}
}while(circulate);
当输入不正确时,首先会向用户显示出错误信息,然后,将循环条件转变为真,再将输入流清空,输入流状态重设为有效状态。
② void next() 函数
这个函数很简单,只是简短地使用了三个语句,第一步提示用户按任意键继续操作[cout << "\n请按任意键继续...\n"],第二步实现按键继续功能[char temp(_getch())],主要是为了让主函数可以人性化地在各功能间进行转换。
③ char showmenu() 函数
这也是一个比较简单的函数,也不难理解,主要有以下三步:第一,使用 system("cls")清空屏幕,使用cin.sync()清空输入流,以免对以后的输入带来影响,第二,用cout输出用户提示信息,第三,enterchar('1', '7');调用enterchar函数,返回正确的输入项。
(二) 模块2:messageuser类
※ 数据成员:
这个类里面,数据成员不多,只有两个,因为这次的程序设计的最主要原因是为了练习一下关联容器的使用,所以,就使用了map类的关联容器作为其中一种数据成员的类型。
① std::map<string,int> users;
定义这个user变量,可以控制整个类中的信息保存,关键字存的是用户的姓名,关键字的值存放的是用户的电话号码。
② const string savefilename;
定义一个默认值为“通讯录保存文件列表.txt”的const常量,作为多个文件存储列表名称。
※ 成员函数:
设计了完成六种功能的十三个成员函数,这些成员函数在以下说明有介绍,这里先简略不写。
1. 添加函数
设计该添加功能时包含了两个函数:
bool inserthelp(string, int) 帮助插入操作;
void insertin(); 可以直接调用的公有函数,函数会提示有多少个需要插入的元素;
① bool messageuser::inserthelp(string stemp, int itemp)
{
// users.insert(make_pair(stemp, itemp));
users.insert(type_map_si::value_type(stemp, itemp));
return true;
}
这函数,主要实现将数据添加进map类型的users变量中,第一个形参为string类型,作为添加的用户名称,第二个形参为int型,作为添加的用户号码,在为map容器添加数据时,使用了insert()函数,该函数中,我选择了value_type的形式加入。将用户资料加入到数据成员users中。
② void messageuser::insertin()
在for中放入if语句,将从用户输入设备中输入的数据通过inserthelp函数放入类中,而通过ntemp,可以控制需要添加的用户数目:
for (int i = 0; i != ntemp; i++)
{
… cin >> stemp;
…… // 省去输入格式控制的函数
… cin >> itemp
if(inserthelp(stemp, itemp)) ;
else ……// 输出错误信息
}
2. 查找函数
bool findAllhelp(string) 通过全名,寻找用户名是否在该类的数据成员中.
bool findSingleHelp(string) 通过部分关键字寻找用户名是否在该类的数据成员中。
void find() 实现查找功能,函数会提示输入需要查找的元素名称并返回相关结果。
① 第一个函数:bool messageuser::findAllHelp(string findname)
{
if ( users.empty() )
return false;
if ( users.find(findname) == users.end() )
return false;
else
return true;
}
这函数,先调用empty函数检查存放数据的容器是否为空,然后通过调用find函数返回迭代器,若迭代器为end则说明寻找失败,此处还可以尝试使用count函数,若函数返回结果为0则说明寻找失败。
② 第二个函数 bool messageuser::findSingleHelp(string findname, type_map_si & tsave);
※ 这函数有两个形参,第一个形参是需要查找的名字,第二个形参可以将寻找到的名字保存。
※ 函数一开始,定义了不少变量,其中:
string::size_type fnameSize( findname.size()) // 得知要查找名字的最大长度
string::size_type fnum(0); // 在后面的函数中需要用于定位findname的位数
string::size_type tnum(0); // 在后面的函数中需要用于定位users容器中关键字的位数
string::size_type tnameSize; // 得知容器users中每个关键字的长度
※ 接下来,主要用了一个算法,将符合(部分关键字与users容器中的关键字相对应)条件的数据找出,请直接看代码及注释说明:
while(truename != users.end()) // 使用一个循环控制对users容器中的全部成员进行比较
{
tnameSize = (truename -> first).size(); // 先读取每个成员的长度
// 容器中每个用户名与输入的姓名作对比
do
{
if( findname[fnum] == (truename -> first)[tnum] )
{ // 需要查找的名字与容器内的名字相对应的字符相同时,执行以下操作
if( fnum + 1 == fnameSize)
{ // 当到达用户查找的关键字符串未尾时,添加元素进新容器
tsave[truename -> first] = truename -> second;
break;
}
else
{
// 由于这次的条件符合,未到字符串未尾的情况下,继续对比下一字符
++fnum;
++tnum;
}
}
else // 需要查找的名字与容器内的名字相对应的字符非对应时,执行以下操作
{
if (fnum == 0)
{ // 当用户输入的关键字仅有一个字符,则可以继续跟容器的下一字符对比
++tnum;
}
else if ( fnum + 1 == fnameSize)
{ // 当到达了用户输入关键字末尾而又未找到相吻合的字符,不执行添加操作
break;
}
else
{ // 当未达到末尾时,继续查找容器中单个关键字的下一字符
++tnum;
}
}
}while(tnum != tnameSize + 1);
fnum = 0; // 执行完一个关键字的对比后,将数值返回0,继续下一轮的对比
tnum = 0;
++truename; // 自增迭代器指向一下元素
}
这个算法,主要是字符间的比较到字符串之间的比较,关键点是字符串长度的控制,当达到字符串长度时,说明对比完成,根据每个字符的对比结果不同,执行相对应的操作。
③ 第三个函数:void messageuser::find()
该函数用于控制整个查找函数的流程,通用i f - else if - else 语句,执行各种操作。
if ( findAllHelp(fname) ) // 全名符合的用户资料
else if ( findSingleHelp(fname, tempToPrint) ) // 有相关字符符合名字的用户资料
else……
3. 删除函数
删除功能中,也包含了两个函数:
① bool deletehelp(string)
if ( users.empty() ) return false;
if( users.erase(dname)) return true;
else return false;
寻找用户名是否在该类的数据成员中,如果用户删除成功则返回true,当容器为空时,立即判断为false。
② void del()
实现删除功能时,函数会提示输入需要删除的元素,首先调用find函数判断是否存在元素,再进行相关操作,返回相关结果。
void messageuser::del()
{……
string dname;
cout << "\n请输入需要删除的资料的姓名:";
cin >> dname;
在上面代码中,从用户的输入设备中获知需要删除的姓名,并将其存放在dname变量中。
if( 0 == findAllHelp(dname) )
{ cout << "\n未找到该用户的资料,请核实后再进行操作。"; return; }
if ( 0 == deletehelp(dname) )
{ cout << "\n意外,删除失败。"; }
else { cout << "\n恭喜,删除 " << dname << " 用户资料成功。"; }
在删除操作前,首先通过find函数查找该函数是否在容器中,当其存在于容器中时,于继续下一步删除工作,否则,提示错误信息。
4. 显示函数
if ( iter == users.end() )
{
cout << "此通讯录为空。\n";
return;
}
int sum(0); // 用以统计用户数目
while(iter != users.end() )
{
cout << iter -> first << "\t\t" << iter -> second << endl;
++sum;
++iter;
}
此函数使用了一个简单的方法遍历map容器,当迭代器不指向容器末端时,输出数据。
5. 保存函数
设计该添加功能时包含了两个函数:
bool messageuser::savedhelp(); 保存文件为二进制文件,若保存文件失败,则返回false;
void messageuser::save(); 按要求输入保存文件的名称,完成保存文件功能。
① void messageuser::savehelp(string fname)
std::ofstream sfile;
sfile.open(fname.c_str(), std::ofstream::out | std::ofstream::binary);
type_map_si::iterator iter = users.begin();
while(iter != users.end() )
{
sfile << iter -> first << "\t\t" << iter -> second << " ";
++iter;
}
sfile.close();
以二进制形式打开文件,并且使用迭代器遍历users容器,将容器资料逐个输入保存在指定文件名的文件中。
② void messageuser::save()
这函数设计思路是:首先,用户输入文件名,然后,统一将用户的文件名输入到.txt文件中保存,然后,读取.txt文件名列表,检测该文件名是否与之前的文件重名,提示用户,是否覆盖文件。最后,调用savehelp 函数,将文件以二进制的形式保存。
6. 读取函数
设计该添加功能时包含了两个函数,这函数与第五个函数保存函数的功能相对应:
bool messageuser::loadhelp();检测是否已存在已保存的文件,若存在,则返回true;
void messageuser::load(); 完成文件读取功能。
① void messageuser::loadhelp(string lfile)
rfile.open(lfile.c_str(), std::ifstream::in | std::ifstream::binary);
type_map_si::iterator iter = users.begin();
while(!rfile.eof())
{
rfile >> temp >> itemp;
users[temp] = itemp;
}
rfile.close();
以二进制形式打开文件,并且使用迭代器遍历users容器,将容器资料逐个读取,然后再将这次资料逐个添加入类中的users容器中。以达到恢复资料的目的。
② void messageuser::load()
这函数的设计思路与messageuser::save()函数相近,或者可以说两个函数的设计是相对应的吧,首先要求用户输入需要读取的用户名,当然,用户可以根据提示选择不读取,然后,程序自动检测文件名列表,检测出是否存在该文件名,之后,如果发现该文件,则调用loadhelp函数加载文件资料进入系统中。
☞ 程序使用说明
――――――――――――――― 001 刚刚打开软件第一界面 ―――――――――――――――――
刚刚打开软件,系统会显示出欢迎界面,并且自动读取已经存储了的通讯录文件列表,根据读取结果,会有两种情况。
当未读取到文件时,用户可以继续按任意键进行下一步操作。
当可读取到文件时,如下图,程序会列出通讯录列表。
如果用户需要读取文件,则按要求需入文件名,若不需要读取文件,可直接输入no,当输入错误时,会出现以下提示操作:
当用户再次输入错误时,系统将默认为不需要读取文件,当用户输入正确时,系统将读取该文件信息,将且进入操作界面。
―――――――――――――――― 002 进入系统操作界面 ―――――――――――――――――――
此时,用户可根据需要选择需要进行的操作,如下图:
――――――――――――――― 003 选择进入添加新用户操作 ―――――――――――――――――
当选择进入了添加新用户操作时,用户可根据提示,输入需要增加的用户数目,当然,若输入错误,系统将会做出相应的提示。输入正确以后,用户可按照提示逐步输入添加姓名及电话号码到软件中。
―――――――――――――――004 选择进入查找用户操作―――――――――――――――――――
当选择了查找用户的操作后,系统将会需要你输入需要查找的用户名称,此时,用户名可为全部,也可以是部分的关键字,系统将会根据情况进行搜索,待系统搜索完毕后,系统将会做出相应的提示,如果刚好查找名与数据库里面的名字相对应,系统将会列出该查找名的相应资料(见下图),如果查找名与数据库中多个名字的部分字符相对应,系统将会列出对应的清单,显示出全部清单的相应资料(见下下图),如果查找名未能在数据库中发现,那么系统将会提示未发现用户。在操作完毕后,系统将会提示按任意键继续操作。
―――――――――――――005 选择删除用户及相关资料操作――――――――――――――――――
当选择进入了删除用户及相关资料操作时,用户可根据提示,输入需要删除的用户,当然,若输入错误,系统将会做出相应的提示。输入正确以后,系统将删除该用户的姓名及电话号码。
――――――――――――006 选择显示通讯录中全部用户资料操作――――――――――――――――
当选择了显示通讯录中全部用户资料操作后,系统将会向用户列出现存的电话列表。如下图所示。
――――――――――――007 选择保存现有的全部用户资料操作――――――――――――――――
当选择了保存现在的全部用户资料操作后,系统将会提示输入需要保存的文件名称,当输入正确后,系统将会判断是否存在同名的文件,若存在,则提示是否覆盖,当选择了不覆盖时,系统将会要求用户重新输入文件名,否则,系统将会覆盖原文件进行保存操作,当不存在同名文件时,系统会直接按刚刚输入的文件名保存,如下图:
―――――――――――――― 008 选择退出通讯录管理软件操作――――――――――――――――
当选择了退出通讯录管理软件的操作后,系统会作出提示,显示是否退出成功。
――――――――――――――――――――――――――――――――――――――――――――――
☞ 总结
本看完《C++ Primer》第二部分(关联容器的内容)后,出于练习使用关联容器的目的,上网搜索可以做的项目资料,并且对需要做的项目进行修改,用了五天(2011.09.13 – 2011.09.18)的断断续续的时间,终于完成了这个500多行的小项目——通讯录小项目DOS版V1.0(小钱版)。
这次是我第二次编写项目,这次的项目相对上次的项目来说,难度有所降低,不过,所汲及到的内容却不完全相同。
这次,首次练习了使用关联容器,通过项目的不断编写过程,对关联容器的使用也渐渐熟悉。知道了map容器的添加,删除,查找操作的使用方法,最之前,遇到一个很傻的问题,不知道为什么老是编译出错,后来才知道,原来使用map容器需要包含的头文件就叫做#include<map>实在是傻啊,还一直在找原来,后来还是通过网络才解决了问题的。
这个项目,要说难度的话,对我而言,主要有二,第一难度是,对文件的保存功能,平常用这个功能用得比较少,这次,还新增了一个新要求,要求软件能读取之前保存过的文件,于是,在整理好自己的思路后,再翻阅回顾一些资料,经过一系列的问题后,终于也将这个功能做了出来,途中,也了解到了,原来二进制的文件保存功能不是这么好用的,第二难度是,对用户名的查找功能,这里主要难度在于,算法,如果将输入的关键字与软件中的关键字对应才是一个难度,这个功能,其实是我自己想到加上去了,这是因为,平时用手机通讯录功能时,常常有这个操作,于是,想想这个也是可以用C++来实现的,就自己给自已出了道题目,当然,完成这个题目,也花了好几个小时的时间,才把这个算法写了出来,刚刚开始时,只是用自然语言写出来,但是,那时候转译成高级程序语言,还是有一定的难度,后来,再次整理思路,找出关键点,终于还是把这个功能给写出来了。
这次小项目的编写,总算达到的最初目的,挺好的!同时,也为自己的积累了一些项目经验,相信能够通过自己的不断努力,最后能纯熟地写出一个个程序来。
☞ 附:程序调试时遇到的问题及解决方法 (部分)
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
关于map::insert的一点小问题。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
《C++ Primer》中文第四版书上P313描述:
m.insert(e)
e是一个用在m上的value_type类型的值。如果键(e.first)不在m中,则插入一个值为e.second的新元素;如果该键在m中已存在,则保持m不变。
该函数返回一个pair类型的对象,包含指向键为e.first的元素的map迭代器,以及一个bool类型的对象,表示是否插入了该元素。
问题:这里所说的是指返回是bool型吗?
如果返回的只是void型,如果有插入失败的情况怎么办?
在编程时,我写了一个程序(如下),然后,当我用if(//放进insert函数)时,程序出错,于是,想知道,是我的程序出问题了,还是这段文字我还没有理解透?
/** 以下是程序的部分内容
bool user::inserthelp(string stemp, int itemp)
{
users.insert(type_map_si::value_type(stemp, itemp))
return true;
}
**/
/**
当我用了下面的方法时,提示
1>user.cpp(20): error C2451: “std::pair<_Ty1,_Ty2>”类型的条件表达式是非法的
1> with
1> [
1> _Ty1=std::_Tree_iterator<std::_Tree_val<std::_Tmap_traits<std::string,int,std::less<std::string>,std::allocator<std::pair<const std::string,int>>,false>>>,
1> _Ty2=bool
1> ]
bool user::inserthelp(string stemp, int itemp)
{
if(users.insert(type_map_si::value_type(stemp, itemp)));
return true;
else
return false;
}
**/
/**
#include <iostream>
#include <string>
#include <utility>
#include <map>
using std::string;
class user
{
private:
typedef std::map<string,int> type_map_si;
type_map_si users;
bool inserthelp(string, int);
};
**/
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
在做通讯录时遇到的保存记录问题 I/O Binary问题。。
调试了N久,想不出为什么,然后又在网上找了资料,核对了一下自己的格式是否出错,
之后,在程序中编译正常的,到现在,还是想不明这里为什么读取不了二进制文件?
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
主要的两小段是:
C/C++ code
void messageuser::loadhelp(string lfile)
{
std::ifstream rfile;
rfile.open(lfile.c_str(), std::ifstream::in | std::ifstream::binary);
rfile.read((char *) &users, sizeof(users));
rfile.close();
return;
}
C/C++ code
void messageuser::savehelp(string fname)
{
std::ofstream sfile;
sfile.open(fname.c_str(), std::ofstream::out | std::ofstream::binary);
sfile.write((char *) & users, users.size());
sfile.close();
return;
}
程序中在尝试一个功能:退出时以进制文件格式保存文件,然后,刚刚进入软件时,读取二进制文件,恢复进度。
问题:保存成功,出现了.dat文件,但是,读取的时候,也成功了,只是,看不见数据在软件里面,当查询用户资料时,显示为空。
后来,请教老师去了,老师给了我一个回复 rfile.read((char*)users.begin(), users.size() );
之后,我上机,改了一下,一编译,错误,那是个迭代器啊?
于是,我查了一下read的语法,
read 语法:
istream &read( char *buffer, streamsize num );
函数read()用于输入流,在将字符放入buffer 之前从流中读取num 个字节。如果碰到EOF,read()中止,丢弃不论多少个字节已经放入。例如:
struct {
int height;
int width;
} rectangle;
input_file.read( (char *)(&rectangle), sizeof(rectangle) );
if( input_file.bad() ) {
cerr << "Error reading data" << endl;
exit( 0 );
}
write 语法:
ostream &write( const char *buffer, streamsize num );
write()函数用于输出流,从buffer中写num个字节到当前输出流中。
这次,我终于想明白了很多了,原来那个迭代的思路是有点对的,但是,在这里实现不了,在messageuser类中,没有构造map,它是一个空容器,哪里会有地址可以让它放入呢?再说,map.begin()是不能放入数据的,得通过map.insert()函数,所以,这就形成一个bug了,想了几个方法,创建新容器。。。失败,最后,还是用输入输出流解决问题了,将savehelp的函数改了一下,将loadhelp的函数改了一下,最后,再将load()函数改了一下,这样一下,程序就完成了,剩下要做的就是给自己的程序写写说明了。
☞ 附:程序源代码
// main.cpp -- 通讯录小项目设计
/**
* 通讯录小项目设计
* 【基本要求】
* (1) 设每个记录有下列数据项:电话号码、用户名;
* (2) 从键盘输入各记录,分别以用户名为关键字建立map容器;
* (3) 可通过关键字查找,如:一个名字中含有“A”的全部人的信息,再加上一个退出系统保留信息功能。
* (4)显示给定电话号码的记录;
* (5)通讯录信息文件保存,下次打开程序时,可直接读取已保存的通讯录。
**/
#include <iostream>
#include <cstdlib> // system("cls")
#include <conio.h> // _getch()
#include "messageuser.h"
using std::cout;
using std::cin;
using std::endl;
char enterchar(char, char); // 要求正确输入指定范围内的字符
void next(); // 进行下一步时的提醒操作
char showmenu(); // 显示主菜单
int main()
{
messageuser mes;
cout << "*************************** 欢迎来到通讯录管理软件 ***************************";
mes.load();
next();
char choice;
do
{
choice = showmenu();
switch(choice)
{
case '1': // 1. 添加新用户
{
mes.insertin();
next();
break;
}
case '2': // 2. 查找用户资料
{
mes.find();
next();
break;
}
case '3': // 3. 删除用户及相关资料
{
mes.del();
next();
break;
}
case '4': // 4. 保存现有的全部用户资料
{
mes.save();
next();
break;
}
case '5': // 5. 显示通讯录中全部用户资料
{
cout << '\n';
mes.printout();
next();
break;
}
case '6': // 6. 退出通讯录管理软件
{
cout << "\n O(∩_∩)O~~ 恭喜您,已成功退出系统。";
next();
break;
}
}
}while(choice != '6');
return 0;
}
char showmenu() // 显示主菜单
{
system("cls");
cin.sync();
cout << "*************************** 欢迎来到通讯录管理软件 ***************************\n"
<< "* *\n"
<< "* 1. 添加新用户 *\n"
<< "* 2. 查找用户资料 *\n"
<< "* 3. 删除用户及相关资料 *\n"
<< "* 4. 保存现有的全部用户资料 *\n"
<< "* 5. 显示通讯录中全部用户资料 *\n"
<< "* 6. 退出通讯录管理软件 *\n\n"
<< "--> 我的选择(填数字):";
return enterchar('1', '7');
}
char enterchar(char min, char max) // 输入错误时要求重新输入
{
char choice;
bool circulate(false);
do
{
do
{
cin >> choice;
circulate = false;
if (!cin)
{
std::cerr << "\n您的输入有误,请重新输入:";
circulate = true;
cin.sync();
cin.clear();
}
}while(circulate);
circulate = false;
if(!(choice >= min && choice < max))
{
std::cerr << "\n您的输入有误,请重新输入:";
circulate = true;
cin.clear();
cin.sync();
}
}while(circulate);
return choice;
}
void next() // 进行下一步时的提醒操作
{
cout << "\n请按任意键继续...\n";
char temp(_getch());
return;
}
// messageuser.h -- 实现电话簿全部相关功能
/*
* 数据成员: map<string, int> users; // 使用关联容器map类型高效地查找电话号码
* 成员函数: 主要有五个,分别可以实行添加,删除,输出,保存,读取的功能。
*/
#ifndef MESSAGEUSER_H_
#define MESSAGEUSER_H_
#include <iostream>
#include <string>
#include <utility>
#include <map>
using std::string;
class messageuser
{
private:
typedef std::map<string,int> type_map_si;
const string savefilename;
type_map_si users;
bool inserthelp(string, int);
bool findAllHelp(string);
bool findSingleHelp(string, type_map_si &);
bool deletehelp(string);
void savehelp(string);
void loadhelp(string);
public:
messageuser():savefilename("通讯录保存文件列表.txt"){}
void insertin();
void find();
void del();
void printout();
void save();
void load();
};
#endif
// messageuser.cpp -- 实现电话簿全部相关功能
/*
* 数据成员: map<string, int> users; // 使用关联容器map类型高效地查找电话号码
* 成员函数: 主要有五个,分别可以实行添加,删除,输出,保存,读取的功能。
*/
#include "messageuser.h"
#include <iostream>
#include <string>
#include <utility>
#include <map>
#include <fstream>
#include <set>
using std::string;
/** 添加函数
* bool inserthelp(string, int)
* 帮助插入操作:有一个函数帮助插入,第一个形参是用户名,第二个形参是电话号码。
* void insertin();
* 可以直接调用的公有函数,函数会提示有多少个需要插入的元素,根据插入成功失败进行相关提示。
*/
bool messageuser::inserthelp(string stemp, int itemp)
{
// users.insert(make_pair(stemp, itemp));
users.insert(type_map_si::value_type(stemp, itemp));
return true;
}
void messageuser::insertin()
{
using std::cout;
using std::cin;
using std::endl;
// 用户增加数目
cout << endl << "请输入需要增加的用户数目:";
int ntemp;
cin >> ntemp;
while(!cin)
{
cout << "\n输入错误,请重新输入:";
cin.sync();
cin.clear();
cin >> ntemp;
}
// 输入用户名及号码
int itemp;
string stemp;
for (int i = 0; i != ntemp; i++)
{
cout << endl << "请输入需要增加的第 " << i + 1 << " 位用户名:";
cin >> stemp;
if (findAllHelp(stemp))
{
std::cerr << "\n输入错误,这位用户的相关资料已存在,请重新输入需要添加的不重复用户:";
cin >> stemp;
}
cout << endl << "请输入需要增加的第 " << i + 1 << " 位用户的电话号码:";
cin >> itemp;
while(!cin)
{
cout << "\n输入错误,请重新输入这位用户的电话号码:";
cin.sync();
cin.clear();
cin >> itemp;
}
if(inserthelp(stemp, itemp))
;
else
{
cout << "\n该用户添加失败,有可能是用户名已存在,停止本次添加操作。";
return;
}
}
cout << "\n该用户组添加成功。";
cin.sync();
cin.clear();
cout << endl;
return;
}
/** 查找函数
* bool findAllhelp(string) 寻找用户名是否在该类的数据成员中,如果用户名完全匹配时则返回true.
* bool findSingleHelp(string) 寻找用户名是否在该类的数据成员中,如果用户名有匹配字符时则返回true.
* void find() 实现查找功能,函数会提示输入需要查找的元素名称并返回相关结果。
*/
bool messageuser::findAllHelp(string findname)
{
if ( users.empty() )
return false;
if ( users.find(findname) == users.end() )
return false;
else
return true;
}
bool messageuser::findSingleHelp(string findname, type_map_si & tsave)
{
string::size_type fnameSize( findname.size());
string::size_type fnum(0);
string::size_type tnum(0);
string::size_type tnameSize;
type_map_si::iterator truename = users.begin();
while(truename != users.end())
{
tnameSize = (truename -> first).size();
// 容器中每个用户名与输入的姓名作对比
do
{
if( findname[fnum] == (truename -> first)[tnum] )
{
if( fnum + 1 == fnameSize)
{ // 符合条件,添加元素进新容器
tsave[truename -> first] = truename -> second;
break;
}
else
{
++fnum;
++tnum;
}
}
else
{
if (fnum == 0)
{
++tnum;
}
else if ( fnum + 1 == fnameSize)
{
break;
}
else
{
++tnum;
}
}
}while(tnum != tnameSize + 1);
fnum = 0;
tnum = 0;
++truename;
}
if ( tsave.end() == tsave.begin() )
return false;
else
return true;
}
void messageuser::find()
{
using std::cin;
using std::cout;
using std::endl;
string fname;
type_map_si tempToPrint;
cout << "\n请输入需要查找的姓名:";
cin >> fname;
if ( findAllHelp(fname) ) // 全名符合的用户资料
{
cout << "\n恭喜,找到" << fname << " 的记录,电话号码为 " << users[fname] << endl;
}
else if ( findSingleHelp(fname, tempToPrint) ) // 有相关字符符合名字的用户资料
{
cout << "\n恭喜,找到 " << tempToPrint.size() << " 个相匹配的用户名。\n\n";
type_map_si::iterator tprint = tempToPrint.begin();
int sum(0);
cout << " 姓名\t\t 号码\n";
while(tprint != tempToPrint.end())
{
cout << tprint -> first << "\t\t" << tprint -> second << endl;
++tprint;
++sum;
}
cout << "\n已成功输出 " << sum << " 名用户资料\n";
}
else
{
cout << "\n对不起,未找到需要搜索的用户。" << endl;
}
return;
}
/** 删除函数
* bool deletehelp(string) 寻找用户名是否在该类的数据成员中,如果用户删除成功则返回true.
* void del() 实现删除功能时,函数会提示输入需要删除的元素,首先调用find函数判断是否存在元素,再进行相关操作,返回相关结果。
*/
bool messageuser::deletehelp(string dname)
{
if ( users.empty() )
return false;
if( users.erase(dname))
return true;
else
return false;
}
void messageuser::del()
{
using std::cout;
using std::cin;
using std::endl;
string dname;
cout << "\n请输入需要删除的资料的姓名:";
cin >> dname;
if( 0 == findAllHelp(dname) )
{
cout << "\n未找到该用户的资料,请核实后再进行操作。";
return;
}
if ( 0 == deletehelp(dname) )
{
cout << "\n意外,删除失败。";
}
else
{
cout << "\n恭喜,删除 " << dname << " 用户资料成功。";
}
return;
}
/** 显示函数
* void printout() 显示全部用户资料至界面。
*/
void messageuser::printout()
{
using std::cin;
using std::cout;
using std::endl;
type_map_si::iterator iter = users.begin();
if ( iter == users.end() )
{
cout << "此通讯录为空。\n";
return;
}
int sum(0); // 用以统计用户数目
cout << " 姓名\t\t 号码\n";
while(iter != users.end() )
{
cout << iter -> first << "\t\t" << iter -> second << endl;
++sum;
++iter;
}
cout << "\n已成功输出 " << sum << " 名用户资料\n";
return;
}
/** 保存函数
* bool messageuser::savedhelp();保存文件为二进制文件,若保存文件失败,则返回false;
* void messageuser::save(); 按要求输入保存文件的名称,完成保存文件功能。
*/
void messageuser::savehelp(string fname)
{
std::ofstream sfile;
sfile.open(fname.c_str(), std::ofstream::out | std::ofstream::binary);
type_map_si::iterator iter = users.begin();
while(iter != users.end() )
{
sfile << iter -> first << "\t\t" << iter -> second << " ";
++iter;
}
sfile.close();
return;
}
void messageuser::save()
{
using std::fstream;
using std::ofstream;
using std::ifstream;
using std::cout;
using std::cin;
fstream inout(savefilename.c_str(), fstream::out | fstream::in);
inout.close();
string sname("");
string comparen("");
char choice('r');
bool circulateInput;
bool circulateInput2;
cout << "请输入需要保存的文件名:";
do
{
inout.open(savefilename.c_str(), fstream::out | fstream::in);
circulateInput = false;
cin >> sname;
sname.append(".dat");
while( inout >> comparen )
// 文件名核对,是否有重名现象
if (comparen == sname)
{
cout << "\n注意,检测到已存在同名文件名。";
cout << std::unitbuf << "\n覆盖请按 " << "y"
<< " 否则请按 \"r\" 重新键入文件名:" << std::nounitbuf;
do
{
circulateInput2 = false;
cin >> choice;
while(!cin)
{
cin.clear();
cin.sync();
std::cerr << "\n输入错误,请重新输入选择:";
cin >> choice;
}
switch(choice)
{
case 'r': circulateInput = true;break;
case 'y': break;
default:
{
std::cerr << "\n输入错误,请重新输入选择:";
cin >> choice;
circulateInput2 = true;
break;
}
}
}while(circulateInput2);
}
inout.clear();
inout.close();
}while(circulateInput);
if (choice == 'r')
{
cout << "接受测试";
string cr(" ");
inout.open(savefilename.c_str(), fstream::out | fstream::in | fstream::app);
inout << sname << cr ;
inout.close();
}
savehelp(sname);
cout << "\n恭喜,文件保存成功!";
cout << std::endl;
return;
}
/** 读取函数
* bool messageuser::loadhelp();检测是否已存在已保存的文件,若存在,则返回true;
* void messageuser::load(); 完成文件读取功能。
*/
void messageuser::loadhelp(string lfile)
{
std::ifstream rfile;
string temp;
int itemp;
rfile.open(lfile.c_str(), std::ifstream::in | std::ifstream::binary);
type_map_si::iterator iter = users.begin();
while(!rfile.eof())
{
rfile >> temp >> itemp;
users[temp] = itemp;
}
rfile.close();
return;
}
void messageuser::load()
{
using std::cout;
using std::cin;
using std::ifstream;
int i(0);
string ofile;
cout << "\n\n正在读取已存储的通讯录文件列表...\n";
std::set<string> TempSaveName;
ifstream reado;
reado.open(savefilename.c_str(), ifstream::in);
if (reado.good())
{
while(!reado.eof())
{
reado >> ofile;
TempSaveName.insert(ofile);
if(!reado)
; // if控制,文档未到末尾时,防止多读取一次最后的空格,但是读取不到任何字符.
// 在文件中的格式为abc.dat cba.dat ,每个文件后都会有一个空格
else
{
cout << "\n" << ++i << ". "<< ofile;
}
}
cout << "\n\n请输入需要读取的文件名称,若不需读取,读输入\"no\":";
cin >> ofile;
if (TempSaveName.count(ofile) )
{
loadhelp(ofile);
cout << "\n读取成功!\n";
}
else if (ofile == "no")
{
cout << "\n操作成功!\n";
}
else
{
cout << "\n读取失败,请检查需要读取的文件的名字是否输入正确!"
<< "\n 还有一次输入机会,若输入错误,将直接进入系统:";
cin >> ofile;
if (TempSaveName.count(ofile) )
{
loadhelp(ofile);
cout << "\n读取成功!\n";
}
else if (ofile == "no")
{
cout << "\n操作成功!\n";
}
else
{
cout << "\n将直接进入系统操作。。。\n";
}
}
}
else
{
reado.clear();
cout << "\n尚未发现已存储的通讯文件。\n";
}
reado.close();
}