当前位置: 首页 > news >正文

CP1-1-用户管理MyUser

C/C++道经 - 项目 - MyUser

文章目录

  • S01. 用户管理项目
    • E01. 准备基础代码
      • 1. 展示提示菜单
      • 2. 获取用户输入
    • E02. 准备结构体数据
      • 1. 日期结构体
      • 2. 用户结构体
      • 3. 用户列表结构体
    • E03. 开发业务代码
      • 1. 添加一名用户
      • 2. 打印全部用户
      • 3. 统计用户数据
      • 4. 查询用户记录
      • 5. 修改用户记录
      • 6. 删除用户记录
      • 7. 导出用户记录
      • 8. 导入用户记录

S01. 用户管理项目

心法:MyUser 是基于 C 语言开发的轻量级控制台用户管理系统,旨在通过模块化设计与基础数据结构(单链表)实现用户信息的全生命周期管理。项目遵循 C 语言结构化编程思想,将功能拆解为 “数据存储 - 交互逻辑 - 业务处理” 三层架构,同时兼顾实用性与学习价值,适合 C 语言初学者理解结构体、链表、文件操作、函数封装等核心知识点的工程化应用。

项目定位:MyUser 以 “简洁高效的本地用户数据管理” 为核心目标,无需依赖数据库或外部框架,仅通过 C 标准库实现用户信息的增删改查、统计分析及数据持久化,可作为 C 语言进阶学习的实战案例,也可作为小型场景(如本地班级信息管理、简易员工档案记录)的轻量工具。

技术架构:MyUser 采用模块化拆分设计,文件结构清晰,各模块职责单一,便于维护与扩展,具体如下:

目录 / 文件功能职责
main.c程序入口,负责控制台编码设置、菜单展示、用户输入接收与业务逻辑分发
head/Date.h定义日期结构体(年 / 月 / 日),为用户 “生日” 字段提供数据类型支持
head/User.h定义用户结构体(编号 / 姓名 / 年龄 / 身高 / 生日),封装用户信息打印函数
head/UserList.h基于单链表实现用户列表管理,定义链表节点 / 链表结构体,封装链表打印函数
head/Tool.h输入工具类,封装字符串转 int/long/long long/float 的通用函数,处理输入异常
service/UserService.h业务逻辑核心,实现用户增删改查、统计、导入导出等所有核心功能

武技:参考 CB1-1-新手村(一) 笔记创建 C 练习项目 MyUser。

  1. 修改外部命令窗口,并初始化主函数如下:
// main.c
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 输出语句printf("\n你好MyUser\n\n");// 暂停控制台system("pause");return 0;
}

效果图

在这里插入图片描述

E01. 准备基础代码

1. 展示提示菜单

心法:程序启动后自动打印标准化菜单,清晰列出 9 项功能(1 - 添加 / 2 - 打印 / 3 - 统计 / 4 - 查询 / 5 - 修改 / 6 - 删除 / 7 - 导出 / 8 - 导入 / 9 - 退出),后续用户可以通过输入数字选择对应的功能。

效果图

在这里插入图片描述

武技:开发展示提示菜单的功能。

// main.c
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>// 展示提示菜单
void showMenu() {printf("\n");printf(" 欢迎来到《MyUser用户管理系统》\n");printf(" @Author 周航宇\n");printf("\n");printf(" ================\n");printf(" =  请输入命令  =\n");printf(" ================\n");printf("\n");printf("【1】添加一名用户\n");printf("【2】打印全部用户\n");printf("【3】统计用户数据(包括用户总数和一些平均值相关信息)\n");printf("【4】查询用户记录(按编号查找)\n");printf("【5】修改用户记录(按编号修改)\n");printf("【6】删除用户记录(按编号删除)\n");printf("【7】导出用户数据\n");printf("【8】导入用户数据\n");printf("【9】退出系统\n");printf("\n");
}int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 展示提示菜单showMenu();// 暂停控制台system("pause");return 0;
}

2. 获取用户输入

心法:通过 Tool.h 封装的通用输入函数(cin_d,cin_ll,cin_f,cin_s 等)统一处理用户输入,自动转换数据类型(如字符串转数字),并检测非数字字符(返回-1)等异常输入,提示错误信息。

退出系统:用户输入 “9” 时,终止 main 函数中的循环,程序正常退出。

效果图

在这里插入图片描述

