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