目录
一、整体介绍
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);
}
至此,我们就完成了对通讯录管理的设计与实现啦,你也快去试一下吧~