武技:开发获取用户输入的功能。

  1. 封装一个工具类,用于从控制台接收值:
// head/Tool.h
// Created by 周航宇 on 2025/2/27.
//
#include <string.h>// 接收一个字符串,转为 int 类型并返回
void cin_d(char *msg, int *result) {// 输出提示信息printf("%s", msg);// 接收一个字符串fflush(stdin);// 接收一个字符串char input[100];gets(input);// 将字符串转为 10 进制 int 类型char *endPtr;*result = strtol(input, &endPtr, 10);// 若文件末尾不是 \0,则表示转换过程中遇到非数字字符if (*endPtr != '\0') {printf("转换过程中遇到非数字字符: %c\n", *endPtr);}
}// 接收一个字符串,转为 long 类型并返回
void cin_l(char *msg, long *result) {cin_d(msg, (int *) result);
}// 接收一个字符串,转为 long long 类型并返回
void cin_ll(char *msg, long long *result) {// 输出提示信息printf("%s", msg);// 接收一个字符串fflush(stdin);// 接收一个字符串char input[100];gets(input);// 将字符串转为 10 进制 long long 类型char *endPtr;*result = strtoll(input, &endPtr, 10);// 若文件末尾不是 \0,则表示转换过程中遇到非数字字符if (*endPtr != '\0') {printf("转换过程中遇到非数字字符: %c\n", *endPtr);}
}// 接收一个字符串,转为 float 类型并返回
void cin_f(char *msg, float *result) {// 输出提示信息printf("%s", msg);// 接收一个字符串fflush(stdin);// 接收一个字符串char input[100];gets(input);// 将字符串转为 10 进制 float 类型char *endPtr;*result = strtof(input, &endPtr);// 若文件末尾不是 \0,则表示转换过程中遇到非数字字符if (*endPtr != '\0') {printf("转换过程中遇到非数字字符: %c\n", *endPtr);}
}// 接收一个字符串并返回
void cin_s(char *msg, char *result) {// 输出提示信息printf("%s", msg);// 接收一个字符串fflush(stdin);// 接收一个字符串char input[100];gets(input);// 将字符串转为 char 类型strcpy(result, input);
}
  1. 开发枚举,提升菜单指令的可读性:
// main.c
// Created by 周航宇 on 2025/2/26.
//// 用户输入枚举
enum MenuCode {INSERT = 1, // 插入SELECT_ALL = 2, // 打印STATISTICS = 3, // 统计SELECT = 4, // 查询UPDATE = 5, // 修改DEL = 6, // 删除WRITE = 7, // 导出READ = 8, // 导入EXIT = 9 // 退出
};
  1. 在主页面中接收控制台变量,并处理用户的输入:
// main.c
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 仅在用户输入 9 时退出循环while (1) {// 展示提示菜单showMenu();// 获取用户输入的命令int inputNum;cin_d("", &inputNum);// 根据用户输入的命令执行对应的操作if (inputNum == INSERT) {printf("\n\t====== 正在添加用户记录.. ======\n\n");printf("\n\t====== 用户记录添加完毕! ======\n\n");} else if (inputNum == SELECT_ALL) {printf("\n\t====== 正在打印用户记录.. ======\n\n");printf("\n\t====== 用户记录打印完毕!===\n\n");} else if (inputNum == STATISTICS) {printf("\n\t====== 正在统计用户数据.. ======\n\n");printf("\n\t====== 用户数据统计完毕! ======\n\n");} else if (inputNum == SELECT) {printf("\n\t====== 正在查询用户记录.. ======\n\n");printf("\n\t====== 用户记录查询完毕! ======\n\n");} else if (inputNum == UPDATE) {printf("\n\t====== 正在修改用户记录.. ======\n\n");printf("\n\t====== 用户记录修改完毕! ======\n\n");} else if (inputNum == DEL) {printf("\n\t====== 正在删除用户记录.. ======\n\n");printf("\n\t====== 用户记录删除完毕! ======\n\n");} else if (inputNum == WRITE) {printf("\n\t====== 正在导出用户数据.. ======\n\n");printf("\n\t====== 用户数据导出完毕! ======\n\n");} else if (inputNum == READ) {printf("\n\t====== 正在导入用户数据.. ======\n\n");printf("\n\t====== 用户数据导入完毕! ======\n\n");} else if (inputNum == EXIT) {break;} else {printf("\n\t输入有误,请重新输入!\n\n");}// 暂停控制台system("pause");// 清空控制台system("cls");}return 0;
}

