如何从结构体数组删除元素并使用fwrite正确覆盖文件
解决C语言图书馆程序删除书籍后文件覆盖的问题
嘿,我来帮你搞定这个问题!你遇到的核心问题其实有两个:一是内存数组操作的小bug,二是**"r+b"模式下文件不会自动缩短,残留的旧数据导致读取异常**。咱们一步步来解决:
首先,揪出代码里的关键问题
1. 操作数组时的越界错误
你的remove_book函数里,前移元素的循环条件有问题:
for (int index = i ; index < *num ; index++) bibl[index] = bibl[index + 1];
当index等于*num - 1时,bibl[index+1]会访问数组的越界位置(比如数组最大索引是MAXBOOK-1,你却访问了MAXBOOK),这会导致未定义行为,甚至把随机数据塞进数组里。
2. 每次操作都重新读取文件,覆盖内存修改
在case 2(删除书籍)里,你先调用了input(bibl, &counter, &counterp),这会把文件里的旧数据重新读入数组,直接覆盖你刚在内存中删除元素后的数组——等于白删了!
3. 文件未截断,残留旧数据
用r+b模式打开文件时,如果你只写入前counter个有效结构体,文件后面的旧数据不会自动消失。比如原来文件有3个结构体,删除后写入2个,文件长度还是3个结构体的大小,读取时会把最后那个旧数据也读进来。
具体修复步骤和代码修改
步骤1:修正remove_book函数的数组操作
修改前移循环的条件,避免越界,同时增加书籍未找到的判断:
void remove_book(info bibl[], int * num) { char title[256]; puts("Enter the title of the book you want to remove:"); read(title, 256); // 先找到要删除的书籍索引 int found_idx = -1; for (int i = 0 ; i < *num ; i++) { if (strcmp(title, bibl[i].title) == 0) { found_idx = i; break; } } if (found_idx == -1) { puts("Oops, that book isn't in the library!"); return; } // 前移元素,循环到*num-2即可,避免访问越界 for (int index = found_idx ; index < *num - 1 ; index++) { bibl[index] = bibl[index + 1]; } // 有效书籍数量减1,不需要赋值空结构体(后面不会写入这个位置) (*num)--; // 打印当前库中的书籍 puts("Updated library contents:"); for (int index = 0 ; index < *num ; index++) { printf("%s, author: %s, price: %.2f €\n", bibl[index].title, bibl[index].autor, bibl[index].price); } }
步骤2:修正菜单中的操作逻辑,避免重复读取文件
在main函数的菜单循环里,不要每次操作都调用input——内存里的数组已经是最新状态了,直接修改数组后写入文件即可。以case 2为例:
case 2 : remove_book(bibl, &counter); // 将文件指针移到开头,准备覆盖写入 rewind(fp); // 写入前counter个有效结构体 size_t written = fwrite(&bibl[0], sizeof(info), counter, fp); if (written != counter) { fprintf(stderr, "Error writing to file! Some data might be lost.\n"); } // 关键:截断文件,去掉后面的旧数据 #ifdef _WIN32 // Windows系统用_chsize if (_chsize(fileno(fp), counter * sizeof(info)) == -1) { fprintf(stderr, "Error truncating file on Windows.\n"); } #else // POSIX系统(Linux/macOS)用ftruncate if (ftruncate(fileno(fp), counter * sizeof(info)) == -1) { fprintf(stderr, "Error truncating file on POSIX system.\n"); } #endif break;
步骤3:确保初始读取逻辑正确
你的input函数应该正确读取文件中所有结构体到数组,初始化counter为当前书籍数量:
void input(info bibl[], int *counter, int *counterp) { *counterp = 0; // 逐个读取结构体,直到文件结束 while (fread(&bibl[*counterp], sizeof(info), 1, fp) == 1) { (*counterp)++; } *counter = *counterp; // 初始时有效书籍数量等于读取到的数量 }
原理说明
- 数组操作修正:前移元素时只循环到
*num-2,保证不会访问数组越界的位置,避免引入随机数据。 - 避免重复读取文件:内存数组是唯一的数据源,所有增删改操作都直接修改内存数组,操作完成后再写入文件,不会覆盖修改结果。
- 文件截断:
ftruncate(POSIX)或_chsize(Windows)会把文件长度调整为counter * sizeof(info),这样文件里就只会保留有效数据,后续读取时不会读到旧的残留数据。
这样修改后,你删除书籍再写入文件,读取时就只会得到有效结构体了!
内容的提问来源于stack exchange,提问作者n01s0n
相关产品推荐
相关产品推荐

