C语言版文件操作,对文件里的数据进行增删改查后写回到文件。
功能有:
列出文件内容
创建文件
添加记录
更新记录
删除文件
删除指定记录
#include
#define maxlen 50
const char* filename = "new.bin";
//学生结构体
typedef struct Record {
char name[maxlen]; //姓名
int age; //年龄
}record;
Record* get_person(Record* precord);
void get_name(char* pname, size_t size);
void write_record(const Record* precord, FILE* pfile);
Record* read_record(Record* Record, FILE* pfile);
void write_file(const char* mode);
void list_file(void);
void update_file(void);
int find_record(Record* Record, FILE* pfile);
void duplicate_file(const Record* pnewrecord, int index, FILE* pfile);
void deleteName(void);
int main()
{
char answer = 'q';
while (true) {
printf_s("\n选择操作:\n"
"列出文件内容,输入 L\n"
"创建文件,输入 C\n"
"添加记录,输入 A\n"
"更新记录,输入 U\n"
"删除文件,输入 D\n"
"删除指定记录,输入 J\n"
"退出程序,输入 Q\n");
scanf_s(" %c", &answer, sizeof(answer));
switch (toupper(answer)) { //转成大写
case'L':
list_file(); //查询数据
break;
case 'C':
(void)getchar();
write_file("wb+"); //创建文件
printf("\n文件创建完成!!\n");
break;
case 'Q':
printf("程序结束!");
exit(0);
case 'A':
(void)getchar();
write_file("ab+"); //添加数据
printf("\n数据写入成功\n");
break;
case 'U':
(void)getchar();
update_file(); //更新数据
break;
case 'D':
printf("确定要删除文件:%s y/n :",filename);
scanf_s("\n %c", &answer, sizeof(answer));
if (tolower(answer) == 'y') {
remove(filename);
printf("\n文件删除成功!!\n");
}
break;
case 'J':
(void)getchar();
deleteName(); //删除文件中指定记录
break;
default:
printf("\n无效选择项\n");
break;
}
}
return 0;
}
//删除某条信息
void deleteName(void) {
FILE* pfile = NULL;
if (fopen_s(&pfile, filename, "rb+")) {
fprintf(stderr, "打开文件错误!");
exit(1);
}
Record record;
int index = find_record(&record, pfile); //查找相同数据
if (index < 0) {
printf("未找到记录!!");
if (pfile != 0)
fclose(pfile); // 关闭流
pfile = NULL;
return;
}
char tempname[L_tmpnam_s]; //L_tmpnam_s应该是表示能用的一个最大值
if (tmpnam_s(tempname, sizeof(tempname))) { //创建唯一的有效文件名
fprintf(stderr, "文件名创建失败!!");
exit(1);
}
FILE* ptempfile = NULL;
if (fopen_s(&ptempfile, tempname, "wb+")) {
fprintf(stderr, "打开文件失败!!");
exit(1);
}
if (pfile != 0)
rewind(pfile); //将文件内部指针指向头部
//把相同数据前的所有数据写入到新文件
for (int i = 0; i < index; ++i)
write_record(read_record(&record,pfile),ptempfile);
read_record(&record, pfile); //把相同的数据写到结构体
while (read_record(&record, pfile))
write_record(&record, ptempfile); //把相同后面的数据写到新文件
/* 接下来删除老文件, 把新文件名字改为老文件名 */
if (pfile != 0 && ptempfile != 0) {
fclose(pfile);
fclose(ptempfile);
}
if (remove(filename)) {
fprintf(stderr, "删除文件失败!!");
exit(1);
}
if (rename(tempname, filename)) {
fprintf(stderr, "文件重命名失败!!");
exit(1);
}
printf("\n记录删除成功!!\n");
}
//更新文件
void update_file(void) {
FILE* pfile = NULL;
if (fopen_s(&pfile, filename, "rb+")) { //打开文件读写
fprintf(stderr, "打开文件错误!!");
exit(1);
}
Record record;
//读取文件数据,直到相同数据,索引
int index = find_record(&record, pfile);
if (index < 0) {
printf("未找到记录!!");
if (pfile != 0)
fclose(pfile); //关闭流
pfile = NULL;
return;
}
/* 找到相同数据 */
printf("\n%s的年龄是:%d\n", record.name, record.age);
printf("-----------------------\n");
printf("输入%s的新名字和年龄:\n\n", record.name);
Record newrecord;
get_person(&newrecord); //输入新数据到结构体
//文件中的名字和输入的名字长度相等
if (strnlen_s(record.name, sizeof(record.name)) == strnlen_s(newrecord.name, sizeof(newrecord.name))) {
if (pfile != 0) {
// fseek从文件当前位置(现在位置是相同那条数据的后面)往后偏移,偏移量是一条数据的全部字节,即:名字长度+名字+年龄,
fseek(pfile, -(long)(sizeof(size_t) + strnlen_s(record.name, sizeof(record.name)) + sizeof(record.age)), SEEK_CUR);
}
//因为相同数据的长度相等,所以可以直接写入覆盖,不会内存溢出
write_record(&newrecord, pfile);
fflush(pfile); //清洗流,清洗情况下会把流中的数据全部写到文件
if (pfile != 0)
fclose(pfile); //关闭流
pfile = NULL;
}
else {
//相同数据的长度不相等,所以不能直接写入覆盖,会内存溢出. 这时可以把要修改的数据和老数据 重新写到新文件,以实现更新数据效果
duplicate_file(&newrecord, index, pfile);
}
printf("\n文件更新完成!\n");
}
// 把要修改的数据和老数据 重新写到新文件
void duplicate_file(const Record* pnewercord, int index, FILE* pfile) {
char tempname[L_tmpnam_s]; //L_tmpnam_s应该是表示能用的一个最大值
if (tmpnam_s(tempname, sizeof(tempname))) { //创建唯一的有效文件名
fprintf(stderr, "文件名创建失败!!");
exit(1);
}
FILE* ptempfile = NULL;
if (fopen_s(&ptempfile, tempname, "wb+")) {
fprintf(stderr, "打开文件失败!!");
exit(1);
}
if (pfile != 0)
rewind(pfile); //将文件内部指针指向头部
Record record;
//index是相同数据的前一条,把相同数据前面的所有数据读取写入到新文件
for (int i = 0; i < index; ++i)
write_record(read_record(&record, pfile), ptempfile);
write_record(pnewercord, ptempfile); //把输入的新数据写入新文件
//这个时候老文件内部指针的位置在相同数据的前一条,现在把相同的数据读取出来放结构,这样下次再从文件读取数据时,就是从相同数据后面那条开始读取了,也就是这代码是跳过相同数据
read_record(&record, pfile);
while (read_record(&record, pfile))//读取相同数据 后面的数据写到新文件
write_record(&record, ptempfile);
if (pfile != 0 && ptempfile != 0) {
fclose(pfile); //关闭流
fclose(ptempfile);
}
if (remove(filename)) { //删除程序开始时创建的 老文件
fprintf(stderr, "删除文件失败!!");
exit(1);
}
if (rename(tempname, filename)) { //新文件名 更改成老文件名
fprintf(stderr, "文件重命名失败!!");
exit(1);
}
}
//是否匹配到相同数据
int find_record(Record* precord, FILE* pfile) {
char name[maxlen];
printf("输入需要查找的名称:");
scanf_s(" %s", name,maxlen);
rewind(pfile); //rewind将文件指针重新指向一个流的开头
int index = 0;
while (true) {
if (!read_record(precord, pfile)) //读取文件中数据直到null
return -1;
if (!strcmp(name, precord->name))//比较字符串,相等返回0
break;
++index;
}
return index; //输入字符串 在 文件中的第几条信息
}
//查询文件内容
void list_file() {
char format[30];
const char* zfc = "名字:%%s 年龄:%%4d\n";
//sprintf_s 将数据(zfc)格式化,存到缓冲区字format
sprintf_s(format, sizeof(format), zfc);
FILE* pfile = NULL;
errno_t er = fopen_s(&pfile, filename, "rb+");
if (er) {
fprintf(stderr, "文件打开失败!");
return;
}
Record record;
printf("文件名:%s\n", filename);
while (read_record(&record, pfile)) //文件读取数据到结构体后输出
printf(format, record.name, record.age);
printf("\n\n");
if (pfile != NULL)
fclose(pfile); //关闭流
}
//从文件中读取数据
Record* read_record(Record* precord, FILE* pfile) {
size_t length = 0;
fread(&length, sizeof(length), 1, pfile);//读取文件名字长度数据
//feof 检查流上的文件结束符,结束返回非0,没结束返回0。是通过文件读取函数fread/fscanf 等返回错误来识别判断的
if (feof(pfile)) {
printf("数据读取完毕!!");
return NULL; //文件结束返回空
}
if (length > maxlen - 1) //名字字符串大于最大值的情况
return precord;
fread(precord->name, sizeof(char), length, pfile); //读取名字数据
precord->name[length] = '\0';
fread(&precord->age, sizeof(precord->age), 1, pfile); //读取年龄
return precord;
}
//创建或写入数据
void write_file(const char* mode) {
char answer = 'y';
FILE* pfile = NULL;
if (mode == "wb+" && mode != 0) { //创建文件
if (fopen_s(&pfile, filename, mode)) {
fprintf(stderr, "文件创建失败"); //文件以存在会创建失败
exit(1);
}
fclose(pfile);//这里不关闭流的话,其它地方打开文件会没权限访问
return;
}
if (mode == "ab+") {
errno_t en = fopen_s(&pfile, filename, mode);
if (en) {
fprintf(stderr, "文件打开失败"); //文件以存在会创建失败
exit(1);
}
}
do {
Record record;
//输入数据到结构体,然后把结构体数据写入文件
write_record(get_person(&record), pfile);
printf("是否继续增加数据 y/n:");
scanf_s(" %c", &answer, sizeof(answer));
(void)getchar();
fflush(stdin); //清洗流
} while (tolower(answer) == 'y');
if (pfile != NULL)
fclose(pfile); //关闭流
}
//二进制写入文件
void write_record(const Record* precord, FILE* pfile) {
//strnlen得到名字字符串的长度
size_t length = strnlen_s(precord->name, sizeof(precord->name));
//把名字字符串长度写进文件,用于后期从文件读取名字数据
fwrite(&length, sizeof(length), 1, pfile);
fwrite(precord->name, sizeof(char), length, pfile);
fwrite(&precord->age, sizeof(precord->age), 1, pfile);
}
//输入数据到结构体
Record* get_person(Record* precord) {
printf("输入小于%d字符的名字:", maxlen);
//输入字符串的时候调用get_name函数可以确保字符串以\0结尾,用scanf有可能不会是\0结尾,这样会导致后面或者字符串长度时溢出读取
//char name[maxlen];
scanf_s(" %s", precord->name,maxlen);
printf("输入%s的年龄:",precord->name);
scanf_s(" %d", &precord->age);
return precord;
}
//输入名字
void get_name(char* pname, size_t size) {
fflush(stdin); //清洗输入流
fgets(pname, size, stdin);
size_t len = strnlen_s(pname, size); //得到输入值长度
if (pname[len - 1] == '\n')
pname[len - 1] = '\0';
}