E02. 准备结构体数据

1. 日期结构体

心法:在 C 语言里并没有内置的日期类型,但你可以通过几种方式来定义日期类型的变量,但可以定义一个结构体来表示日期,结构体里包含年、月、日这几个成员。

武技:开发日期结构体

  1. 开发日期结构体:
// head/Date.h
// Created by 周航宇 on 2025/2/26.
//// 定义日期结构体
typedef struct Date {int year;  // 年int month; // 月int day;   // 日
} Date;
  1. 测试日期结构体:
// main.h
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>
#include "head/Date.h"int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 测试 Date 结构体Date date = {2023, 1, 1};printf("%d-%d-%d\n", date.year, date.month, date.day);// 略return 0;
}

2. 用户结构体

心法:用户结构体用来装载一个用户的信息(包括编号,姓名,年龄,生日等)。

注意:用户结构体包含一个日期属性(生日),需要引入并使用 Date.h,所在 main.c 中测试时,不要再单独引入 Date.h 文件,以免爆发重复引入相关的错误。

武技:开发用户结构体。

  1. 开发用户结构体:
// head/User.h
// Created by 周航宇 on 2025/2/26.
//
#include "Date.h"// 定义用户结构体,并起别名为 User
typedef struct User {long long id; // 编号char name[20]; // 姓名int age; // 年龄float height; // 身高Date birthday; // 生日
} User;void printUser(User *user){printf("编号:%lld\n", user->id);printf("姓名:%s\n", user->name);printf("年龄:%d\n", user->age);printf("身高:%.2f\n", user->height);printf("生日:%d-%d-%d\n",user->birthday.year,user->birthday.month,user->birthday.day);
}
  1. 测试用户结构体:
// main.h
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>
#include "head/User.h"int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 测试 User 结构体User user = {.id = 1,.name = "张三",.age = 20,.height = 180.5F,.birthday = {.year = 2025,.month = 2,.day = 22}};printf("用户编号:%lld\n", user.id);printf("用户姓名:%s\n", user.name);printf("用户年龄:%d\n", user.age);printf("用户身高:%.1f\n", user.height);printf("用户生日:%d-%d-%d\n",user.birthday.year,user.birthday.month,user.birthday.day);// 略return 0;
}

3. 用户列表结构体

心法:用户列表结构体用来装载全部用户的信息,底层使用单链表数据结构实现。

注意:用户列表结构体包含用户结构体,用户结构体中包含一个日期属性(生日),需要引入并使用 Date.h,所在 main.c 中测试时,不要再单独引入 User.h 和 Date.h 这两个文件,以免爆发重复引入相关的错误。

武技:开发用户列表结构体。

  1. 开发用户列表结构体:
// head/UserList.h
// Created by 周航宇 on 2025/2/26.
//
#include "User.h"// 定义链表节点结构体
typedef struct Node {User user; // 用户数据struct Node *next; // 后继指针
} Node;// 定义链表结构体
typedef struct UserList {Node *head; // 头节点int size; // 链表长度
} UserList;void printUserList(UserList *userList) {// 遍历链表,输出用户信息Node *current = userList->head;if (current == NULL) {printf("当前没有用户信息!\n");return;}printf("编号\t姓名\t年龄\t身高\t生日\n");while (current != NULL) {printf("%lld\t%s\t%d\t%.2f\t%d-%d-%d\n",current->user.id,current->user.name,current->user.age,current->user.height,current->user.birthday.year,current->user.birthday.month,current->user.birthday.day);// 移动到下一个节点current = current->next;}
}
  1. 测试用户列表结构体:使用 memset() 函数需要引入 string.h 头文件:
// main.c
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "head/UserList.h"int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 测试 UserList 结构体UserList userList;// 初始化链表:将链表的头节点指向的内存块清零,确保了链表在使用前处于一个已知的、安全的状态。memset(&userList, 0, sizeof(userList));// 使用 malloc 分配一个 Node 长度的内存Node *newNode = malloc(sizeof(Node));newNode->next = NULL;newNode->user.id = 1;newNode->user.age = 20L;newNode->user.height = 180.5F;strcpy(newNode->user.name, "张三");newNode->user.birthday.year = 2000L;newNode->user.birthday.month = 1L;newNode->user.birthday.day = 1L;// 将新节点插入链表头部newNode->next = userList.head;userList.head = newNode;userList.size++;// 输出链表头的数据printf("编号: %lld\n", userList.head->user.id);printf("姓名: %s\n", userList.head->user.name);printf("年龄: %ld\n", userList.head->user.age);printf("身高: %.1f\n", userList.head->user.height);printf("生日: %ld-%ld-%ld\n",userList.head->user.birthday.year,userList.head->user.birthday.month,userList.head->user.birthday.day);// 释放内存free(newNode);// 略return 0;
}

