目录
1 需求说明
1.1 菜单显示
1.2 登记收入
1.3 登记支出
1.4 显示收支明细
1.5 退出
2 流程分析
2.1 总流程图
2.2 登记收入流程图
2.3 登记支出流程图
2.4 收支明细流程图
2.5 退出流程图
3 代码实现
3.1 框架搭建
3.2 收支明细功能
3.3 登记收入功能
3.4 登记支出功能
3.5 退出功能
4 安全升级
5 代码升级
5.1 抽取结构体
5.2 使用函数封装相应功能
1 需求说明
1.1 菜单显示
进入系统,展示主菜单,输入各功能对应的数字编号选择要进行的操作,如下图:
1.2 登记收入
输入2,进入 “登记收入”,需输入“本次收入金额” 和 “本次收入说明”,输入完成后会再次进入主菜单。
1.3 登记支出
输入 3,进入“登记支出”,需输入 “本次支出金额” 和 “本次支出说明”,输入完成后会判断余额是否充足,如果余额充足会再次进入主菜单。
如果余额不足,会给出相应的提示。
1.4 显示收支明细
输入 1,进入“收支明细”,如下图所示:
当没有任何收支明细时,提示 "当前没有收支明细... 来一笔吧!"
显示完收支明细,会再次进入主菜单。
1.5 退出
用户输入 4 退出时,给出提示 "你确定要退出吗? y/n",必须输入正确的 y/n ,否则循环输入指令,直到输入 y 或者 n。
2 流程分析
2.1 总流程图
2.2 登记收入流程图
2.3 登记支出流程图
2.4 收支明细流程图
2.5 退出流程图
3 代码实现
3.1 框架搭建
下面的代码框架提供了一个基本的家庭收支记账软件的结构。通过一个主菜单,用户可以选择查看收支明细、登记收入、登记支出或退出程序。每个选项对应一个简单的提示信息,后续可以在此基础上扩展具体的功能实现。
#include <stdio.h>
int main()
{
int loop = 1; // 循环标志,用于控制程序是否继续运行
// 主循环,直到用户选择退出
do
{
// 打印菜单选项
printf("\n\n---------家庭收支记账软件-----------------");
printf("\n1 收支明细");
printf("\n2 登记收入");
printf("\n3 登记支出");
printf("\n4 退出");
printf("\n选择(1-4):");
int selection; // 存储用户的选择:1:收支明细 2:登记收入 3:登记支出 4:退出
scanf("%d", &selection); // 读取用户的选择
// 根据用户的选择执行相应的操作
switch (selection)
{
case 1:
// 显示收支明细的功能(此处仅为提示信息)
puts("1.收支明细");
break;
case 2:
// 登记收入的功能(此处仅为提示信息)
puts("2.登记收入");
break;
case 3:
// 登记支出的功能(此处仅为提示信息)
puts("3.登记支出");
break;
case 4:
// 用户选择退出,设置循环标志为 0 以结束循环
puts("4.退出");
loop = 0;
break;
default:
// 如果用户输入了无效的选项,则不执行任何操作
puts("无效的选择,请重新选择!");
break;
}
} while (loop); // 当 loop 为 1 时,继续循环
// 程序正常结束
return 0;
}
3.2 收支明细功能
“收支明细” 功能用于显示用户当前的所有收支记录。如果用户还没有进行任何收支记录,程序会提示用户当前没有收支明细,并建议用户进行一笔记录。如果已经有记录,程序会显示详细的收支记录信息,包括每笔收支的类型、金额、账户余额和说明。
#include <stdio.h>
int main()
{
int loop = 1; // 循环标志,用于控制程序是否继续运行
int hasRecord = 0; // 标记是否有记录,0 表示没有记录,1 表示有记录
// 初始化收支记录信息,包含标题
// 注意:字符串如果太长,可以使用双引号进行换行隔开,双引号之间没有任何字符
char details[3000] = "-----------------当前收支明细记录-----------------\n"
"收支\t收支金额\t\t账户金额\t\t说明\n"; // 存储收支的记录信息
// 主循环,直到用户选择退出
do
{
// 打印菜单选项
printf("\n\n---------家庭收支记账软件-----------------");
printf("\n1 收支明细");
printf("\n2 登记收入");
printf("\n3 登记支出");
printf("\n4 退出");
printf("\n选择(1-4):");
int selection; // 存储用户的选择:1:收支明细 2:登记收入 3:登记支出 4:退出
scanf("%d", &selection); // 读取用户的选择
// 根据用户的选择执行相应的操作
switch (selection)
{
case 1:
// 显示收支明细
if (!hasRecord)
{
puts("当前没有收支明细…来一笔吧!"); // 如果没有记录,提示用户
}
else
{
puts(details); // 显示收支记录
}
break;
case 2:
// 登记收入的功能(此处仅为提示信息)
puts("2.登记收入");
break;
case 3:
// 登记支出的功能(此处仅为提示信息)
puts("3.登记支出");
break;
case 4:
// 用户选择退出,设置循环标志为 0 以结束循环
puts("4.退出");
loop = 0;
break;
default:
// 如果用户输入了无效的选项,则不执行任何操作
puts("无效的选择,请重新选择!");
break;
}
} while (loop); // 当 loop 为 1 时,继续循环
// 程序正常结束
return 0;
}
3.3 登记收入功能
“登记收入” 功能允许用户记录一笔收入,包括收入的金额和说明。程序会更新账户余额,并将这条收入记录追加到收支记录中,并标记 hasRecord 为 1,表示已经有记录。
#include <stdio.h>
#include <string.h> // 包含字符串处理函数的头文件
int main()
{
int loop = 1; // 循环标志,用于控制程序是否继续运行
int hasRecord = 0; // 标记是否有记录,0 表示没有记录,1 表示有记录
// 初始化收支记录信息,包含标题
// 注意:字符串如果太长,可以使用双引号进行换行隔开,双引号之间没有任何字符
char details[3000] = "-----------------当前收支明细记录-----------------\n"
"收支\t收支金额\t\t账户金额\t\t说明\n"; // 存储收支的记录信息
double initBalance = 1000; // 初始账户余额
// 主循环,直到用户选择退出
do
{
// 打印菜单选项
printf("\n\n---------家庭收支记账软件-----------------");
printf("\n1 收支明细");
printf("\n2 登记收入");
printf("\n3 登记支出");
printf("\n4 退出");
printf("\n选择(1-4):");
int selection; // 存储用户的选择:1:收支明细 2:登记收入 3:登记支出 4:退出
scanf("%d", &selection); // 读取用户的选择
// 根据用户的选择执行相应的操作
switch (selection)
{
case 1:
// 显示收支明细
if (!hasRecord)
{
puts("当前没有收支明细…来一笔吧!"); // 如果没有记录,提示用户
}
else
{
puts(details); // 显示收支记录
}
break;
case 2:
// 登记收入的功能
printf("%s", "本次收入金额:");
double addMoney;
scanf("%lf", &addMoney); // 读取收入金额
printf("%s", "本次收入说明:");
char addDetail[100];
scanf("%s", addDetail); // 读取收入说明
// 修改账户余额
initBalance += addMoney;
char addDetails[300]; // 临时存储用户输入的信息
// 将用户输入的信息拼接成一个字符串,并追加到收支记录中
sprintf(addDetails, "收入\t+%lf\t\t%lf\t\t%s\n", addMoney, initBalance, addDetail);
strcat(details, addDetails);
// 标记已有记录
hasRecord = 1;
puts("登记收入完成");
break;
case 3:
// 登记支出的功能(此处仅为提示信息)
puts("3.登记支出");
break;
case 4:
// 用户选择退出,设置循环标志为 0 以结束循环
puts("4.退出");
loop = 0;
break;
default:
// 如果用户输入了无效的选项,则不执行任何操作
puts("无效的选择,请重新选择!");
break;
}
} while (loop); // 当 loop 为 1 时,继续循环
// 程序正常结束
return 0;
}
3.4 登记支出功能
“登记支出” 功能允许用户记录一笔支出,包括支出的金额和说明。程序会检查账户余额是否足够支付这笔支出,如果余额充足,程序会更新账户余额,并将这条支出记录追加到收支记录中。标记 hasRecord 为 1,表示已经有记录。
#include <stdio.h>
#include <string.h> // 包含字符串处理函数的头文件
int main()
{
int loop = 1; // 循环标志,用于控制程序是否继续运行
int hasRecord = 0; // 标记是否有记录,0 表示没有记录,1 表示有记录
// 初始化收支记录信息,包含标题
// 注意:字符串如果太长,可以使用双引号进行换行隔开,双引号之间没有任何字符
char details[3000] = "-----------------当前收支明细记录-----------------\n"
"收支\t收支金额\t\t账户金额\t\t说明\n"; // 存储收支的记录信息
double initBalance = 1000; // 初始账户余额
// 主循环,直到用户选择退出
do
{
// 打印菜单选项
printf("\n\n---------家庭收支记账软件-----------------");
printf("\n1 收支明细");
printf("\n2 登记收入");
printf("\n3 登记支出");
printf("\n4 退出");
printf("\n选择(1-4):");
int selection; // 存储用户的选择:1:收支明细 2:登记收入 3:登记支出 4:退出
scanf("%d", &selection); // 读取用户的选择
// 根据用户的选择执行相应的操作
switch (selection)
{
case 1:
// 显示收支明细
if (!hasRecord)
{
puts("当前没有收支明细…来一笔吧!"); // 如果没有记录,提示用户
}
else
{
puts(details); // 显示收支记录
}
break;
case 2:
// 登记收入的功能
printf("%s", "本次收入金额:");
double addMoney;
scanf("%lf", &addMoney); // 读取收入金额
printf("%s", "本次收入说明:");
char addDetail[100];
scanf("%s", addDetail); // 读取收入说明
// 修改账户余额
initBalance += addMoney;
char addDetails[300]; // 临时存储用户输入的信息
// 将用户输入的信息拼接成一个字符串,并追加到收支记录中
sprintf(addDetails, "收入\t+%lf\t\t%lf\t\t%s\n", addMoney, initBalance, addDetail);
strcat(details, addDetails);
// 标记已有记录
hasRecord = 1;
puts("登记收入完成");
break;
case 3:
// 登记支出的功能
printf("%s", "本次支出金额:");
double minusMoney;
scanf("%lf", &minusMoney); // 读取支出金额
if (minusMoney > initBalance)
{
puts("余额不足…"); // 如果余额不足,提示用户并中断支出操作
break;
}
else
{
// 修改账户余额
initBalance -= minusMoney;
printf("%s", "本次支出说明:");
char minusDetail[100];
scanf("%s", minusDetail); // 读取支出说明
char minusDetails[300]; // 临时存储用户输入的信息
// 将用户输入的信息拼接成一个字符串,并追加到收支记录中
sprintf(minusDetails, "支出\t-%lf\t\t%lf\t\t%s\n", minusMoney, initBalance, minusDetail);
strcat(details, minusDetails);
// 标记已有记录
hasRecord = 1;
puts("登记支出完成");
}
break;
case 4:
// 用户选择退出,设置循环标志为 0 以结束循环
puts("4.退出");
loop = 0;
break;
default:
// 如果用户输入了无效的选项,则不执行任何操作
puts("无效的选择,请重新选择!");
break;
}
} while (loop); // 当 loop 为 1 时,继续循环
// 程序正常结束
return 0;
}
3.5 退出功能
“退出功能” 允许用户确认是否要退出程序。当用户选择退出时,程序会提示用户确认是否真的要退出。如果用户确认退出,程序会设置循环标志 loop 为 0,结束主循环并退出程序。如果用户取消退出,程序将继续运行。
#include <stdio.h>
#include <string.h> // 包含字符串处理函数的头文件
int main()
{
int loop = 1; // 循环标志,用于控制程序是否继续运行
int hasRecord = 0; // 标记是否有记录,0 表示没有记录,1 表示有记录
// 初始化收支记录信息,包含标题
// 注意:字符串如果太长,可以使用双引号进行换行隔开,双引号之间没有任何字符
char details[3000] = "-----------------当前收支明细记录-----------------\n"
"收支\t收支金额\t\t账户金额\t\t说明\n"; // 存储收支的记录信息
double initBalance = 1000; // 初始账户余额
// 主循环,直到用户选择退出
do
{
// 打印菜单选项
printf("\n\n---------家庭收支记账软件-----------------");
printf("\n1 收支明细");
printf("\n2 登记收入");
printf("\n3 登记支出");
printf("\n4 退出");
printf("\n选择(1-4):");
int selection; // 存储用户的选择:1:收支明细 2:登记收入 3:登记支出 4:退出
scanf("%d", &selection); // 读取用户的选择
// 根据用户的选择执行相应的操作
switch (selection)
{
case 1:
// 显示收支明细
if (!hasRecord)
{
puts("当前没有收支明细…来一笔吧!"); // 如果没有记录,提示用户
}
else
{
puts(details); // 显示收支记录
}
break;
case 2:
// 登记收入的功能
printf("%s", "本次收入金额:");
double addMoney;
scanf("%lf", &addMoney); // 读取收入金额
printf("%s", "本次收入说明:");
char addDetail[100];
scanf("%s", addDetail); // 读取收入说明
// 修改账户余额
initBalance += addMoney;
char addDetails[300]; // 临时存储用户输入的信息
// 将用户输入的信息拼接成一个字符串,并追加到收支记录中
sprintf(addDetails, "收入\t+%lf\t\t%lf\t\t%s\n", addMoney, initBalance, addDetail);
strcat(details, addDetails);
// 标记已有记录
hasRecord = 1;
puts("登记收入完成");
break;
case 3:
// 登记支出的功能
printf("%s", "本次支出金额:");
double minusMoney;
scanf("%lf", &minusMoney); // 读取支出金额
if (minusMoney > initBalance)
{
puts("余额不足…"); // 如果余额不足,提示用户并中断支出操作
break;
}
else
{
// 修改账户余额
initBalance -= minusMoney;
printf("%s", "本次支出说明:");
char minusDetail[100];
scanf("%s", minusDetail); // 读取支出说明
char minusDetails[300]; // 临时存储用户输入的信息
// 将用户输入的信息拼接成一个字符串,并追加到收支记录中
sprintf(minusDetails, "支出\t-%lf\t\t%lf\t\t%s\n", minusMoney, initBalance, minusDetail);
strcat(details, minusDetails);
// 标记已有记录
hasRecord = 1;
puts("登记支出完成");
}
break;
case 4:
// 退出功能
puts("你确定要退出吗(y/n):");
char isExit;
while (1)
{
// getchar(); // 清除缓冲区(因为输入 4 之后,还按了一个回车)
// 读取用户输入,确认是否退出
scanf(" %c", &isExit); // 注意 %c 前面的空格,用于跳过前面的空白符,或者在前面使用 getchar 读取多余的换行符
if (isExit == 'y' || isExit == 'n')
{
break; // 如果输入有效,结束循环
}
else
{
puts("你输入有误,请从新输入(y/n):"); // 提示用户重新输入
}
}
if (isExit == 'y')
{
// 设置循环标志为 0,准备退出程序
loop = 0;
puts("欢迎再次来到家庭记账软件^_^");
}
break;
default:
// 如果用户输入了无效的选项,则不执行任何操作
puts("无效的选择,请重新选择!");
break;
}
} while (loop); // 当 loop 为 1 时,继续循环
// 退出程序时的提示信息
puts("你退出了家庭财务软件~");
// 程序正常结束
return 0;
}
自此,本项目已成功实现,但仍存在需要进一步优化的地方。具体详情,请见后文。
4 安全升级
scanf 函数在读取输入时,如果输入不符合预期格式,会留下未读取的数据在输入缓冲区中,这会导致后续的 scanf 调用继续读取这些未处理的数据,从而形成无限循环。为了防止这种情况,可以在读取用户输入时检查 scanf 的返回值,并在必要时清理输入缓冲区。
#include <stdio.h>
#include <string.h> // 包含字符串处理函数的头文件
int main()
{
int loop = 1; // 循环标志,用于控制程序是否继续运行
int hasRecord = 0; // 标记是否有记录,0 表示没有记录,1 表示有记录
// 初始化收支记录信息,包含标题
// 注意:字符串如果太长,可以使用双引号进行换行隔开,双引号之间没有任何字符
char details[3000] = "-----------------当前收支明细记录-----------------\n"
"收支\t收支金额\t\t账户金额\t\t说明\n"; // 存储收支的记录信息
double initBalance = 1000; // 初始账户余额
// 主循环,直到用户选择退出
do
{
// 打印菜单选项
printf("\n\n---------家庭收支记账软件-----------------");
printf("\n1 收支明细");
printf("\n2 登记收入");
printf("\n3 登记支出");
printf("\n4 退出");
printf("\n选择(1-4):");
int selection; // 存储用户的选择:1:收支明细 2:登记收入 3:登记支出 4:退出
if (scanf("%d", &selection) != 1) // 检查 scanf 是否成功读取一个整数
{
puts("无效的输入,请输入一个数字 (1-4)!");
// 清理输入缓冲区
while (getchar() != '\n')
;
continue; // 重新显示菜单
}
// 根据用户的选择执行相应的操作
switch (selection)
{
case 1:
// 显示收支明细
if (!hasRecord)
{
puts("当前没有收支明细…来一笔吧!"); // 如果没有记录,提示用户
}
else
{
puts(details); // 显示收支记录
}
break;
case 2:
// 登记收入的功能
printf("%s", "本次收入金额:");
double addMoney;
if (scanf("%lf", &addMoney) != 1) // 检查 scanf 是否成功读取一个浮点数
{
puts("无效的输入,请输入一个数字!");
while (getchar() != '\n')
; // 清理输入缓冲区
continue; // 重新显示菜单
}
printf("%s", "本次收入说明:");
char addDetail[100];
if (scanf("%99s", addDetail) != 1) // 检查 scanf 是否成功读取一个字符串
{
puts("无效的输入,请输入一个有效的说明!");
while (getchar() != '\n')
; // 清理输入缓冲区
continue; // 重新显示菜单
}
// 修改账户余额
initBalance += addMoney;
char addDetails[300]; // 临时存储用户输入的信息
// 将用户输入的信息拼接成一个字符串,并追加到收支记录中
sprintf(addDetails, "收入\t+%lf\t\t%lf\t\t%s\n", addMoney, initBalance, addDetail);
strcat(details, addDetails);
// 标记已有记录
hasRecord = 1;
puts("登记收入完成");
break;
case 3:
// 登记支出的功能
printf("%s", "本次支出金额:");
double minusMoney;
if (scanf("%lf", &minusMoney) != 1) // 检查 scanf 是否成功读取一个浮点数
{
puts("无效的输入,请输入一个数字!");
while (getchar() != '\n')
; // 清理输入缓冲区
continue; // 重新显示菜单
}
if (minusMoney > initBalance)
{
puts("余额不足…"); // 如果余额不足,提示用户并中断支出操作
break;
}
else
{
// 修改账户余额
initBalance -= minusMoney;
printf("%s", "本次支出说明:");
char minusDetail[100];
if (scanf("%99s", minusDetail) != 1) // 检查 scanf 是否成功读取一个字符串
{
puts("无效的输入,请输入一个有效的说明!");
while (getchar() != '\n')
; // 清理输入缓冲区
continue; // 重新显示菜单
}
char minusDetails[300]; // 临时存储用户输入的信息
// 将用户输入的信息拼接成一个字符串,并追加到收支记录中
sprintf(minusDetails, "支出\t-%lf\t\t%lf\t\t%s\n", minusMoney, initBalance, minusDetail);
strcat(details, minusDetails);
// 标记已有记录
hasRecord = 1;
puts("登记支出完成");
}
break;
case 4:
// 退出功能
puts("你确定要退出吗(y/n):");
char isExit;
while (1)
{
// 读取用户输入,确认是否退出
if (scanf(" %c", &isExit) != 1) // 检查 scanf 是否成功读取一个字符
{
puts("无效的输入,请输入 y 或 n!");
while (getchar() != '\n')
; // 清理输入缓冲区
continue; // 重新读取输入
}
if (isExit == 'y' || isExit == 'n')
{
break; // 如果输入有效,结束循环
}
else
{
puts("你输入有误,请从新输入(y/n):"); // 提示用户重新输入
}
}
if (isExit == 'y')
{
// 设置循环标志为 0,准备退出程序
loop = 0;
puts("欢迎再次来到家庭记账软件^_^");
}
break;
default:
// 如果用户输入了无效的选项,则不执行任何操作
puts("无效的选择,请重新选择!");
break;
}
} while (loop); // 当 loop 为 1 时,继续循环
// 退出程序时的提示信息
puts("你退出了家庭财务软件~");
// 程序正常结束
return 0;
}
5 代码升级
5.1 抽取结构体
#include <stdio.h>
#include <string.h> // 包含字符串处理函数的头文件
// 声明一个结构体,用于存储家庭账户信息
typedef struct FamilyAccount
{
// 结构体成员声明
int hasRecord; // 是否有记录标志,0 表示没有记录,1 表示有记录
char details[3000]; // 用于存储收支的记录信息
double initBalance; // 用于存储初始账户余额
} familyAccount;
/*
菜单函数,用于显示和处理用户的选择
注意,这里 familyAccount 等价于 struct FamilyAccount
如若没有使用 typedef,请使用 void menu(struct FamilyAccount *fAccount)
*/
void menu(familyAccount *fAccount)
{
int loop = 1; // 循环标志,用于控制程序是否继续运行
// 主循环,直到用户选择退出
do
{
// 打印菜单选项
printf("\n\n---------家庭收支记账软件-----------------");
printf("\n1 收支明细");
printf("\n2 登记收入");
printf("\n3 登记支出");
printf("\n4 退出");
printf("\n选择(1-4):");
int selection; // 存储用户的选择:1:收支明细 2:登记收入 3:登记支出 4:退出
if (scanf("%d", &selection) != 1) // 检查 scanf 是否成功读取一个整数
{
puts("无效的输入,请输入一个数字 (1-4)!");
// 清理输入缓冲区
while (getchar() != '\n')
;
continue; // 重新显示菜单
}
// 根据用户的选择执行相应的操作
switch (selection)
{
case 1:
// 显示收支明细
// if (!(*fAccount).hasRecord)// 写法 1
if (!fAccount->hasRecord) // 写法 2
{
puts("当前没有收支明细…来一笔吧!"); // 如果没有记录,提示用户
}
else
{
puts(fAccount->details); // 显示收支记录
}
break;
case 2:
// 登记收入的功能
printf("%s", "本次收入金额:");
double addMoney;
if (scanf("%lf", &addMoney) != 1) // 检查 scanf 是否成功读取一个浮点数
{
puts("无效的输入,请输入一个数字!");
while (getchar() != '\n')
; // 清理输入缓冲区
continue; // 重新显示菜单
}
printf("%s", "本次收入说明:");
char addDetail[100];
if (scanf("%99s", addDetail) != 1) // 检查 scanf 是否成功读取一个字符串
{
puts("无效的输入,请输入一个有效的说明!");
while (getchar() != '\n')
; // 清理输入缓冲区
continue; // 重新显示菜单
}
// 修改账户余额
fAccount->initBalance += addMoney;
char addDetails[300]; // 临时存储用户输入的信息
// 将用户输入的信息拼接成一个字符串,并追加到收支记录中
sprintf(addDetails, "收入\t+%lf\t\t%lf\t\t%s\n", addMoney, fAccount->initBalance, addDetail);
strcat(fAccount->details, addDetails);
// 标记已有记录
fAccount->hasRecord = 1;
puts("登记收入完成");
break;
case 3:
// 登记支出的功能
printf("%s", "本次支出金额:");
double minusMoney;
if (scanf("%lf", &minusMoney) != 1) // 检查 scanf 是否成功读取一个浮点数
{
puts("无效的输入,请输入一个数字!");
while (getchar() != '\n')
; // 清理输入缓冲区
continue; // 重新显示菜单
}
if (minusMoney > fAccount->initBalance)
{
puts("余额不足…"); // 如果余额不足,提示用户并中断支出操作
break;
}
else
{
// 修改账户余额
fAccount->initBalance -= minusMoney;
printf("%s", "本次支出说明:");
char minusDetail[100];
if (scanf("%99s", minusDetail) != 1) // 检查 scanf 是否成功读取一个字符串
{
puts("无效的输入,请输入一个有效的说明!");
while (getchar() != '\n')
; // 清理输入缓冲区
continue; // 重新显示菜单
}
char minusDetails[300]; // 临时存储用户输入的信息
// 将用户输入的信息拼接成一个字符串,并追加到收支记录中
sprintf(minusDetails, "支出\t-%lf\t\t%lf\t\t%s\n", minusMoney, fAccount->initBalance, minusDetail);
strcat(fAccount->details, minusDetails);
// 标记已有记录
fAccount->hasRecord = 1;
puts("登记支出完成");
}
break;
case 4:
// 退出功能
puts("你确定要退出吗(y/n):");
char isExit;
while (1)
{
// 读取用户输入,确认是否退出
if (scanf(" %c", &isExit) != 1) // 检查 scanf 是否成功读取一个字符
{
puts("无效的输入,请输入 y 或 n!");
while (getchar() != '\n')
; // 清理输入缓冲区
continue; // 重新读取输入
}
if (isExit == 'y' || isExit == 'n')
{
break; // 如果输入有效,结束循环
}
else
{
puts("你输入有误,请从新输入(y/n):"); // 提示用户重新输入
}
}
if (isExit == 'y')
{
// 设置循环标志为 0,准备退出程序
loop = 0;
puts("欢迎再次来到家庭记账软件^_^");
}
break;
default:
// 如果用户输入了无效的选项,则不执行任何操作
puts("无效的选择,请重新选择!");
break;
}
} while (loop); // 当 loop 为 1 时,继续循环
// 退出程序时的提示信息
puts("你退出了家庭财务软件~");
}
int main()
{
// 声明结构体变量,并为结构体成员赋值
// familyAccount fAccount;
// fAccount.hasRecord = 0;
// 初始化收支记录信息,包含标题
// 注意:字符串如果太长,可以使用双引号进行换行隔开,双引号之间没有任何字符
// fAccount.details = "-----------------当前收支明细记录-----------------\n"
// "收支\t收支金额\t\t账户金额\t\t说明\n";
// 可以使用 strcpy
// strcpoy(fAccount.details, "-----------------当前收支明细记录-----------------\n收支\t收支金额\t\t账户金额\t\t说明\n");
// fAccount.initBalance = 1000;
// 也可以使用大括号一次性赋值,但必须在声明结构体时
familyAccount fAccount = {0,
"-----------------当前收支明细记录-----------------\n收支\t收支金额\t\t账户金额\t\t说明\n",
1000};
// 显示菜单界面
menu(&fAccount);
// 程序正常结束
return 0;
}
5.2 使用函数封装相应功能
#include <stdio.h>
#include <string.h>
// 定义家庭账户结构体
// 使用 typedef 定义别名,在后续操作中可以使用别名 FamilyAccount 代替 struct FamilyAccount,以此简化操作
typedef struct FamilyAccount
{
int hasRecord; // 是否有记录标志,0 表示没有记录,1 表示有记录
char details[3000]; // 用于存储收支的记录信息
double balance; // 用于存储账户余额
} FamilyAccount;
// 全局变量,循环标志,用于控制主循环的继续或退出
int loop = 1;
// 声明函数原型
void menu(FamilyAccount *fAccount); // 菜单函数
void showDetails(FamilyAccount *fAccount); // 显示收支明细
void addIncome(FamilyAccount *fAccount); // 登记收入
void addExpense(FamilyAccount *fAccount); // 登记支出
void confirmExit(); // 确认退出
int main()
{
// 定义结构体变量,并使用 {} 一次性为结构体成员赋值
// 使用 {} 赋值 必须在结构体变量定义时才行
FamilyAccount fAccount = {0, "-----------------当前收支明细记录-----------------\n收支\t收支金额\t\t账户金额\t\t说明\n", 1000};
// 传递结构体变量的地址(即结构体指针),方便后续对结构体成员的数据进行操作
menu(&fAccount);
return 0;
}
// 显示收支明细
void showDetails(FamilyAccount *fAccount)
{
// 检查是否有记录
if (!fAccount->hasRecord)
{
puts("当前没有收支明细…来一笔吧!");
}
else
{
// 输出收支记录信息
puts(fAccount->details);
}
}
// 登记收入
void addIncome(FamilyAccount *fAccount)
{
printf("本次收入金额:");
double addMoney;
// 读取用户输入的收入金额
if (scanf("%lf", &addMoney) != 1)
{
puts("无效的输入,请输入一个数字!");
// 清理输入缓冲区
while (getchar() != '\n')
;
return; // 退出函数
}
printf("本次收入说明:");
char addDetail[100];
// 读取用户输入的收入说明
if (scanf("%99s", addDetail) != 1)
{
puts("无效的输入,请输入一个有效的说明!");
// 清理输入缓冲区
while (getchar() != '\n')
;
return; // 退出函数
}
// 更新账户余额
fAccount->balance += addMoney;
// 创建一个临时字符串,用于存储本次收入的详细信息
char addDetails[300];
sprintf(addDetails, "收入\t+%lf\t\t%lf\t\t%s\n", addMoney, fAccount->balance, addDetail);
// 将本次收入的详细信息追加到记录中
strcat(fAccount->details, addDetails);
// 标记已有记录
fAccount->hasRecord = 1;
// 提示用户收入登记完成
puts("登记收入完成");
}
// 登记支出
void addExpense(FamilyAccount *fAccount)
{
printf("本次支出金额:");
double minusMoney;
// 读取用户输入的支出金额
if (scanf("%lf", &minusMoney) != 1)
{
puts("无效的输入,请输入一个数字!");
// 清理输入缓冲区
while (getchar() != '\n')
;
return; // 退出函数
}
// 检查余额是否足够
if (minusMoney > fAccount->balance)
{
puts("余额不足…");
return; // 退出函数
}
// 更新账户余额
fAccount->balance -= minusMoney;
printf("本次支出说明:");
char minusDetail[100];
// 读取用户输入的支出说明
if (scanf("%99s", minusDetail) != 1)
{
puts("无效的输入,请输入一个有效的说明!");
// 清理输入缓冲区
while (getchar() != '\n')
;
return; // 退出函数
}
// 创建一个临时字符串,用于存储本次支出的详细信息
char minusDetails[300];
sprintf(minusDetails, "支出\t-%lf\t\t%lf\t\t%s\n", minusMoney, fAccount->balance, minusDetail);
// 将本次支出的详细信息追加到记录中
strcat(fAccount->details, minusDetails);
// 标记已有记录
fAccount->hasRecord = 1;
// 提示用户支出登记完成
puts("登记支出完成");
}
// 确认退出
void confirmExit()
{
puts("你确定要退出吗(y/n):");
char isExit;
// 读取用户输入的确认信息
while (1)
{
if (scanf(" %c", &isExit) != 1) // %c 前面有空格,用于跳过前面的空白字符
{
puts("无效的输入,请输入 y 或 n!");
// 清理输入缓冲区
while (getchar() != '\n')
;
continue; // 跳过本次循环
}
// 检查用户输入是否有效
if (isExit == 'y' || isExit == 'n')
{
break; // 结束循环
}
else
{
puts("你输入有误,请从新输入(y/n):");
}
}
// 如果用户选择退出,设置循环标志为 0
if (isExit == 'y')
{
loop = 0;
puts("欢迎再次来到家庭记账软件^_^");
}
}
// 菜单函数
void menu(FamilyAccount *fAccount)
{
do
{
// 打印菜单选项
printf("\n\n---------家庭收支记账软件-----------------\n");
printf("1 收支明细\n");
printf("2 登记收入\n");
printf("3 登记支出\n");
printf("4 退出\n");
printf("选择(1-4):");
int selection;
// 读取用户选择
if (scanf("%d", &selection) != 1)
{
puts("无效的输入,请输入一个数字 (1-4)!");
// 清理输入缓冲区
while (getchar() != '\n')
;
continue;
}
// 根据用户选择执行相应操作
switch (selection)
{
case 1:
showDetails(fAccount); // fAccount 就是结构体指针类型,不需要在前面再加上取地址符 &
break;
case 2:
addIncome(fAccount);
break;
case 3:
addExpense(fAccount);
break;
case 4:
confirmExit();
break;
default:
puts("无效的选择,请重新选择!");
break;
}
} while (loop); // 当 loop 为 1 时,继续循环
// 提示用户已退出软件
puts("你退出了家庭财务软件~");
}
至此,该项目的代码已经进行了充分的优化。如果您有任何更好的优化建议或改进思路,欢迎在评论区踊跃留言!