自学内容网 自学内容网

47 C 语言实战项目——家庭收支记账软件

目录

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("你退出了家庭财务软件~");
}

        至此,该项目的代码已经进行了充分的优化。如果您有任何更好的优化建议或改进思路,欢迎在评论区踊跃留言!


原文地址:https://blog.csdn.net/qq_53139964/article/details/142853247

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!