E03. 开发业务代码

心法:MyUser 提供 8 大核心功能(添加,打印,统计,查询,修改,删除,导出,导入),覆盖用户数据管理的全流程,所有操作均通过控制台交互完成,操作逻辑直观易懂。

注意:UserService.h 中需要引入了 UserList.h(关联引入 User.h 和 Date.h),所在 main.c 中测试时,不要再引入 UserList,User.h 和 Date.h 这三个文件,以免爆发重复引入相关的错误。

武技:创建 UserService.h 头文件

  1. 创建 UserService.h 头文件:
// service/UserService.h
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>  
#include <stdlib.h>  
#include "../head/UserList.h"  
#include "../head/Tool.h"
  1. 初始化 main.c 文件:
// main.c
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>
#include "service/UserService.h"int main(void) {// 略
}

1. 添加一名用户

心法:通过单链表头部插入方式添加新用户,避免遍历链表提升效率。

操作流程

  1. 程序提示用户依次输入 “用户编号(long long)、姓名(字符串)、年龄(int)、身高(float)、生日(年 / 月 / 日)”。
  2. 动态分配链表节点内存(malloc),将输入信息赋值到节点的用户结构体中。
  3. 将新节点插入链表头部,更新链表长度,并即时打印新添加的用户信息,确认插入成功。
  4. 异常处理:若内存分配失败(malloc 返回 NULL),提示 “内存分配失败” 并终止操作。

效果图

在这里插入图片描述

武技:开发添加一名用户的功能。

// service/UserService
// Created by 周航宇 on 2025/2/26.
//// 添加一名用户
void insert(UserList *userList) {// 使用 malloc 分配一个 Node 长度的内存Node *newNode = malloc(sizeof(Node));if (newNode == NULL) {printf("内存分配失败!\n");return;}// 初始化新节点newNode->next = NULL;// 输入用户信息cin_ll("输入编号:", &newNode->user.id);cin_s("输入姓名:", newNode->user.name);cin_d("输入年龄:", &newNode->user.age);cin_f("输入身高:", &newNode->user.height);cin_d("输入生日(年):", &newNode->user.birthday.year);cin_d("输入生日(月):", &newNode->user.birthday.month);cin_d("输入生日(日):", &newNode->user.birthday.day);// 将新节点插入链表头部newNode->next = userList->head;userList->head = newNode;// 输出刚刚插入的用户信息printf("\n用户信息已插入:\n\n");printUser(&userList->head->user);
}
// main.c
// Created by 周航宇 on 2025/2/26.
//int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 创建用户链表UserList userList = {0};while (1) {// 略if (inputNum == INSERT) {printf("\n\t====== 正在添加用户记录.. ======\n\n");insert(&userList);printf("\n\t====== 用户记录添加完毕! ======\n\n");} // 略}return 0;
}

2. 打印全部用户

心法:遍历单链表,以 “表格化格式” 输出所有用户信息,便于快速查看整体数据。

输出格式:采用 “编号 \t 姓名 \t 年龄 \t 身高 \t 生日” 的列对齐格式,身高保留 2 位小数,生日以 “年 - 月 - 日” 格式展示。

空数据处理:若链表为空(无用户数据),自动提示 “当前没有用户信息!”,避免遍历空指针异常。

效果图

在这里插入图片描述

武技:开发打印全部用户的功能。

// service/UserService
// Created by 周航宇 on 2025/2/26.
//void selectAll(UserList *userList) {printUserList(userList);
}
// main.c
// Created by 周航宇 on 2025/2/26.
//int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 创建用户链表UserList userList = {0};while (1) {// 略else if (inputNum == SELECT_ALL) {printf("\n\t====== 正在打印用户记录.. ======\n\n");selectAll(&userList);printf("\n\t====== 用户记录打印完毕!===\n\n");} // 略}return 0;
}

3. 统计用户数据

