03_通讯录小项目设计DOS版V1.0(小钱版)[2011-09-18]

 

 引言

(一)程序名称

通讯录小项目设计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();
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值