C/C++道经 - 项目 - MyUser
文章目录
- [S01. 用户管理项目](#S01. 用户管理项目)
-
- [E01. 准备基础代码](#E01. 准备基础代码)
-
- [1. 展示提示菜单](#1. 展示提示菜单)
- [2. 获取用户输入](#2. 获取用户输入)
- [E02. 准备结构体数据](#E02. 准备结构体数据)
-
- [1. 日期结构体](#1. 日期结构体)
- [2. 用户结构体](#2. 用户结构体)
- [3. 用户列表结构体](#3. 用户列表结构体)
- [E03. 开发业务代码](#E03. 开发业务代码)
-
- [1. 添加一名用户](#1. 添加一名用户)
- [2. 打印全部用户](#2. 打印全部用户)
- [3. 统计用户数据](#3. 统计用户数据)
- [4. 查询用户记录](#4. 查询用户记录)
- [5. 修改用户记录](#5. 修改用户记录)
- [6. 删除用户记录](#6. 删除用户记录)
- [7. 导出用户记录](#7. 导出用户记录)
- [8. 导入用户记录](#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。
- 修改外部命令窗口,并初始化主函数如下:
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// 设置控制台编码为UTF-8
system("chcp 65001 > nul");
// 输出语句
printf("\n你好MyUser\n\n");
// 暂停控制台
system("pause");
return 0;
}
效果图:

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

武技:开发展示提示菜单的功能。
c
// 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-8
system("chcp 65001 > nul");
// 展示提示菜单
showMenu();
// 暂停控制台
system("pause");
return 0;
}
2. 获取用户输入
心法:通过 Tool.h 封装的通用输入函数(cin_d,cin_ll,cin_f,cin_s 等)统一处理用户输入,自动转换数据类型(如字符串转数字),并检测非数字字符(返回-1)等异常输入,提示错误信息。
退出系统:用户输入 "9" 时,终止 main 函数中的循环,程序正常退出。
效果图:

武技:开发获取用户输入的功能。
- 封装一个工具类,用于从控制台接收值:
c
// 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);
}
- 开发枚举,提升菜单指令的可读性:
c
// 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 // 退出
};
- 在主页面中接收控制台变量,并处理用户的输入:
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// 设置控制台编码为UTF-8
system("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 语言里并没有内置的日期类型,但你可以通过几种方式来定义日期类型的变量,但可以定义一个结构体来表示日期,结构体里包含年、月、日这几个成员。
武技:开发日期结构体
- 开发日期结构体:
c
// head/Date.h
// Created by 周航宇 on 2025/2/26.
//
// 定义日期结构体
typedef struct Date {
int year; // 年
int month; // 月
int day; // 日
} Date;
- 测试日期结构体:
c
// main.h
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>
#include "head/Date.h"
int main(void) {
// 设置控制台编码为UTF-8
system("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 文件,以免爆发重复引入相关的错误。
武技:开发用户结构体。
- 开发用户结构体:
c
// 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);
}
- 测试用户结构体:
c
// main.h
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>
#include "head/User.h"
int main(void) {
// 设置控制台编码为UTF-8
system("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 这两个文件,以免爆发重复引入相关的错误。
武技:开发用户列表结构体。
- 开发用户列表结构体:
c
// 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;
}
}
- 测试用户列表结构体:使用 memset() 函数需要引入 string.h 头文件:
c
// 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-8
system("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 头文件
- 创建 UserService.h 头文件:
c
// service/UserService.h
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>
#include "../head/UserList.h"
#include "../head/Tool.h"
- 初始化 main.c 文件:
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include <stdlib.h>
#include "service/UserService.h"
int main(void) {
// 略
}
1. 添加一名用户
心法:通过单链表头部插入方式添加新用户,避免遍历链表提升效率。
操作流程:
- 程序提示用户依次输入 "用户编号(long long)、姓名(字符串)、年龄(int)、身高(float)、生日(年 / 月 / 日)"。
- 动态分配链表节点内存(malloc),将输入信息赋值到节点的用户结构体中。
- 将新节点插入链表头部,更新链表长度,并即时打印新添加的用户信息,确认插入成功。
- 异常处理:若内存分配失败(malloc 返回 NULL),提示 "内存分配失败" 并终止操作。
效果图:

武技:开发添加一名用户的功能。
c
// 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);
}
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
int main(void) {
// 设置控制台编码为UTF-8
system("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 位小数,生日以 "年 - 月 - 日" 格式展示。
空数据处理:若链表为空(无用户数据),自动提示 "当前没有用户信息!",避免遍历空指针异常。
效果图:

武技:开发打印全部用户的功能。
c
// service/UserService
// Created by 周航宇 on 2025/2/26.
//
void selectAll(UserList *userList) {
printUserList(userList);
}
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
int main(void) {
// 设置控制台编码为UTF-8
system("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 位小数)。
效果图:

武技:开发统计用户数据的功能。
c
// 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);
}
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
int main(void) {
// 设置控制台编码为UTF-8
system("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. 查询用户记录
心法:按 "用户编号" 精确查询用户记录。
操作流程:
- 提示用户输入待查询的用户编号。
- 遍历链表,对比节点中用户的编号与输入编号:
- 找到匹配用户时,调用 printUser 函数打印该用户的完整信息。
- 未找到匹配用户时,提示 "未找到该编号的用户!"。
效果图:

武技:开发按用户编号查询用户记录的功能。
c
// 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(¤t->user);
return;
}
current = current->next;
}
printf("\n未找到该编号的用户!\n");
}
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
int main(void) {
// 设置控制台编码为UTF-8
system("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. 修改用户记录
心法:按 "用户编号" 定位目标用户,支持修改除 "编号" 外的所有字段(避免编号重复导致查询混乱)。
操作流程:
- 提示用户输入待修改的用户编号,遍历链表定位目标节点。
- 定位成功后,提示用户依次输入 "新姓名、新年龄、新身高、新生日"。
- 将新输入的信息覆盖原用户结构体中的对应字段。
- 即时打印修改后的用户信息,确认修改成功。
- 未找到目标用户时,提示 "未找到该编号的用户!"。
效果图:

武技:开发按用户编号修改用户记录的功能。
c
// 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("> 年龄:", ¤t->user.age);
cin_f("> 身高:", ¤t->user.height);
cin_d("> 生日(年):", ¤t->user.birthday.year);
cin_d("> 生日(月):", ¤t->user.birthday.month);
cin_d("> 生日(日):", ¤t->user.birthday.day);
// 输出修改后的用户信息
printf("\n%lld号用户信息已修改:\n\n", id);
printUser(¤t->user);
return;
}
current = current->next;
}
printf("\n未找到该编号的用户!\n");
}
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
int main(void) {
// 设置控制台编码为UTF-8
system("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. 删除用户记录
心法:按 "用户编号" 定位目标节点,支持删除链表头部、中间及尾部节点,删除后释放内存(避免内存泄漏)。
操作流程:
- 提示用户输入待删除的用户编号,通过 "双指针"(当前节点 currentUser、前驱节点 previousUser)遍历链表:
- 若删除 "头部节点":直接将链表头指针指向头部节点的下一个节点。
- 若删除 "中间 / 尾部节点":将前驱节点的 next 指向当前节点的下一个节点;
- 释放当前节点内存,更新链表长度。
- 未找到目标用户时,提示 "未找到该编号的用户!"。
效果图:

武技:开发按用户编号删除用户记录的功能。
c
// 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");
}
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
int main(void) {
// 设置控制台编码为UTF-8
system("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):以二进制形式存储用户数据(先存链表长度,再存每个用户的完整结构体),便于后续导入时快速读取,节省存储空间。
效果图:

导出文件的位置如图:

武技:开发导出用户记录的功能。
c
// 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(¤t->user, sizeof(User), 1, fp2);
// 移动
current = current->next;
}
// 关闭文件
fclose(fp2);
}
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
int main(void) {
// 设置控制台编码为UTF-8
system("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 二进制文件导入数据(二进制格式可保证数据完整性,避免文本解析错误)。
导入逻辑:
- 导入前自动清空原有链表(遍历释放所有节点内存,避免内存泄漏)。
- 以 "二进制读模式(rb)" 打开 user.bin,先读取链表长度,再循环读取每个用户的结构体数据。
- 为每个读取的用户数据分配新链表节点,插入链表头部,重建用户列表。
- 导入完成后自动调用 "打印全部用户" 功能,展示导入结果。
- 异常处理:若 user.bin 文件不存在或打开失败,提示 "文件打开失败!"。
效果图:

武技:开发导入用户记录的功能。
c
// 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);
}
c
// main.c
// Created by 周航宇 on 2025/2/26.
//
int main(void) {
// 设置控制台编码为UTF-8
system("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