心法:当前版本支持 2 类核心统计指标(用户总数和平均年龄),单次遍历链表完成计数与年龄累加,时间复杂度为 O (n),确保高效性。

统计指标 - 用户总数:遍历链表计数,统计当前系统中存储的用户总数量。

统计指标 - 平均年龄:累加所有用户年龄,计算平均值(自动转换为 float 类型,保留 2 位小数)。

效果图

在这里插入图片描述

武技:开发统计用户数据的功能。

// service/UserService
// Created by 周航宇 on 2025/2/26.
//void statistics(UserList *userList) {// 统计用户数量和平均年龄int count = 0;int totalAge = 0;Node *current = userList->head;while (current != NULL) {count++;totalAge += current->user.age;current = current->next;}float averageAge = (float) totalAge / (float) count;// 输出统计结果printf("\n用户数量:%d\n", count);printf("平均年龄:%.2f\n", averageAge);
}
// main.c
// Created by 周航宇 on 2025/2/26.
//int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 创建用户链表UserList userList = {0};while (1) {// 略else if (inputNum == STATISTICS) {printf("\n\t====== 正在统计用户数据.. ======\n\n");statistics(&userList);printf("\n\t====== 用户数据统计完毕! ======\n\n");}// 略}return 0;
}

4. 查询用户记录

心法:按 “用户编号” 精确查询用户记录。

操作流程

  1. 提示用户输入待查询的用户编号。
  2. 遍历链表,对比节点中用户的编号与输入编号:
    • 找到匹配用户时,调用 printUser 函数打印该用户的完整信息。
    • 未找到匹配用户时,提示 “未找到该编号的用户!”。

效果图

在这里插入图片描述

武技:开发按用户编号查询用户记录的功能。

// service/UserService
// Created by 周航宇 on 2025/2/26.
//void select(UserList *userList) {// 输入要查询的用户编号long long id;cin_ll("\n请输入要查询的用户编号:", &id);// 遍历链表,查找用户Node *current = userList->head;while (current != NULL) {if (current->user.id == id) {// 输出用户信息printf("\n用户信息:\n\n");printUser(&current->user);return;}current = current->next;}printf("\n未找到该编号的用户!\n");
}
// main.c
// Created by 周航宇 on 2025/2/26.
//int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 创建用户链表UserList userList = {0};while (1) {// 略else if (inputNum == SELECT) {printf("\n\t====== 正在查询用户记录.. ======\n\n");select(&userList);printf("\n\t====== 用户记录查询完毕! ======\n\n");}// 略}return 0;
}

5. 修改用户记录

心法:按 “用户编号” 定位目标用户,支持修改除 “编号” 外的所有字段(避免编号重复导致查询混乱)。

操作流程

  1. 提示用户输入待修改的用户编号,遍历链表定位目标节点。
  2. 定位成功后,提示用户依次输入 “新姓名、新年龄、新身高、新生日”。
  3. 将新输入的信息覆盖原用户结构体中的对应字段。
  4. 即时打印修改后的用户信息,确认修改成功。
  5. 未找到目标用户时,提示 “未找到该编号的用户!”。

效果图

在这里插入图片描述

武技:开发按用户编号修改用户记录的功能。

// service/UserService
// Created by 周航宇 on 2025/2/26.
//void update(UserList *userList) {// 输入要修改的用户编号long long id;cin_ll("\n请输入要修改的用户编号:", &id);// 遍历链表,查找用户Node *current = userList->head;while (current != NULL) {if (current->user.id == id) {// 输入新的用户信息printf("\n请输入新的用户信息:\n\n");cin_s("> 姓名:", current->user.name);cin_d("> 年龄:", &current->user.age);cin_f("> 身高:", &current->user.height);cin_d("> 生日(年):", &current->user.birthday.year);cin_d("> 生日(月):", &current->user.birthday.month);cin_d("> 生日(日):", &current->user.birthday.day);// 输出修改后的用户信息printf("\n%lld号用户信息已修改:\n\n", id);printUser(&current->user);return;}current = current->next;}printf("\n未找到该编号的用户!\n");
}
// main.c
// Created by 周航宇 on 2025/2/26.
//int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 创建用户链表UserList userList = {0};while (1) {// 略else if (inputNum == UPDATE) {printf("\n\t====== 正在修改用户记录.. ======\n\n");update(&userList);printf("\n\t====== 用户记录修改完毕! ======\n\n");}// 略}return 0;
}

