C语言文件操作,增删改查

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';
}

你可能感兴趣的:(C++)