尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C语言课程设计:笔记本电脑销售管理系统实现详解

C语言课程设计:笔记本电脑销售管理系统实现详解 简介笔记本电脑销售管理系统C语言课程设计实验是面向C语言初学者的综合实践项目文档围绕产品信息文件创建、信息增删改查、销售管理、按条件查询与销量统计等模块展开功能设计贴近课程设计真实需求。这份doc文档完整呈现了系统功能框架、struct goods结构体定义和核心C语言源码涵盖fopen、fclose、fread、fwrite等文件读写操作以及销售数量、库存数量联动更新、品牌销量最高型号统计等关键逻辑同时梳理了菜单驱动程序与文件持久化存储的整体思路可直接作为课程设计报告撰写和编码调试的参照。资源共1个doc文件压缩包仅57KB内容紧凑精炼便于快速查阅。目前已有873人学习下载适合需要完成C语言课程设计或入门文件管理编程的高校学生。1. 为什么课程设计都选“笔记本电脑销售管理系统”当C语言压轴题很多第一次做C语言课程设计的人看到“笔记本电脑销售管理系统”这个名字会以为只是做一个商品登记表。写出来就会明白这道题把C语言里最常考的四件事全串起来了结构体定义数据、链表管理内存、文件读写做持久化、菜单循环处理用户输入。它比图书管理难在字段多、业务逻辑有“销售扣库存”这种联动操作比学生成绩管理系统又多了买卖场景正好适合当大作业。这套系统解决的核心问题是在不使用数据库的前提下用纯C语言保存笔记本的库存与销售记录并提供录入、删除、查询、统计这些基础功能。适合的人群不只是在校学生还包括想补一遍C语言工程结构的初级开发以及需要带实验课的助教。顺着这个标题做出来的方案拿去写课程设计报告、答辩演示或者面试时展示代码风格都够用。2. 用结构体和链表搭出“笔记本电脑销售管理系统”的数据模型2.1 先把笔记本和销售记录定义成两个结构体一个商品管理系统的核心是先把“笔记本”和“一笔销售”这两个业务对象从描述变成C语言类型。定义笔记本信息时至少要有品牌、型号、CPU、内存、硬盘、价格、库存量定义销售记录时要有销售单号、笔记本型号、数量、成交单价、日期、客户名。注意别把“品牌”和“型号”合并成一个字符串后面查询要分别按品牌和型号过滤拆开写才方便用strstr做模糊匹配。#define NAME_LEN 32 #define MODEL_LEN 32 #define CPU_LEN 32 #define DATE_LEN 16 typedef struct Laptop { char brand[NAME_LEN]; char model[MODEL_LEN]; char cpu[CPU_LEN]; int memory_gb; // 内存大小单位 GB int storage_gb; // 硬盘容量单位 GB double price; // 售价 int stock; // 当前库存 struct Laptop *next; // 链表下一节点 } Laptop; typedef struct SaleRecord { char sale_id[20]; // 销售单号如 S20260612001 char model[MODEL_LEN];// 关联笔记本型号 int quantity; double unit_price; char sale_date[DATE_LEN]; struct SaleRecord *next; } SaleRecord;这里用了两个独立的链表没有把销售记录嵌进笔记本结构体里。原因是销售记录会不断增长而笔记本列表是相对固定的主数据每次销售只把型号复制到销售链表不直接持有指向笔记本节点的指针否则删除笔记本时销售历史会被悬空指针污染。字段类型上价格用double而不是float是因为金额累计时float的精度会在统计大额销售额时丢分。2.2 链表增删改查的四个必备函数课程设计评分时最看重“能不能完整地增删改查”。下面这套函数是常见做法我一般会拆成创建节点、尾部插入、按型号查找、按型号删除四部分。删除的时候要同时维护前驱节点指针这是链表题最容易出 bug 的地方。Laptop* create_laptop(...) { Laptop *p (Laptop*)malloc(sizeof(Laptop)); if (p NULL) { perror(malloc failed); return NULL; } // 用 strcpy 拷贝字段这里省略全部传参 p-next NULL; return p; } void append_laptop(Laptop **head, Laptop *new_node) { if (*head NULL) { *head new_node; return; } Laptop *cur *head; while (cur-next ! NULL) cur cur-next; cur-next new_node; } Laptop* find_laptop(Laptop *head, const char *model) { for (Laptop *p head; p ! NULL; p p-next) { if (strcmp(p-model, model) 0) return p; } return NULL; } int delete_laptop(Laptop **head, const char *model) { Laptop *prev NULL; for (Laptop *cur *head; cur ! NULL; cur cur-next) { if (strcmp(cur-model, model) 0) { if (prev NULL) *head cur-next; else prev-next cur-next; free(cur); return 1; // 删除成功 } prev cur; } return 0; // 型号不存在 }函数内部先判空、再操作delete_laptop用二级指针才能修改调用方的头节点。注意find_laptop返回的是节点指针而不是复制一个结构体这样在后面做“销售扣库存”时可以直接改p-stock。2.3 内存管理释放链表比创建链表更考验基本功实验报告里经常能看到编写了增加和删除却忘了写释放函数。程序退出前不释放链表在课程设计这种短进程里问题不大但会被答辩老师追问。更加隐蔽的错误是删除链表节点时先free再访问cur-next导致野指针崩溃。正确顺序是先用临时变量保存下一个节点再释放当前节点。统一的释放函数如下void free_laptop_list(Laptop *head) { while (head ! NULL) { Laptop *tmp head-next; free(head); head tmp; } }销售链表同理。写代码时可以把两个释放函数分别命名也可以在结构体里加一个type字段做成统一链表。课程设计没有必要为了炫技把业务数据混在一个链表里分开维护更让人看得懂。边界条件建议重点测试空链表删除、头节点被删、链表只有一个节点、连续删除所有节点。这些都是答辩时老师最爱现场敲入的测试路径。3. 给系统接上C语言文件读写操作让重启不丢失3.1 文本文件还是二进制文件课程设计推荐文本格式数据要持久化就得把链表内容写入磁盘。很多初学者第一反应是用fwrite直接写结构体二进制省事、文件也小但课程设计并不推荐。原因是二进制文件无法用记事本直接检查一旦字段顺序调整老数据文件全部作废答辩现场如果把数据文件改成可见的文本演示效果会好很多。下表是文本与二进制方式的直观对比对比项文本文件fprintf/fscanf二进制文件fwrite/fread可读性打开记事本就能检查库存和销售记录乱码只有程序能读调试难度出错时能直接定位到某一行数据需要写额外 hexdump 工具兼容性纯字符跨平台和跨版本都稳结构体对齐不同容易读错文件体积相对大但对小系统无所谓小课程设计加分可以展示“导出数据报表”功能无所以我的标准做法是笔记本库存表和销售记录表各用一个文本文件比如laptops.txt和sales.txt每行一条记录字段之间用|分隔不用空格是因为品牌和型号里可能出现空格。3.2 保存数据到文件fprintf 是顺序写注意 flush保存函数要覆盖两个链表。先把笔记本链表写成如下形式联想|Y7000P|i7-13700H|16|512|7299.00|12每个字段对应结构体里的成员。写文件时用fopen(..., w)模式w会清空原文件所以必须保证所有数据已经完整地在链表里否则文件会被写空。void save_laptops(Laptop *head, const char *filename) { FILE *fp fopen(filename, w); if (fp NULL) { perror(save_laptops open failed); return; } Laptop *p; for (p head; p ! NULL; p p-next) { fprintf(fp, %s|%s|%s|%d|%d|%.2f|%d\n, p-brand, p-model, p-cpu, p-memory_gb, p-storage_gb, p-price, p-stock); } fclose(fp); }fprintf的格式串和printf几乎一样区别只在于第一个参数是文件指针。要注意%.2f输出两位小数的价格这样读取时用%lf就能对应上。fclose会把缓冲区里的数据真正落盘程序崩溃时最后几条可能丢失但只要正常通过菜单退出不会有问题。如果想更稳一点可以在fclose前加fflush(fp)对课程设计不是必须的。销售记录文件的格式可以设计成S001|Y7000P|1|7299.00|2026-06-12|张三字段包括销售单号、型号、数量、单价、日期、客户姓名。保存逻辑和上面一致只是换成SaleRecord链表的遍历。建议把两个保存函数都放在同一个file_ops.c文件里主函数只调用接口不直接出现fopen。3.3 启动时加载数据退出时保存数据先处理“文件不存在”加载文件比保存文件难一点因为课程设计第一次运行时laptops.txt根本不存在。如果fopen用r模式打开不存在的文件会返回NULL这时不能打印错误后退出而是应该当作空系统让用户从菜单开始录入。Laptop* load_laptops(Laptop *head, const char *filename) { FILE *fp fopen(filename, r); if (fp NULL) { printf(库存文件不存在将创建新列表。\n); return head; } Laptop temp; while (fscanf(fp, %31[^|]|%31[^|]|%31[^|]|%d|%d|%lf|%d\n, temp.brand, temp.model, temp.cpu, temp.memory_gb, temp.storage_gb, temp.price, temp.stock) 7) { Laptop *new_node create_laptop(); strcpy(new_node-brand, temp.brand); strcpy(new_node-model, temp.model); strcpy(new_node-cpu, temp.cpu); new_node-memory_gb temp.memory_gb; new_node-storage_gb temp.storage_gb; new_node-price temp.price; new_node-stock temp.stock; append_laptop(head, new_node); } fclose(fp); return head; }这里用了%31[^|]读取不含|的最多31个字符防止字段超长溢出缓冲区。fscanf的返回值是成功匹配的输入项数这里规定有7个字段所以返回值必须等于7才认为这一行是完整的。如果文件最后多一个空行fscanf返回值可能是 EOF 或者 0循环会自动停止。加载和保存是成对出现的保存时怎么用fprintf写加载时就要用对应的fscanf读一个格式串对不上整列数据就会错位。4. 在销售登记、查询和统计里把C语言指针用活4.1 销售登记先查库存再扣库存最后写销售链表销售是这个系统里最有业务感的功能。用户输入型号Y7000P、数量2时系统必须先到笔记本链表里查库存。库存不足就拒绝销售库存够则把stock减去相应数量再往销售链表尾部增加一条记录。这两步必须发生在同一个操作里否则只扣库存不加销售记录统计数据就会对不上。void sell_laptop(Laptop *laptop_list, SaleRecord **sale_list) { char model[MODEL_LEN]; int quantity; printf(请输入笔记本型号: ); scanf(%s, model); printf(请输入购买数量: ); scanf(%d, quantity); Laptop *lap find_laptop(laptop_list, model); if (lap NULL) { printf(该型号不存在。\n); return; } if (lap-stock quantity) { printf(库存不足剩余 %d 台。\n, lap-stock); return; } lap-stock - quantity; SaleRecord *sale create_sale_record(model, quantity, lap-price); append_sale_record(sale_list, sale); printf(销售成功单号 %s金额 %.2f\n, sale-sale_id, quantity * lap-price); }注意sale-sale_id是怎么生成的。常见做法是维护一个全局计数器每次销售时按下标递增再拼上日期生成S20260612001这样的单号。如果不做计数器可以用系统时间戳但课程设计阶段没有必要引入time.h的复杂度。这个函数原则上是模型层的操作可以把printf移到菜单层不过实验报告中写成这样更容易看懂。4.2 按品牌、型号、价格区间查询用 strstr 还是 strcmp查询模块是最容易展示基本功的地方。按品牌查应该用strstr做模糊匹配比如用户输入联想匹配出所有品牌包含“联想”的笔记本按型号查则要用strcmp精确匹配因为型号Y7000P和Y7000不是同一个东西。价格区间查询用两个double变量承接输入再在遍历链表时做范围判断。void query_by_brand(Laptop *head) { char keyword[NAME_LEN]; printf(请输入品牌关键字: ); scanf(%s, keyword); int count 0; for (Laptop *p head; p ! NULL; p p-next) { if (strstr(p-brand, keyword) ! NULL) { printf(%s %s 价格%.2f 库存%d\n, p-brand, p-model, p-price, p-stock); count; } } if (count 0) printf(没有匹配的笔记本。\n); printf(共找到 %d 条记录。\n, count); }这个查询函数看似简单但能体现你对 C 字符串函数边界条件的理解strstr在关键字为空串时返回每一个节点所以建议在scanf之前判断输入是否为空。价格查询最好加上“最低价不能大于最高价”的校验否则结果为空还容易误导用户说系统有 bug。4.3 销售统计与排行用结构体数组接住链表数据再排序统计功能要计算总销售额、总销量以及销量最高的型号。直接把链表里的数据搬到结构体数组里排序是最稳的方案因为链表不方便像数组那样随意交换元素。这里先用一个数组记录每个型号的销量再写一个冒泡排序只交换结构体变量。typedef struct SaleStat { char model[MODEL_LEN]; int total_qty; double total_amount; } SaleStat; void generate_sales_report(SaleRecord *sales, Laptop *laptops) { // 第一步统计每个型号的销量与金额 SaleStat stats[100]; int stat_count 0; for (SaleRecord *s sales; s ! NULL; s s-next) { int idx -1; for (int i 0; i stat_count; i) { if (strcmp(stats[i].model, s-model) 0) { idx i; break; } } if (idx -1) { strcpy(stats[stat_count].model, s-model); stats[stat_count].total_qty s-quantity; stats[stat_count].total_amount s-quantity * s-unit_price; stat_count; } else { stats[idx].total_qty s-quantity; stats[idx].total_amount s-quantity * s-unit_price; } } // 第二步按销量冒泡排序交换结构体变量 for (int i 0; i stat_count - 1; i) { for (int j 0; j stat_count - 1 - i; j) { if (stats[j].total_qty stats[j1].total_qty) { SaleStat temp stats[j]; stats[j] stats[j1]; stats[j1] temp; } } } printf(型号 销量 销售额\n); for (int i 0; i stat_count; i) { printf(%-10s %4d %10.2f\n, stats[i].model, stats[i].total_qty, stats[i].total_amount); } }SaleStat数组大小固定为100课程设计的数据量通常不会超过这个数但严谨一点应该在循环里加“stat_count 100”的越界判断。用strcmp累加时销售记录里的型号要和笔记本里的型号完全一致否则会统计成两个型号。这里的冒泡排序事件复杂度是 O(n²)对100条以内的数据完全够用答辩时如果老师问为什么不用 qsort可以回答“为了展示手写排序与分析能力”。5. 课设演示从头到尾的验证路径与一个亮点技巧课设答辩不是把代码讲一遍就完而是要在几分钟内证明系统真的能“跑通”。我先给出一条最少 5 分钟的演示路径启动程序确认laptops.txt不存在时能自动进入菜单添加三台笔记本执行一次销售退出程序再次启动输入查询命令看到刚才的数据还在。这一步把“文件读写操作”的价值直接展示出来。为了让演示更有说服力可以提前准备好一个带脏数据的文件。比如laptops.txt里手动放一行缺少价格的坏数据启动加载后程序不应崩溃而是跳过错误行继续加载其他记录。处理方式是检查fscanf返回值不是7就直接丢弃这一行。这种“脏数据容忍”是课程设计中很少人做、但很加分的点。额外亮点建议做“销售撤销”功能。实现方式不复杂在销售链表里找到最新一条记录把对应笔记本的stock加回去然后从销售链表中删除该节点。注意删除前要判断笔记本是否已经被删掉如果型号不存在也要允许删除销售记录只弹一条警告提示。这个功能是第 2 章链表删除函数和第 4 章销售扣库存逻辑的复现能证明你不是只写了菜单壳子。最后提一个验证技巧把save_laptops和load_laptops单独抽出来写一个test_load_save()函数在main函数最开始先调用一次。这个测试会创建临时文件、写入一条测试数据、再读回内存然后用strcmp比较字段是否一致不一致就退出并打印错误。很多初学者以为“能编译能运行”就是程序没问题实际上文件读写最容易在换行符和格式串上出错用这个自动测试可以帮你省下大量手敲命令的时间。本文还有配套的精品资源点击获取
返回列表