6. 删除用户记录

心法:按 “用户编号” 定位目标节点,支持删除链表头部、中间及尾部节点,删除后释放内存(避免内存泄漏)。

操作流程

  1. 提示用户输入待删除的用户编号,通过 “双指针”(当前节点 currentUser、前驱节点 previousUser)遍历链表:
    • 若删除 “头部节点”:直接将链表头指针指向头部节点的下一个节点。
    • 若删除 “中间 / 尾部节点”:将前驱节点的 next 指向当前节点的下一个节点;
  2. 释放当前节点内存,更新链表长度。
  3. 未找到目标用户时,提示 “未找到该编号的用户!”。

效果图

在这里插入图片描述

武技:开发按用户编号删除用户记录的功能。

// service/UserService
// Created by 周航宇 on 2025/2/26.
//void del(UserList *userList) {long long id;cin_ll("\n请输入要删除的用户编号:", &id);// 当前用户Node *currentUser = userList->head;// 上一个用户Node *previousUser = NULL;// 遍历链表,查找用户while (currentUser != NULL) {// 找到用户if (currentUser->user.id == id) {// 删除的是第一个用户if (previousUser == NULL) {userList->head = currentUser->next;}// 删除的是中间的用户else {previousUser->next = currentUser->next;}// 使用 free 释放内存,并将链表长度减一free(currentUser);userList->size--;return;}// 移动previousUser = currentUser;currentUser = currentUser->next;}printf("\n未找到该编号的用户!\n");
}
// main.c
// Created by 周航宇 on 2025/2/26.
//int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 创建用户链表UserList userList = {0};while (1) {// 略else if (inputNum == DEL) {printf("\n\t====== 正在删除用户记录.. ======\n\n");del(&userList);printf("\n\t====== 用户记录删除完毕! ======\n\n");} // 略}return 0;
}

7. 导出用户记录

心法:使用 fopen 以 “写模式(w)” 打开 Markdown 文件、“二进制写模式(wb)” 打开二进制文件,操作完成后调用 fclose 关闭文件,避免文件句柄泄漏。

导出格式:同时生成两种格式文件,满足不同场景需求:

  • Markdown 文件(user.md):以表格形式存储用户数据,支持用记事本、Markdown 编辑器打开,便于人类阅读。
  • 二进制文件(user.bin):以二进制形式存储用户数据(先存链表长度,再存每个用户的完整结构体),便于后续导入时快速读取,节省存储空间。

效果图

在这里插入图片描述

导出文件的位置如图

在这里插入图片描述

武技:开发导出用户记录的功能。

// service/UserService
// Created by 周航宇 on 2025/2/26.
//void write(UserList *userList) {// 打开文件FILE *fp = fopen("user.md", "w");if (fp == NULL) {printf("文件打开失败!\n");return;}// 写入表头fprintf(fp, "|编号|姓名|年龄|身高|生日|\n");fprintf(fp, "|---|---|---|---|---|\n");// 遍历链表,写入用户信息Node *current = userList->head;while (current != NULL) {fprintf(fp, "|%lld|%s|%d|%.2f|%d-%d-%d|\n",current->user.id,current->user.name,current->user.age,current->user.height,current->user.birthday.year,current->user.birthday.month,current->user.birthday.day);// 移动current = current->next;}// 关闭文件fclose(fp);// 写出一个二进制文件FILE *fp2 = fopen("user.bin", "wb");if (fp2 == NULL) {printf("文件打开失败!\n");return;}// 写入链表长度fwrite(&userList->size, sizeof(int), 1, fp2);// 遍历链表,写入用户信息current = userList->head;while (current != NULL) {// 写入用户信息fwrite(&current->user, sizeof(User), 1, fp2);// 移动current = current->next;}// 关闭文件fclose(fp2);
}
// main.c
// Created by 周航宇 on 2025/2/26.
//int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 创建用户链表UserList userList = {0};while (1) {// 略else if (inputNum == WRITE) {printf("\n\t====== 正在导出用户数据.. ======\n\n");write(&userList);printf("\n\t====== 用户数据导出完毕! ======\n\n");} // 略}return 0;
}

8. 导入用户记录

心法:仅从之前导出的 user.bin 二进制文件导入数据(二进制格式可保证数据完整性,避免文本解析错误)。

