零、上篇回顾下篇起点上篇我们建立了银行系统的骨架结构体、动态数组、扩容、伪删除、验证。但一个银行系统如果只能“创建账户”和“冻结账户”那它连玩具都算不上。下篇要解决的问题存款、取款、查询余额 —— 如何优雅地复用验证逻辑菜单与流程控制 —— 为什么do-while是菜单的最佳选择输入校验 —— 用户输入-100元怎么办输入abc呢账号唯一性 ——100000 count为什么是错的文件持久化 —— 程序关了数据还在吗资源释放 —— 为什么free后还要置NULL这些问题每一个都是 C 语言面试的高频考点。一、存款、取款、查询 —— 验证的复用上篇我们写了verifyAccount它返回账户下标。存款、取款、查询的本质是验证账户拿到下标操作余额存款实现void deposit(BankSystem* bank) { int idx verifyAccount(bank); if (idx -1) return; // 验证失败直接返回 double money; printf(请输入存款金额); scanf(%lf, money); // 输入校验金额必须为正 if (money 0) { printf(存款金额必须大于0\n); return; } bank-accounts[idx].balance money; printf(存款成功当前余额%.2f\n, bank-accounts[idx].balance); }取款实现void withdraw(BankSystem* bank) { int idx verifyAccount(bank); if (idx -1) return; double money; printf(请输入取款金额); scanf(%lf, money); // 双重校验正数 余额充足 if (money 0) { printf(取款金额必须大于0\n); return; } if (money bank-accounts[idx].balance) { printf(余额不足当前余额%.2f\n, bank-accounts[idx].balance); return; } bank-accounts[idx].balance - money; printf(取款成功当前余额%.2f\n, bank-accounts[idx].balance); }查询余额void queryBalance(BankSystem* bank) { int idx verifyAccount(bank); if (idx -1) return; printf(账号%lld 当前余额%.2f\n, bank-accounts[idx].accountNumber, bank-accounts[idx].balance); }设计原则验证逻辑只写一次到处复用每个业务的校验金额正负、余额充足各自负责二、菜单系统 ——do-while的经典用法void menu(BankSystem* bank) { int choice; do { printf(\n 银行管理系统 \n); printf(1. 创建账户\n); printf(2. 存款\n); printf(3. 取款\n); printf(4. 查询余额\n); printf(5. 冻结账户\n); printf(6. 退出系统\n); printf(请选择); scanf(%d, choice); switch (choice) { case 1: createAccount(bank); break; case 2: deposit(bank); break; case 3: withdraw(bank); break; case 4: queryBalance(bank); break; case 5: freezeAccount(bank); break; case 6: printf(感谢使用\n); break; default: printf(无效选项请重新输入\n); } } while (choice ! 6); }为什么用do-while而不是while菜单至少显示一次先显示再输入再判断是否退出do-while天然适合这种“至少执行一次”的场景switch的注意事项每个case末尾要break否则会“穿透”故意穿透有时用于合并逻辑如 case 1 和 case 2 共用代码三、输入校验 —— 用户不按套路出牌怎么办问题 1负数金额我们已经用if (money 0)拦截了。问题 2用户输入abc而不是数字scanf(%lf, money)会返回 0表示没读到有效数字但money的值不确定。更健壮的写法double getPositiveAmount(const char* prompt) { double money; int ret; while (1) { printf(%s, prompt); ret scanf(%lf, money); // 清理输入缓冲区关键 while (getchar() ! \n); if (ret ! 1 || money 0) { printf(输入无效请输入正数\n); } else { return money; } } }为什么需要while (getchar() ! \n)用户输入abc后abc还留在输入缓冲区下次scanf会再次读到abc形成死循环清空缓冲区是必须的四、账号唯一性 ——100000 count为什么是错的上篇我们用newAcc-accountNumber 100000 bank-count;问题删除账户后count不变但账号不会回收这倒也没大问题账号不用回收真正的问题是多线程/分布式下不唯一更好的方案时间戳#include time.h long long generateAccountNumber() { // 秒级时间戳未来可加随机数 return (long long)time(NULL); }注意time(NULL)返回秒数约1.7e9仍在long long范围内。再进一步时间戳 自增序列static long long _seq 0; long long generateAccountNumber() { return ((long long)time(NULL) * 1000) (_seq % 1000); }这样同一秒内也能生成不同账号。五、文件持久化 —— 数据不能丢银行系统如果每次重启数据都清空那就是个笑话。保存数据到文件void saveToFile(BankSystem* bank, const char* filename) { FILE* fp fopen(filename, wb); if (fp NULL) { printf(保存失败\n); return; } // 先写元数据账户数量 fwrite(bank-count, sizeof(int), 1, fp); // 再写所有账户 fwrite(bank-accounts, sizeof(Account), bank-count, fp); fclose(fp); printf(数据已保存\n); }从文件加载数据void loadFromFile(BankSystem* bank, const char* filename) { FILE* fp fopen(filename, rb); if (fp NULL) { printf(未找到存档将创建新系统\n); return; } // 先读账户数量 int count; fread(count, sizeof(int), 1, fp); // 确保容量足够 if (count bank-capacity) { // 需要扩容 Account* newMem (Account*)realloc(bank-accounts, count * sizeof(Account)); if (newMem NULL) { printf(加载失败\n); fclose(fp); return; } bank-accounts newMem; bank-capacity count; } // 读账户数据 fread(bank-accounts, sizeof(Account), count, fp); bank-count count; fclose(fp); printf(加载成功共 %d 个账户\n, bank-count); }wb/rb是二进制模式直接写内存字节速度快但不同平台间不兼容long long大小、字节序跨平台需要用文本格式JSON、CSV六、资源释放 —— 为什么free后还要置NULLvoid freeBankSystem(BankSystem* bank) { if (bank-accounts ! NULL) { free(bank-accounts); bank-accounts NULL; // 关键 } bank-count 0; bank-capacity 0; }如果不置NULLfree(bank-accounts); // 这里 bank-accounts 仍然指向一块已释放的内存野指针 // 如果后续代码不小心再 free 一次 → 双重释放 → 崩溃规则free 之后指针立即置 NULL。这不是多此一举这是防御性编程。七、完整的下篇代码#define _CRT_SECURE_NO_WARNINGS #include stdio.h #include stdlib.h #include string.h #include assert.h #include stdbool.h #include time.h #define MAX_ACCOUNT 10 #define PWD_LEN 7 #define DATA_FILE bank.dat typedef struct Account { long long accountNumber; char password[PWD_LEN]; double balance; int isActive; } Account; typedef struct BankSystem { Account* accounts; int count; int capacity; } BankSystem; // 辅助函数 void printAccount(const Account* p) { printf(账号%lld | 余额%.2f | 状态%s\n, p-accountNumber, p-balance, p-isActive ? 活跃 : 冻结); } // 生成唯一账号时间戳 序列 long long generateAccountNumber() { static long long seq 0; return (long long)time(NULL) * 1000 (seq % 1000); } // 安全输入正数金额 double getPositiveAmount(const char* prompt) { double money; int ret; while (1) { printf(%s, prompt); ret scanf(%lf, money); while (getchar() ! \n); // 清空缓冲区 if (ret 1 money 0) { return money; } printf(输入无效请输入正数\n); } } // 内存管理 void initBankSystem(BankSystem* bank) { assert(bank ! NULL); bank-capacity MAX_ACCOUNT; bank-count 0; bank-accounts (Account*)malloc(bank-capacity * sizeof(Account)); if (bank-accounts NULL) { printf(内存分配失败\n); exit(1); } } bool expandBankSystem(BankSystem* bank) { int newCap bank-capacity * 2; Account* newMem (Account*)realloc(bank-accounts, newCap * sizeof(Account)); if (newMem NULL) { printf(扩容失败\n); return false; } bank-accounts newMem; bank-capacity newCap; return true; } void freeBankSystem(BankSystem* bank) { if (bank-accounts) { free(bank-accounts); bank-accounts NULL; } bank-count 0; bank-capacity 0; } // 持久化 void saveToFile(BankSystem* bank) { FILE* fp fopen(DATA_FILE, wb); if (!fp) return; fwrite(bank-count, sizeof(int), 1, fp); fwrite(bank-accounts, sizeof(Account), bank-count, fp); fclose(fp); } void loadFromFile(BankSystem* bank) { FILE* fp fopen(DATA_FILE, rb); if (!fp) return; int count; fread(count, sizeof(int), 1, fp); if (count bank-capacity) { Account* newMem (Account*)realloc(bank-accounts, count * sizeof(Account)); if (!newMem) { fclose(fp); return; } bank-accounts newMem; bank-capacity count; } fread(bank-accounts, sizeof(Account), count, fp); bank-count count; fclose(fp); } // 业务逻辑 int verifyAccount(BankSystem* bank) { long long num; char pwd[PWD_LEN]; printf(请输入账号); scanf(%lld, num); printf(请输入密码); scanf(%s, pwd); for (int i 0; i bank-count; i) { if (bank-accounts[i].accountNumber num strcmp(bank-accounts[i].password, pwd) 0 bank-accounts[i].isActive 1) { return i; } } printf(验证失败\n); return -1; } void createAccount(BankSystem* bank) { if (bank-count bank-capacity) { if (!expandBankSystem(bank)) return; } Account* acc bank-accounts[bank-count]; acc-accountNumber generateAccountNumber(); printf(设置6位数字密码); scanf(%s, acc-password); if (strlen(acc-password) ! 6) { printf(密码必须6位\n); return; } acc-balance 0.0; acc-isActive 1; bank-count; printf(创建成功账号%lld\n, acc-accountNumber); saveToFile(bank); } void deposit(BankSystem* bank) { int idx verifyAccount(bank); if (idx -1) return; double money getPositiveAmount(存款金额); bank-accounts[idx].balance money; printf(存款成功余额%.2f\n, bank-accounts[idx].balance); saveToFile(bank); } void withdraw(BankSystem* bank) { int idx verifyAccount(bank); if (idx -1) return; double money getPositiveAmount(取款金额); if (money bank-accounts[idx].balance) { printf(余额不足\n); return; } bank-accounts[idx].balance - money; printf(取款成功余额%.2f\n, bank-accounts[idx].balance); saveToFile(bank); } void queryBalance(BankSystem* bank) { int idx verifyAccount(bank); if (idx -1) return; printf(余额%.2f\n, bank-accounts[idx].balance); } void freezeAccount(BankSystem* bank) { int idx verifyAccount(bank); if (idx -1) return; bank-accounts[idx].isActive 0; printf(账户已冻结\n); saveToFile(bank); } // 菜单 int main() { BankSystem bank; initBankSystem(bank); loadFromFile(bank); int choice; do { printf(\n1.创建 2.存款 3.取款 4.查询 5.冻结 6.退出\n); printf(请选择); scanf(%d, choice); switch (choice) { case 1: createAccount(bank); break; case 2: deposit(bank); break; case 3: withdraw(bank); break; case 4: queryBalance(bank); break; case 5: freezeAccount(bank); break; case 6: printf(再见\n); break; default: printf(无效\n); } } while (choice ! 6); freeBankSystem(bank); return 0; }下篇总结知识点为什么重要复用验证逻辑DRY 原则do-while菜单至少执行一次输入缓冲区清空防止死循环时间戳生成账号解决唯一性问题二进制文件读写数据持久化free后置NULL防御野指针上下两篇合起来就是一个完整的银行管理系统。