目录
前言:
当前这篇博客是测试版,教大家相关添加单个商品,显示所有商品,修改单个商品知识点;
看之前建议先看上篇博客:
(C语言)超市管理系统(测试版)(指针)(数据结构)(二进制文件读写)-CSDN博客
共6个文件(加上二进制文件);
源代码:
product.h
//product.h
#pragma once //防止头文件重复定义
#define NAME_LEN 50 //商品名称最大容量
//单个商品结构体
typedef struct {
int id;//商品编号
char name[NAME_LEN];//商品名字
float price;//商品单价
int stock;//商品库存
}Product;
//商品列表表结构体
typedef struct {
Product* Data;//指向单个商品数组的指针
int count;//当前商品数量
}ProductList;
// 函数原型
void Init_products(ProductList* list);//初始化商品列表结构体
void add_product(ProductList* list,Product* product);//添加单个商品
void display_products(ProductList* list);//显示所有商品
void mod_product(ProductList* list, Product* product);//修改单个商品
product.c
//product.c
#include "product.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//初始化商品列表结构体
void Init_products(ProductList* list) {
list->Data = NULL;//指针置空,防止野指针
list->count = 0;//商品数量归0
}
//添加单个商品
void add_product(ProductList* list,Product* product) {
//1.扩展空间
Product* listnew_Data = realloc(list->Data, (list->count + 1) * sizeof(Product));
if (listnew_Data==NULL) {
printf("内存分配失败!\n");
exit(EXIT_FAILURE);
}
list->count++;
list->Data = listnew_Data;//依然用老数组表示描述
//2.ID自动生成
list->Data[list->count - 1].id = list->count;
printf("商品ID:%d\n",list->count);
//3.商品信息录入
printf("请输入商品名称:");
scanf("%49s", list->Data[list->count-1].name);
printf("请输入单价:");
scanf("%f", &list->Data[list->count-1].price);
printf("请输入库存:");
scanf("%d", &list->Data[list->count-1].stock);
printf("添加成功!\n");
}
//显示所有商品
void display_products(ProductList* list) {
//1.判断列表是否为空
if (list->count == 0) {
printf("库存为空\n");
return;
}
//2.打印表头
printf("\n%5s %-20s %10s %6s\n", "ID", "名称", "单价", "库存");
printf("--------------------------------------------\n");
//3.打印商品信息
for (int i = 0; i < list->count; i++) {
printf("%5d %-20s %10.2f %5d\n",
list->Data[i].id,
list->Data[i].name,
list->Data[i].price,
list->Data[i].stock);
}
}
//修改单个商品
void mod_product(ProductList* list, Product* product) {
//1.判断列表是否为空
if (list->count == 0) {
printf("库存为空\n");
return;
}
//2.输入要修改的ID
int id_0;
printf("请输入要修改的ID:");
scanf("%d", &id_0);
//3.判断ID是否存在
if (id_0 > list->count) {
printf("ID不存在!\n");
return;
}
//4.找要修改商品的ID
int i=0;
for (i; i < list->count; i++) {
if (id_0 == list->Data[i].id) {
break;
}
}
//5.修改商品
printf("\n%5s %-20s %10s %6s\n", "ID", "名称", "单价", "库存");
printf("--------------------------------------------\n");
printf("%5d %-20s %10.2f %5d\n",
list->Data[i].id,
list->Data[i].name,
list->Data[i].price,
list->Data[i].stock);
printf("--------------------------------------------\n");
printf("修改商品名称:");
scanf("%49s", list->Data[i].name);
printf("修改单价:");
scanf("%f", &list->Data[i].price);
printf("修改库存:");
scanf("%d", &list->Data[i].stock);
printf("修改成功!\n");
}
fileio.h
//fileio.h
#pragma once
#include "product.h"
// 文件操作函数原型
void save_to_file(const char* filename, const ProductList* list);
void load_from_file(const char* filename, ProductList* list);
fileio.c
//fileio.c
//引用头文件
#include <stdio.h>
#include <stdlib.h>
#include "product.h"
// 保存数据到文件(二进制写入)
void save_to_file(const char* filename, const ProductList* list) {
//1.打开文件(二进制写入模式)
FILE* fp = fopen(filename, "wb");
// "wb":二进制写入模式,会清空原文件内容
// 若文件不存在则创建新文件
if (!fp) { // fp == NULL 表示打开失败
perror("保存失败"); // 输出错误信息(包含具体原因,如权限不足)
exit(EXIT_FAILURE); // 终止程序,EXIT_FAILURE 表示异常退出
}
//2.先写入商品数量(int 类型)
fwrite(&list->count,sizeof(int),1,fp);
// &list->count:取商品数量的内存地址
// sizeof(int):每个元素的大小(4字节)
// 1:写入1个元素
// fp:文件指针
//3.再写入所有商品数据(Product 结构体数组)
fwrite(list->Data, sizeof(Product), list->count, fp);
// list->Data:商品数组首地址
// sizeof(Product):每个商品占用的字节数
// list->count:要写入的商品数量
//4.关闭文件
fclose(fp);
}
// 从文件加载数据(二进制读取)
void load_from_file(const char* filename, ProductList* list) {
//1.初始化结构体(防御性编程)
Init_products(&list);//初始化商品列表结构体
//2.尝试打开文件(二进制读取模式)
FILE* fp = fopen(filename, "rb");// "rb":二进制读取模式,文件不存在时返回 NULL
if (!fp) {//文件打开失败处理
return; // 保持 list 的初始状态(count=0, Data=NULL)
}
//3.读取商品数量(int 类型)
fread(&list->count,sizeof(int),1,fp);
// 从文件中读取4字节到 list->count
//4.根据数量分配内存
list->Data = malloc(list->count * sizeof(Product));
// 计算总字节数 = 商品数量 × 单个商品大小
//检查是否分配成功
if (list->Data == NULL) { // list->Data == NULL 表示失败
printf("内存分配失败\n");
exit(EXIT_FAILURE); // 终止程序
}
//5.读取所有商品数据
fread(list->Data, sizeof(Product), list->count, fp);
// 将文件内容直接读入 Data 数组
//6.关闭文件
fclose(fp);
}
main.c
//mian.c
#include <stdio.h>
#include <stdlib.h>
#include "product.h"
#include "fileio.h"
#define FILENAME "products.dat"//宏定义文件名
//清屏操作
void clear_screen() {
//判断是否为Windows系统
#ifdef _WIN32
system("cls");
//其他系统
#else
system("clear");
#endif
}
// 显示主菜单(用户界面)
void display_menu() {
printf("\n超市管理系统\n");
printf("1. 添加商品\n");
printf("2. 显示所有商品\n");
printf("3. 修改商品信息\n");
printf("4. 删除商品\n");
printf("5. 搜索商品\n");
printf("6. 保存并退出\n");
printf("请选择操作:");
}
int main() {
//1.创建结构体并初始化
Product product;//创建单个商品结构体
ProductList list;//创建商品列表结构体
Init_products(&list);//初始化
//2.读文件
load_from_file(FILENAME, &list);//读文件
//3.选择模块
int choice;//选择选项
while (1) {
display_menu();//显示菜单
scanf("%d", &choice);//输入选项
switch (choice) {
case 1:
clear_screen();
add_product(&list,&product);
printf("--------------------------------------------\n");
break;
case 2:
clear_screen();
display_products(&list);
printf("--------------------------------------------\n");
break;
case 3:
clear_screen();
mod_product(&list,&product);
printf("--------------------------------------------\n");
break;
case 6:
save_to_file(FILENAME, &list); // 保存数据
free(list.Data); // 释放动态内存
printf("系统已退出\n");
return 0; // 正确退出
default:
printf("无效输入\n");
}
}
}
代码解析:
一、程序结构概述
整个程序分为三个核心模块:
数据管理模块 (
product.c
):处理商品的增删改查文件操作模块 (
fileio.c
):负责数据保存与加载主控模块 (
main.c
):协调程序流程和用户交互
二、product.c 函数详解
1. 初始化商品列表
Init_products
void Init_products(ProductList* list) { list->Data = NULL; // 指针置空,防止野指针 list->count = 0; // 商品数量归0 }
功能:
初始化商品列表结构体,确保程序启动时处于干净状态。
实现步骤:
Data = NULL
:将动态数组指针置空,避免指向随机内存。
count = 0
:商品数量初始化为0。为什么这样写:
防御性编程:确保程序启动时没有残留数据。
动态内存安全:
Data
初始为NULL
,realloc
在首次调用时会自动分配内存。如何使用:
ProductList list; // 声明一个商品列表 Init_products(&list); // 初始化列表(必须调用)
2. 添加商品
add_product
void add_product(ProductList* list, Product* product) { // 1. 扩展内存 Product* listnew_Data = realloc(list->Data, (list->count + 1) * sizeof(Product)); if (listnew_Data == NULL) { printf("内存分配失败!\n"); exit(EXIT_FAILURE); } list->count++; list->Data = listnew_Data; // 2. 自动生成ID list->Data[list->count - 1].id = list->count; printf("商品ID:%d\n", list->count); // 3. 录入商品信息 printf("请输入商品名称:"); scanf("%49s", list->Data[list->count-1].name); printf("请输入单价:"); scanf("%f", &list->Data[list->count-1].price); printf("请输入库存:"); scanf("%d", &list->Data[list->count-1].stock); printf("添加成功!\n"); }
功能:
动态扩展内存,添加新商品并自动生成ID。
实现步骤:
内存扩展:使用
realloc
将数组大小增加1个商品位置。错误处理:检查内存是否分配成功,失败则终止程序。
生成ID:新商品ID = 当前商品总数 + 1(例如第一个商品ID=1)。
输入信息:依次输入名称、单价、库存。
为什么这样写:
动态内存管理:
realloc
自动处理内存扩展,无需手动复制数据。简单ID生成:直接使用
count
作为ID,但存在删除商品后ID不连续的问题(后续改进点)。如何使用:
ProductList list; Init_products(&list); add_product(&list, NULL); // 添加第一个商品
输入示例:
请输入商品名称:苹果 请输入单价:5.5 请输入库存:20
注意事项:
输入缓冲区问题:连续使用
scanf
可能导致残留换行符,需清空缓冲区(代码未处理)。名称输入限制:
%49s
防止溢出,但无法输入带空格的名称(如“红富士苹果”)。
3. 显示商品
display_products
void display_products(ProductList* list) { if (list->count == 0) { printf("库存为空\n"); return; } printf("\n%5s %-20s %10s %6s\n", "ID", "名称", "单价", "库存"); printf("--------------------------------------------\n"); for (int i = 0; i < list->count; i++) { printf("%5d %-20s %10.2f %5d\n", list->Data[i].id, list->Data[i].name, list->Data[i].price, list->Data[i].stock); } }
功能:
以表格形式打印所有商品信息,处理空列表情况。
实现步骤:
空列表检查:直接返回提示信息。
打印表头:使用格式化字符串对齐标题。
遍历打印:循环输出每个商品的字段。
为什么这样写:
用户体验:清晰的表格布局提升可读性。
格式控制符:
%5d
:ID占5字符宽度,右对齐。
%-20s
:名称左对齐,占20字符。
%10.2f
:单价保留两位小数,总宽度10。如何使用:
display_products(&list); // 显示当前所有商品
输出示例:
ID 名称 单价 库存 -------------------------------------------- 1 苹果 5.50 20 2 香蕉 3.80 15
4. 修改商品
mod_product
void mod_product(ProductList* list, Product* product) { if (list->count == 0) { printf("库存为空\n"); return; } int id_0; printf("请输入要修改的ID:"); scanf("%d", &id_0); if (id_0 > list->count) { printf("ID不存在!\n"); return; } int i=0; for (i; i < list->count; i++) { if (id_0 == list->Data[i].id) { break; } } // 显示原信息并修改 printf("\n%5s %-20s %10s %6s\n", "ID", "名称", "单价", "库存"); printf("--------------------------------------------\n"); printf("%5d %-20s %10.2f %5d\n", list->Data[i].id, list->Data[i].name, list->Data[i].price, list->Data[i].stock); printf("--------------------------------------------\n"); printf("修改商品名称:"); scanf("%49s", list->Data[i].name); printf("修改单价:"); scanf("%f", &list->Data[i].price); printf("修改库存:"); scanf("%d", &list->Data[i].stock); printf("修改成功!\n"); }
功能:
根据用户输入的ID查找商品,修改其信息。
实现步骤:
空列表检查:直接返回提示。
输入目标ID:用户指定要修改的商品。
ID存在性检查:错误判断逻辑不严谨(
id_0 > count
可能漏判)。遍历查找:找到对应商品的数组索引。
显示并修改:打印原信息,逐项修改。
为什么这样写:
直观交互:先展示原信息再修改,减少误操作。
直接修改内存:通过指针直接修改数组元素。
问题与改进:
ID检查缺陷:
id_0 > list->count
假设ID连续且等于count,实际可能因删除操作导致ID大于count。未处理未找到ID:循环结束后未检查是否找到有效索引,可能导致越界访问。
如何使用:
mod_product(&list, NULL); // 修改ID为2的商品
输入示例:
请输入要修改的ID:2 ...(显示原信息)... 修改商品名称:香蕉 修改单价:4.5 修改库存:25
三、main.c 主函数详解
1. 主函数
main
int main() { Product product; // 单个商品(未实际使用) ProductList list; // 商品列表 Init_products(&list); // 初始化列表 load_from_file(FILENAME, &list); // 加载数据 int choice; while (1) { display_menu(); // 显示菜单 scanf("%d", &choice); switch (choice) { case 1: add_product(&list, &product); break; case 2: display_products(&list); break; case 3: mod_product(&list, &product); break; case 6: save_to_file(FILENAME, &list); // 保存数据 free(list.Data); // 释放内存 printf("系统已退出\n"); return 0; default: printf("无效输入\n"); } } }
功能:
程序入口,管理整个生命周期:初始化→加载数据→循环处理用户操作→退出保存。
实现步骤:
初始化:创建商品列表并初始化。
加载数据:从文件读取历史数据。
主循环:
显示菜单,获取用户选择。
调用对应功能函数。
退出处理:保存数据并释放内存。
关键设计:
循环结构:
while(1)
保持程序持续运行。内存释放:退出前必须
free(list.Data)
,否则内存泄漏。模块化调用:通过
switch-case
调用各功能函数。用户交互流程:
graph TD A[启动程序] --> B[加载数据] B --> C{显示菜单} C --> D[用户选择] D -->|1-5| E[执行操作] E --> C D -->|6| F[保存并退出]
2. 辅助函数
clear_screen
void clear_screen() { #ifdef _WIN32 system("cls"); // Windows清屏 #else system("clear"); // Linux/Mac清屏 #endif }
功能:
清空控制台屏幕,提升界面整洁度。
为什么这样写:
跨平台兼容:通过预编译指令区分系统。
简单调用:
system
函数直接执行系统命令。如何使用:
clear_screen(); // 清空屏幕后显示新内容
四、核心知识点总结
1. 动态内存管理
realloc
的作用:动态调整内存大小,首次调用时等效于malloc
。错误处理:必须检查返回值是否为
NULL
。内存释放:
free
必须与malloc/realloc
配对使用。
2. 结构体的使用
数据封装:将商品信息打包为
Product
结构体。列表管理:
ProductList
封装动态数组和长度,提升代码可维护性。
3. 输入输出安全
缓冲区溢出防护:
scanf("%49s")
限制输入长度。格式化输出:
printf
的格式控制符对齐数据。
4. 文件操作
二进制模式:
"wb"
和"rb"
确保数据精确存储。数据序列化:直接读写结构体内存,高效但需注意平台兼容性。
五、动手实践建议
添加删除功能:实现
delete_product
函数,练习内存缩减 (realloc
)。增强输入验证:检查价格是否为负数,库存是否为整数。
实现搜索功能:按名称或ID查找商品,练习字符串处理 (
strstr
)。
注:该代码是本人自己所写,可能不够好,不够简便,欢迎大家指出我的不足之处。如果遇见看不懂的地方,可以在评论区打出来,进行讨论,或者联系我。上述内容全是我自己理解的,如果你有别的想法,或者认为我的理解不对,欢迎指出!!!如果可以,可以点一个免费的赞支持一下吗?谢谢各位彦祖亦菲!!!!!