目录
一、整体介绍
1.1、需求和目标
项目的需求,实现一个叫做“通讯录管理”的基于命令行界面的通讯录管理软件。
我们的目标,是通过项目练习,初步掌握编写软件的方法和技巧。
1.2、整体功能描述
这个软件相对简单,只需要基于命令行做纯文本的交互,不涉及图形化界面。作为一个记录通讯录软件,主要有三大功能:添加联系人、显示联系人与修改联系人。也就是说,软件需要能够记录联系人的姓名与联系方式等相关信息,并提供查询显示联系人信息的功能,也可对联系人信息进行修改。而且为了让软件有真正的实用价值,需要对数据做持久保存:我们这里只需要存放在一个指定的文本文件中。
项目采用分级菜单方式,每一级菜单应该有“返回主菜单”功能;主菜单有“退出”选项。
二、页面及功能描述
2.1 主菜单
运行软件后首先应该显示主菜单。主菜单提供四个选项:“添加联系人”、“显示联系人”、"修改联系人"和“退出”,并在下方提示用户输入1-4的数字,选择不同的功能。
主菜单界面如下:
2.2 添加联系人菜单
在主菜单页面选择“1”,可以进入记账菜单(二级菜单)。添加联系人菜单提供三个选项:“办公”、“个人”和“退出”;并在下方提示用户按对应的数字进行功能选择。
记账菜单界面如下:
在添加联系人菜单页面选择“1”,可以记录一条办公类联系人信息。联系人信息包括:类型(办公/个人)、姓名、电话、住址。这里需要提示用户输入联系人的姓名、电话与住址信息,然后显示完成添加。
类似地,在联系人菜单页面选择“2”,可以记录一条个人类联系人信息。提示用户输入联系人的姓名、电话与住址,然后显示完成添加。
2.3 显示联系人菜单
在主菜单页面选择“2”,可以进入显示联系人菜单。显示联系人菜单提供四个选项:“全部”、“办公”、“个人”和“退出”;并在下方提示用户按对应的数字进行功能选择。
显示联系人菜单界面如下:
在显示联系人菜单页面,选择“1”可以显示全部联系人信息;选择“2”可以显示所有办公类联系人;选择“3”可以显示所有个人类联系人。用户选择之后,除了应该显示对应的联系人信息外,还应该对所有联系人个数进行统计汇总。
对应地,选择“1”之后列出所有联系人,统计的是通讯录的总条数;选择“2”之后列出所有办公类联系人,统计办公类总条数;选择“3”之后列出所有个人类联系人,统计个人类总条数。
2.4 修改联系人菜单
在主菜单页面选择“3”,可以进入修改联系人菜单。修改联系人菜单提供三个选项:“办公”、“个人”和“退出”;并在下方提示用户按对应的数字进行功能选择。
显示联系人菜单界面如下:
在修改联系人菜单页面,选择“1”可以修改办公类别联系人信息;选择“2”可以个人类别联系人信息。用户选择之后,根据提示输入要修改的联系人姓名,如没有找到,提示未找到此人;如找到此联系人,输入更改后的姓名、电话与住址,并提示修改成功。
2.5 退出功能
在主菜单页面,选择“3”可以退出软件。页面将做确认退出的对话提醒:如果用户输入“Y”则退出,输入“N”则返回主菜单页面。
三、流程设计
3.1 主流程
3.2 添加操作流程
3.3 显示联系人操作流程
3.4 修改联系人操作流程
四、代码设计
4.1 核心思路
通讯录软件处理的数据,就是添加或者修改的“联系人”。每一条联系人数据,都应该包含类型、姓名、电话、住址四部分,这可以构建一个结构体类型 Person来表示。
而程序运行的开始,应该读取保存在文件中的数据,读取之后应该是一个 Person类型的数组。
在流程控制方面,如果用户不选择退出,程序就不会结束,所以应该用一个while循环来处理整个流程;当用户确认退出时,更改一个标志位,用来退出循环。
每一级菜单做键盘选择后,可以用 switch 分支语句来处理;不同的功能模块,可以包装成函数。
4.2 项目文件分类设计
4.2.1 头文件
对于一个C++项目来说,构建整体架构很重要的一步就是定义头文件。一般我们会把全局变量、函数声明、以及结构体的定义都放在头文件中。
本项目可以设计两个头文件,来管理不同的内容:
- 结构体 Person的定义,以及涉及到账目操作的函数声明,都可以放在一个头文件 person_item.h 中;
- 其它一些通用的设置和功能性函数,可以放在另一个头文件common.h 中
4.2.2 源文件
真正的代码实现都放在源文件中。根据不同的用途,项目中可以用四个源文件来实现对应的功能
- 主体代码可以放在一个源文件 person_main.cpp 中
- 绘制菜单的函数都放在menus.cpp中
- 通用函数(比如读取键盘输入)放在read_input.cpp中
- 针对账目的所有操作函数(比如记账、查询)全部放在operations.cpp中
4.2.3 代码实现步骤
1、实现整体框架和主题流程
- 在头文件person_item.h中定义好结构体;在common.h 中定义通用配置
- 在源文件person_main.cpp的主函数中,参照主流程图实现主体流程
2、填充具体功能
- 在menus.cpp中定义绘制各级菜单的函数;函数声明放在common.h里
- 在read_input.cpp中定义读取键盘输入的函数,包括读取菜单选择、读取输入的金额数、读取确认退出信息;这些函数都需要对输入做合法性校验,并返回正确的值;函数声明放在common.h里
- 在operations.cpp中定义从文件加载数据、记账、查询对应的函数。
3、各功能的细分
1)添加功能
- 首先需要读取键盘输入的姓名、电话和备住址信息;
- 然后将其保存在一个Person对象中,并添加到vector,同时写入文件
2)显示功能
- 需要用for循环遍历整个vector
- 通过条件筛选需要的联系人打印输出,并统计总和
2)修改功能
- 需要用for循环遍历整个vector
- 通过条件匹配到联系人,对数据进行更改
- 将更改后的数组重新写入文件
4)退出功能
- 提示用户确认退出
- 用户确认后,更改循环标志位,退出循环
4.3 代码具体实现
4.3.1 定义头文件
首先可以在common.h中,引入相应的库,定义出需要的全局变量,以及声明绘制菜单和读取键盘输入的通用函数。通用设置,通用功能性函数声明。
#pragma once //通用设置,通用功能性函数声明 #include<iostream> //输入输出 #include<string> //字符串操作 #include<fstream> //文件操作 #include<vector> #include <algorithm> using namespace std; #define max 1000 #define OFFICE "办公" #define MYSELF "个人" #define FILENAME "book.txt" //绘制菜单函数 void menus_main(); void menus_app(); void menus_show(); void menus_revise(); void menus_delete(); //读取键盘输入进行合法性校验的函数 char readMenuSelection(int options); char readquitconfirm();
然后在person_item.h定义核心数据结构:结构体 Person,并声明涉及到账目操作的函数。结构体定义,涉及账目操作的函数声明。
#pragma once //结构体定义,涉及通讯录管理操作的函数声明 #include "common.h" //设计联系人结构体 struct Person { string PersonItem; //类别,1、办公,2、个人 string name; //姓名 string iphone; //电话 string address; //单位 }; //针对通讯录数据进行操作的函数 //加载通讯录数据 void loadDataFromFile(vector<Person>& ps); //添加 void person_add(vector<Person>& ps); void insertIntoFile(const Person& p); void person_addmyself(vector<Person>& ps); void person_addOffice(vector<Person>& ps); //显示 void person_show(vector<Person>& ps); void showPerson(const vector<Person>& ps); void showPerson(const vector<Person>& ps, const string PersonItem); void printPerson(const Person& p); //修改 void person_revise(vector<Person>& ps); void change(vector<Person>& ps, string item);
4.3.2 实现主体流程
在源文件 person_main.cpp 的主函数中,参照主流程图实现主体流程。
代码如下:
#include "common.h" #include "person_item.h" int main() { //从文件加载数据 vector<Person> ps; loadDataFromFile(ps); bool quit = false; while (!quit) { //1、显示主菜单 menus_main(); //2、读取键盘输入,并做合法性校验 char key= readMenuSelection(5); switch (key) { case '1': //1、添加联系人 menus_app(); //添加菜单 person_add(ps); //添加功能 break; case '2': //2、显示联系人 menus_show(); //显示菜单 person_show(ps); //显示功能 break; case '3': //3、修改联系人 menus_revise(); //修改菜单 person_revise(ps); //修改功能 break; case '4': //退出 cout << "确认退出? (Y/N)"; if (readquitconfirm() == 'Y') quit = true; break; default: break; } } }
4.3.3 绘制菜单
在 menus.cpp 中定义绘制各级菜单的函数;对应的函数声明放在 common.h里。
代码如下:
//菜单函数 #include "common.h" //主菜单 void menus_main() { system("cls"); //清屏 cout << "-----------------------------------------------------------" << endl; cout << "| |" << endl; cout << "|*************** 1 添加联系人 ********************|" << endl; cout << "|*************** 2 显示联系人 ********************|" << endl; cout << "|*************** 3 修改联系人 ********************|" << endl; cout << "|*************** 4 退 出 ********************|" << endl; cout << "|_________________________________________________________|" << endl; cout << "\n请选择(1 - 4):"; } //添加联系人菜单 void menus_app() { cout << "-----------------------------------------------------------" << endl; cout << "| |" << endl; cout << "|*************** 请选择添加联系人类别 ********************|" << endl; cout << "|*************** 1 办 公 ********************|" << endl; cout << "|*************** 2 个 人 ********************|" << endl; cout << "|*************** 3 退 出 ********************|" << endl; cout << "|_________________________________________________________|" << endl; cout << "\n请选择(1 - 3):"; cin.get(); } //显示联系人菜单 void menus_show() { cout << "-----------------------------------------------------------" << endl; cout << "| |" << endl; cout << "|*************** 请选择显示联系人类别 ********************|" << endl; cout << "|*************** 1 全 部 ********************|" << endl; cout << "|*************** 2 办 公 ********************|" << endl; cout << "|*************** 3 个 人 ********************|" << endl; cout << "|*************** 4 退 出 ********************|" << endl; cout << "|_________________________________________________________|" << endl; cout << "\n请选择(1 - 4):"; } //修改联系人菜单 void menus_revise() { cout << "-----------------------------------------------------------" << endl; cout << "| |" << endl; cout << "|*************** 请选择修改联系人类别 ********************|" << endl; cout << "|*************** 1 办 公 ********************|" << endl; cout << "|*************** 2 个 人 ********************|" << endl; cout << "|*************** 3 退 出 ********************|" << endl; cout << "|_________________________________________________________|" << endl; cout << "\n请选择(1 - 3):"; } //删除联系人菜单 void menus_delete() { cout << "-----------------------------------------------------------" << endl; cout << "| |" << endl; cout << "|*************** 请选择删除联系人类别 ********************|" << endl; cout << "|*************** 1 办 公 ********************|" << endl; cout << "|*************** 2 个 人 ********************|" << endl; cout << "|*************** 3 退 出 ********************|" << endl; cout << "|_________________________________________________________|" << endl; cout << "\n请选择(1 - 3):"; }
4.3.4 读取键盘输入
在 read_input.cpp 中定义读取键盘输入的函数,包括读取菜单选择、读取输入的金额数、读取确认退出信息;这些函数都需要对输入做合法性校验,并返回正确的值。
代码如下:
//读取输入函数 #include "common.h" //读取键盘输入的菜单选项,进行合法性校验 char readMenuSelection(int options) { string str; while (true) { getline(cin, str); //进行合法化校验 if (str.size() != 1 || str[0] - '0' <= 0 || str[0] - '0' > options) cout << "输入错误,请重新选择:"; else break; } //输入合法 return str[0]; } //读取确认退出信息,并进行合法性校验 char readquitconfirm() { string str; while (true) { getline(cin, str); //进行合法性校验 if (str.size() != 1 || toupper(str[0]) != 'Y' && toupper(str[0]) != 'N') cout << "输入错误,请重新选择:"; else break; } //输入合法 return toupper(str[0]); }
4.3.5 实现具体操作功能
在 operations.cpp 中,定义从文件加载数据、添加、显示、修改等操作对应的函数。
- (1)从文件加载数据
loadDataFromFile():逐行读取文件中数据,按空格将每个词赋值给Person中的每个成员,得到一个Person对象,并把它保存到数组中。
由于数组长度是固定的,这里应该用可变长的容器对象vector<Person>来保存;为了能够在函数中修改vector,应该把它的引用作为函数参数传入。
- (2)添加功能
首先需要读取键盘输入的金额和备注信息;然后将其保存在一个AccountItem 对象中,并添加到数组,同时写入文件。
person_add():添加联系人主体函数
person_addOffice():记录办公类联系人,将数据添加到vector中,并调用insertIntoFile()函数,写入文件做持久化保存
person_addmyself():记录个人类联系人,将数据添加到vector中,并调用insertIntoFile()函数,写入文件做持久化保存
insertIntoFile():将一条账目写入文件中。
- (3)显示功能
需要用for循环遍历整个数组,通过条件 筛选需要的联系人打印输出,并统计总和。
person_show():查询主体函数
showPerson():显示通讯录联系人
- (4)修改功能
需要用for循环遍历整个数组,通过条件 筛选需要的联系人进行更改,更改后使用for循环再次遍历整个数组,重新写入文件中做持久化保存。
person_revise():修改联系人主体函数
change():遍历数组找到联系人进行更改,将更改后的结果重新写入文件
具体代码如下:
//涉及通讯录操作的函数 #include "common.h" #include "person_item.h" //读取文件中的账目数据 void loadDataFromFile(vector<Person>& ps) { ifstream input(FILENAME); //逐行读取每一条数据,包装成Person对象 Person p; string line; while (input >> p.PersonItem >> p.name >> p.iphone >> p.address) { ps.push_back(p); } input.close(); } //----------------------------------------添加----------------------------------------------- //添加操作 void person_add(vector<Person>& ps) { //读取键盘选择,并做合法化校验 char key = readMenuSelection(3); switch (key) { case '1': //办公 person_addOffice(ps); break; case '2': //个人 person_addmyself(ps); break; case '3': //返回主菜单 break; default: break; } } //记录办公类别通讯录 void person_addOffice(vector<Person>& ps) { //新构建一个Person对象 Person p; //类型已经确定,就是办公 p.PersonItem = OFFICE; //与用户交互,键盘输入姓名,电话,住址 cout << "\n请输入姓名:"; getline(cin, p.name); cout << "\n请输入电话:"; getline(cin, p.iphone); cout << "\n请输入住址:"; getline(cin, p.address); //添加到vector中 ps.push_back(p); //写入文件做持久化保存 insertIntoFile(p); //显示成功信息 cout << "\n----------------添加成功----------------\n"; cout << "\n请按回车键返回主菜单......." << endl; string str; getline(cin, str); } //记录个人类别通讯录 void person_addmyself(vector<Person>& ps) { //新构建一个Person对象 Person p; //类型已经确定,就是办公 p.PersonItem = MYSELF; //与用户交互,键盘输入姓名,电话,住址 cout << "\n请输入姓名:"; getline(cin, p.name); cout << "\n请输入电话:"; getline(cin, p.iphone); cout << "\n请输入住址:"; getline(cin, p.address); //添加到vector中 ps.push_back(p); //写入文件做持久化保存 insertIntoFile(p); //显示成功信息 cout << "\n----------------添加成功----------------\n"; cout << "\n请按回车键返回主菜单......." << endl; string str; getline(cin, str); } //将一条数据写入文件中 void insertIntoFile(const Person& p) { //创建一个ofstream对象,第二个参数表示以追加的方式进行写入 ofstream output(FILENAME, ios::out | ios::app); output << p.PersonItem << "\t" << p.name << "\t" << p.iphone << "\t" << p.address << endl; output.close(); } //-------------------------------------显示---------------------------------------------------------------- void person_show(vector<Person>& ps) { //读取键盘选择,并做合法性校验 char key = readMenuSelection(4); switch (key) { case '1': //所有 showPerson(ps); break; case '2': //办公 showPerson(ps,OFFICE); break; case '3': //个人 showPerson(ps,MYSELF); break; case '4': //返回主菜单 break; default: break; } } //显示所有通讯录 void showPerson(const vector<Person>& ps) { cout << "----------------------查询结果-----------------------" << endl; cout << "\n类型\t\t姓名\t\t电话\t\t住址\n" << endl; //遍历所有通讯录,并添加数目 int count = 0; for (auto p : ps) { printPerson(p); ++count; } //输出信息 cout << "===========================================================" << endl; cout << "总联系人数:" << count << endl; cout << "\n请按回车键返回主菜单......" << endl; string str; getline(cin, str); } //函数重载,显示联系人 void showPerson(const vector<Person>& ps,const string PersonItem) { cout << "----------------------查询结果-----------------------" << endl; cout << "\n类型\t\t姓名\t\t电话\t\t\t住址\n" << endl; //遍历所有通讯录,并添加数目 int count = 0; for (auto p : ps) { if (p.PersonItem != PersonItem) continue; printPerson(p); ++count; } //输出信息 cout << "===========================================================" << endl; cout << "总联系人数:" << count << endl; cout << "\n请按回车键返回主菜单......" << endl; string str; getline(cin, str); } //打印每一条通讯录数据 void printPerson(const Person& p) { cout << p.PersonItem << "\t\t" << p.name << "\t\t" << p.iphone << "\t\t" << p.address << endl; } //----------------------------------------修改--------------------------------------------------------------------------- //修改联系人主体函数 void person_revise(vector<Person>& ps) { //读取键盘选择,并做合法化校验 char key = readMenuSelection(3); switch (key) { case '1': //办公 change(ps,OFFICE); break; case '2': //个人 change(ps,MYSELF); break; case '3': //退出 break; default: break; } } //修改联系人操作函数 void change(vector<Person>& ps, string item) { cout << "请输入您要修改的联系人姓名:"; string name; bool found = false; while (!found) { getline(cin, name); for (auto& p : ps) { if (p.PersonItem == item && p.name == name) { string changeNmae; string changeIphone; string changeAddress; cout << "请输入更改后的姓名:"; getline(cin, changeNmae); p.name = changeNmae; cout << endl; cout << "请输入更改后的电话:"; getline(cin, changeIphone); p.iphone = changeIphone; cout << endl; cout << "请输入更改后的住址:"; getline(cin, changeAddress); p.address = changeAddress; cout << endl; found = true; break; } } if (!found) { cout << "未找到此联系人,请重新输入:" << endl; } } // 将更改写回到文件, trunc模式会将文件中的数据清空,然后重新写入新的内容 ofstream outputFile(FILENAME, ios::out | ios::trunc); if (outputFile.is_open()) { for (const auto& p : ps) { outputFile << p.PersonItem << "\t" << p.name << "\t" << p.iphone << "\t" << p.address << endl; } outputFile.close(); } else { cerr << "无法打开文件进行写入." << endl; } // 显示成功信息 cout << "----------------修改成功----------------" << endl; cout << "请按回车键返回主菜单......." << endl; string str; getline(cin, str); }
至此,我们就完成了对通讯录管理的设计与实现啦,你也快去试一下吧~