导入逻辑

  1. 导入前自动清空原有链表(遍历释放所有节点内存,避免内存泄漏)。
  2. 以 “二进制读模式(rb)” 打开 user.bin,先读取链表长度,再循环读取每个用户的结构体数据。
  3. 为每个读取的用户数据分配新链表节点,插入链表头部,重建用户列表。
  4. 导入完成后自动调用 “打印全部用户” 功能,展示导入结果。
  5. 异常处理:若 user.bin 文件不存在或打开失败,提示 “文件打开失败!”。

效果图

在这里插入图片描述

武技:开发导入用户记录的功能。

// service/UserService
// Created by 周航宇 on 2025/2/26.
//void read(UserList *userList) {// 清空链表Node *current = userList->head;while (current != NULL) {Node *next = current->next;free(current);current = next;}userList->head = NULL;userList->size = 0;// 打开二进制文件FILE *fp = fopen("user.bin", "rb");if (fp == NULL) {printf("文件打开失败!\n");return;}// 读取链表长度fread(&userList->size, sizeof(int), 1, fp);// 临时用户User tempUser;// 从文件中读取用户信息while (fread(&tempUser, sizeof(User), 1, fp) == 1) {// 使用 malloc 分配一个 Node 长度的内存Node *newNode = malloc(sizeof(Node));if (newNode == NULL) {printf("内存分配失败!\n");return;}// 初始化新节点newNode->next = NULL;newNode->user.id = tempUser.id;newNode->user.age = tempUser.age;newNode->user.height = tempUser.height;newNode->user.birthday.year = tempUser.birthday.year;newNode->user.birthday.month = tempUser.birthday.month;newNode->user.birthday.day = tempUser.birthday.day;strcpy(newNode->user.name, tempUser.name);// 将新节点插入链表头部newNode->next = userList->head;userList->head = newNode;}printUserList(userList);// 关闭文件fclose(fp);
}
// main.c
// Created by 周航宇 on 2025/2/26.
//int main(void) {// 设置控制台编码为UTF-8system("chcp 65001 > nul");// 创建用户链表UserList userList = {0};while (1) {// 略else if (inputNum == READ) {printf("\n\t====== 正在导入用户数据.. ======\n\n");read(&userList);printf("\n\t====== 用户数据导入完毕! ======\n\n");}// 略}return 0;
}

C/C++道经 - 项目 - MyUser

http://www.dtcms.com/a/354535.html

相关文章:

  • jQuery 从入门到实践:基础语法、事件与元素操作全解析
  • 通过vs code配置spring boot+maven项目
  • vxetable数据导出
  • GaussDB 数据库架构师修炼(十八) SQL执行引擎-概述
  • 【爬虫】通过模拟鼠标点击和键盘操作抓取网页数据
  • 算法 --- 二分
  • 【深度学习新浪潮】显著性检测最新研究进展(2022-2025)
  • LeetCode 刷题【55. 跳跃游戏】
  • 用 PyTorch 搭建 CNN 实现 MNIST 手写数字识别
  • 如何开发线下陪玩儿小程序
  • 【图像处理基石】DCT在图像处理中的应用及实现
  • natapp 内网穿透
  • 【iOS】Masnory自动布局的简单学习
  • 图算法详解:最短路径、拓扑排序与关键路径
  • 使用 httpsok 工具全面排查网站安全配置
  • Nginx + Certbot配置 HTTPS / SSL 证书(简化版已测试)
  • Android稳定性问题的常见原因是什么
  • JSP程序设计之JSP指令
  • react+vite+ts 组件模板
  • CVPR2025丨VL2Lite:如何将巨型VLM的“知识”精炼后灌入轻量网络?这项蒸馏技术实现了任务专用的极致压缩
  • 传统星型拓扑结构的5G,WiFi无线通信网络与替代拓扑结构自组网
  • BGP路由协议(一):基本概念
  • UE的SimpleUDPTCPSocket插件使用
  • 百度地图+vue+flask+爬虫 推荐算法旅游大数据可视化系统Echarts mysql数据库 带沙箱支付+图像识别技术
  • 【数字黑洞2178】2022-10-28
  • Linux学习-TCP并发服务器构建(epoll)
  • 【C++】C++11的右值引用和移动语义
  • Unity游戏打包——iOS打包基础、上传
  • 使用Docker部署ZLMediaKit流媒体服务器实现gb/t28181协议的设备
  • Day30 多线程编程 同步与互斥 任务队列调度