[C]基于结构体与文件实现歌手数据增删改及相关问题咨询
嘿,咱们一步步来解决你这个歌手数据管理程序的问题,先把核心痛点拆解清楚:
一、数组扩容的解决方案
你现在用的是固定大小为21的数组,要实现动态扩容,C语言里最常用的就是动态内存分配+realloc,这比预分配超大数组要高效得多。
1. 先定义歌手结构体
首先把你的数据结构明确下来:
#include <stdio.h> #include <stdlib.h> #include <string.h> // 定义歌手结构体 typedef struct { char first_name[50]; // 姓名 char last_name[50]; // 姓氏 int id; // 唯一ID char genre[30]; // 流派 } Singer;
2. 动态数组初始化与扩容逻辑
用指针代替固定数组,初始分配21个元素的空间,当存储数量达到当前容量时,自动扩容(比如每次扩容一倍,或者固定增加20个,按需调整):
int main() { // 初始化动态数组:初始容量21 Singer *singers = malloc(21 * sizeof(Singer)); int current_capacity = 21; int singer_count = 0; // 当前已存储的歌手数量 // 当需要添加新歌手时,先检查是否已满 if (singer_count == current_capacity) { // 扩容:这里选择容量翻倍,你也可以改成 current_capacity += 20 current_capacity *= 2; Singer *temp_ptr = realloc(singers, current_capacity * sizeof(Singer)); // 必须检查realloc是否成功(内存不足时会返回NULL) if (temp_ptr != NULL) { singers = temp_ptr; printf("数组已扩容,当前容量:%d\n", current_capacity); } else { printf("内存分配失败,无法添加新歌手!\n"); // 这里可以选择退出或者做降级处理 return 1; } } // 后续增删改逻辑... // 程序退出前一定要释放动态内存,避免泄漏 free(singers); return 0; }
二、核心功能实现思路
1. Case 2:屏幕展示数据
直接遍历已存储的歌手数据即可,注意只遍历singer_count个元素(不要遍历整个容量):
void display_singers(Singer *singers, int count) { if (count == 0) { printf("暂无歌手数据!\n"); return; } printf("\n===== 歌手列表 =====\n"); printf("ID\t姓名\t\t姓氏\t\t流派\n"); for (int i = 0; i < count; i++) { printf("%d\t%-10s\t%-10s\t%s\n", singers[i].id, singers[i].first_name, singers[i].last_name, singers[i].genre); } }
2. Case 1:结构体数据写入文件
推荐用二进制写入(保留结构体的原始格式,读取更方便),如果需要可读的文本格式也可以用文本写入:
// 二进制写入(推荐) void save_to_file(Singer *singers, int count) { FILE *file = fopen("singers.dat", "wb"); if (!file) { printf("无法打开文件进行写入!\n"); return; } // 写入所有已存储的歌手数据 fwrite(singers, sizeof(Singer), count, file); fclose(file); printf("数据已成功保存到文件!\n"); } // 可选:文本格式写入(方便手动查看) void save_to_text_file(Singer *singers, int count) { FILE *file = fopen("singers.txt", "w"); if (!file) { printf("无法打开文件进行写入!\n"); return; } for (int i = 0; i < count; i++) { fprintf(file, "%d|%s|%s|%s\n", singers[i].id, singers[i].first_name, singers[i].last_name, singers[i].genre); } fclose(file); printf("文本格式数据已保存!\n"); }
3. 新增、编辑、删除功能
- 新增:检查容量→扩容(如果需要)→接收用户输入→赋值给数组末尾,
singer_count++ - 编辑:根据ID遍历数组找到目标歌手→更新对应字段
- 删除:找到目标歌手索引→将后面的元素依次往前移动一位→
singer_count--
举个新增的例子:
void add_singer(Singer **singers, int *capacity, int *count) { // 先检查扩容 if (*count == *capacity) { *capacity *= 2; Singer *temp = realloc(*singers, *capacity * sizeof(Singer)); if (!temp) { printf("内存不足,添加失败!\n"); return; } *singers = temp; } // 接收用户输入 printf("请输入歌手ID:"); scanf("%d", &(*singers)[*count].id); // 注意:用getchar()清除scanf留下的换行符,避免fgets直接读取空行 getchar(); printf("请输入姓名:"); fgets((*singers)[*count].first_name, 50, stdin); // 去掉fgets读取的换行符 (*singers)[*count].first_name[strcspn((*singers)[*count].first_name, "\n")] = '\0'; printf("请输入姓氏:"); fgets((*singers)[*count].last_name, 50, stdin); (*singers)[*count].last_name[strcspn((*singers)[*count].last_name, "\n")] = '\0'; printf("请输入流派:"); fgets((*singers)[*count].genre, 30, stdin); (*singers)[*count].genre[strcspn((*singers)[*count].genre, "\n")] = '\0'; (*count)++; printf("歌手添加成功!\n"); }
三、菜单逻辑整合
把这些功能放到你的菜单switch-case里就行,比如:
int main() { Singer *singers = malloc(21 * sizeof(Singer)); int current_capacity = 21; int singer_count = 0; 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: save_to_file(singers, singer_count); break; case 2: display_singers(singers, singer_count); break; case 3: add_singer(&singers, ¤t_capacity, &singer_count); break; case 4: // 实现edit_singer函数 edit_singer(singers, singer_count); break; case 5: // 实现delete_singer函数 delete_singer(singers, &singer_count); break; case 6: printf("退出程序...\n"); break; default: printf("无效选择,请重新输入!\n"); } } while(choice != 6); free(singers); return 0; }
额外注意点
- 要保证ID的唯一性,新增/编辑时可以加个检查逻辑,避免重复ID
- 处理用户输入时,尽量用
fgets代替scanf读取字符串,避免缓冲区溢出和换行符问题 - 每次动态分配内存后都要检查是否成功,避免空指针错误
内容的提问来源于stack exchange,提问作者Giacomo Corcella
相关产品推荐
相关产品推荐

