【C语言】文件操作— —上机实操经验总结

author : &Carlton

book:谭浩强 《C语言程序设计 第五版》 

tags:C语言,文件操作

topic:打开、关闭与文件读写操作的实操经验

date:2023年7月7日


文本文件与二进制文件的区别

二进制处理方式

程序描述

        

        新建并打开一个既读又写的二进制文件,从键盘上向文件写入数据,再从文件读出数据并打印在显示屏上。

源代码

#include <stdio.h>
#include <stdlib.h>
int main()
{
	FILE *fp;
	//既读又写,在该源文件所在文件夹下新建一个二进制文件std.dat
	if((fp=fopen("std.dat","wb+"))==NULL)
	{
		printf("This file can not open\n");
		exit(1);
	}
	int a=5;
	fwrite(&a,sizeof(int),1,fp);
	//重置文件位置标记到开头
	rewind(fp);
	int b;
	fread(&b,sizeof(int),1,fp);
	printf("%d\n",b);
	return 0;
}

文件数据情况

程序运行结果

   

文本处理方式

程序描述

        删除原有的stu.dat二进制文件,用对文本文件既读又写的方式新建并打开一个stu.dat文本文件,从键盘输入一个整数并存入给文件,再从文件里读出数据并打印到显示屏上。

源代码

#include <stdio.h>
#include <stdlib.h>
int main()
{
	FILE *fp;
	//既读又写,在该源文件所在的文件夹下删除原有的,新建一个文本文件std.dat
	if((fp=fopen("std.dat","w+"))==NULL)
	{
		printf("This file can not open\n");
		exit(1);
	}
	int a=5;
	fprintf(fp,"%d",a);
	//重置文件位置标记到开头
	rewind(fp);
	int b;
	fscanf(fp,"%d",&b);
	printf("%d\n",b);
	return 0;
}

文件数据情况

程序运行结果

示例经验总结

数据流传输过程

两种处理方式的不同

        在任一种处理方式下相应的文件打开方式数据输入输出函数都要调整。

        如二进制处理方式下文件打开方式带b,使用向文件写入二进制数据fwrite,从文件读出数据函数fread等等

两种处理方式的适用性

        文本处理方式方便阅读,例如以上示例中用记事本打开dat文件可以在“字符示意环境”下获取数据情况。

        二进制处理方式适用于需要频繁在内存与磁盘文件交换数据的场景,从磁盘中读入数据到计算机内存,程序对这些数据进行检查、分析、修改和其他处理,把修改过的数据再保存在磁盘上。


综合案例— —学生成绩信息管理

案例描述

        

        对输入的学生数据进行简单的整理,并将整理的数据存入磁盘文件,并及时从文件读出并打印存入到文件的数据来评估数据准确性。        

        ①有5个学生,每个学生均有3门课程的成绩,从键盘输入学生数据(包括学号,姓名,3门课程成绩),计算出平均成绩,将原有数据和计算出的平均分数存放在磁盘文件stu中。

        ②将stu文件中的学生数据按平均分从高到低进行排序处理,将已排序的学生数据存入新文件stu_sort中。

        ③将已排序的学生成绩文件进行插入处理(按平均分成绩高低排序原则),插入后存入原有的stu_sort文件中。

        

算法逻辑

        这又是一道模拟题,算法的思考并不困难,特殊情况也较少,重点在于理解终端、内存及磁盘文件三者的数据传输过程
        第一题:将从键盘输入的数据存入一个数组,再将数组的数据存入给磁盘文件。

        第二题:选择排序法对平均分进行排序即可。

        第三题:

        验证的方法即从相应文件里读出数据存入数组,在显示屏打印出数组的数据以对比验证。
        

值得注意的点和难点

        

         ①可以使用函数完成步骤类似的工作

        例如用data_to_file函数处理保存数据给文件的工作,通过实参的改变来完成建立文件、存储数据给不同文件的任务。

       

        ②利用函数通用性带来的便捷之余要考虑修改函数以适应更大需求的成本

        本例为了囊括更多情况下(数据项数目不同时)都能使用某一函数(如验证函数verify,保存数据给文件函数data_to_file)而对它们进行了改造(多了一个参数m表示数据项),花了一定的时间更新与修改。

        但对于最后的插入问题,发觉修改牵涉键盘输入数据函数keyboard修改作用小,全局变量stu“数组定义,长度确定”的问题,成本高,不如单独设置一个综合的插入函数insert完成全程工作。

        ③注意对象是结构体本身还是结构体成员。在比较大小时是通过average成员,在交换位置时是stu [ i ] 结构体变量。

       

        ④在对文件数据进行读写时注意文件读写标记的位置。适当地用rewind, fseek函数进行调整。

        ⑤定义的数组长度需要是确定的

       👉因此“直接使用数组来存储数据”的做法不能满足我们“需要不同长度空间的内存来存储数据”的需求,所以使用动态内存分配 

       👉或者一开始就定义一个元素数量足够大的数组以满足后续插入多个学生数据的需求。        

        相关的标准:

        高内聚,低耦合

        http://t.csdn.cn/M91rT

源代码

#include <stdio.h>
#include <stdlib.h>
//初始学生个数为5
#define M 5
//声明结构体数据类型并定义结构体数组学生数据为全局变量
struct Student
{
	int number;
	char name[10];
	float score[3];
	float average;
}stu[20];
//将数组元素数量定义得足够大

int main()
{	
	void keyboard(int m);
	void data_to_file(FILE *fp,int m);
	void verify(FILE *fp,int m);
	void sort(struct Student stu[20]);
	void insert(struct Student stu[20],int m);
	FILE *fp1,*fp2,*fp3;
	//会在当前源文件所属文件夹下新建stu.dat和stu_sort.dat文件
	if((fp1=fopen("stu.dat","wb+"))==NULL)
	{
		printf("This file can not open\n");
		exit(1);
	}
	if((fp2=fopen("stu_sort.dat","wb+"))==NULL)
	{
		printf("This file can not open\n");
		exit(1);
	}
	//从键盘输入5个学生的原始数据
	keyboard(M);
	//将数组的数据保存至磁盘文件stu.dat
	data_to_file(fp1,M);
	//将文件的数据打印显示以便对比验证
	verify(fp1,M);
	printf("\n——————————————————————\n");
	//对数组保存的数据进行排序整理
	sort(stu);
	//将排序后的数据从数组中保存至磁盘文件stu_sort.dat
	data_to_file(fp2,M);
	//将文件的数据打印显示以便对比验证
	verify(fp2,M);
	int flag=1,i,m=M;
	while(flag)
	{
		printf("insert a student?(y/n)\n");
		fflush(stdin);
		if(getchar()=='n')
		{
			break;
		}
		m++;
		//数据后移,腾出空位
		for(i=m-1;i>=1;i--)
		{
			stu[i]=stu[i-1];
		}
		//从键盘输入要插入的一个学生数据,保存至stu[0]中
		keyboard(1);
		//整理数组数据,完善插入需求
		insert(stu,m);
		//重置文件读写标记到开头,重写数据以防干扰
		rewind(fp2);
		//将整理好的数组数据保存给磁盘文件stu_sort.dat
		data_to_file(fp2,m);
		//将文件的数据打印显示以便对比验证
		verify(fp2,m);
	}
	//关闭文件
	fclose(fp1);
	fclose(fp2);
	return 0;
}

void keyboard(int m)
{
	printf("input student's number,name,3 scores:(totally %d students)\n",m);
	int i,j;
	//从键盘输入初始数据
	for(i=0;i<m;i++)
	{
		scanf("%d%s",&stu[i].number,stu[i].name);
		for(j=0;j<3;j++)
		{
			scanf("%f",&stu[i].score[j]);
		}
		stu[i].average=(stu[i].score[0]+stu[i].score[1]+stu[i].score[2])/3;
	}
}

void data_to_file(FILE *fp,int m)
{
	int n;
	//将数据存入文件
	for(n=0;n<m;n++)
	{                                                    
		fwrite(&stu[n],sizeof(struct Student),1,fp);
	}
}

void verify(FILE *fp,int m)
{
	//通过打印显示来验证存入数据是否准确

	//重置文件位置标记到开头
	rewind(fp);
	//这里类似于定义、使用一个新的结构体数组保存从文件读出的数据,再用来打印输出
	//定义的数组长度需要是确定的,因此“直接使用数组来存储数据”的做法不能满足我们需要不同长度空间的内存来存储数据的需求,所以使用动态内存分配
	struct Student *p;
	p=(struct Student*)calloc(m,sizeof(struct Student));
	int n,j;
	for(n=0;n<m;n++)
	{
		fread(p+n,sizeof(struct Student),1,fp);
		printf("%5d%10s",(p+n)->number,(p+n)->name);
		for(j=0;j<3;j++)
		{
			printf("%10.1f",(p+n)->score[j]);
		}
		printf("%10.1f\n",(p+n)->average);
	}
}

void sort(struct Student stu[20])
{
	int i,j,k;
	struct Student temp;
	for(i=0;i<M-1;i++)
	{
		k=i;
		for(j=i+1;j<M;j++)
		{
			if(stu[k].average<stu[j].average)
			{
				k=j;
			}
		}
		//注意比过一轮只进行一次交换操作,且交换的对象是整个结构体数据
		if(k!=i)
		{
			temp=stu[k];
			stu[k]=stu[i];
			stu[i]=temp;
		}
	}
}

void insert(struct Student[20],int m)
{
	int i,j;
	struct Student temp;
	//寻找插入位置
	for(i=1;i<m;i++)
	{
		if(stu[i].average<=stu[0].average)
		{
			j=i;
			break;
		}
	}
	temp=stu[0];
	//比插入学生平均分大的学生数据往前移
	for(i=0;i<j-1;i++)
	{
		stu[i]=stu[i+1];
	}
	//插入数据
	stu[j-1]=temp;
}

程序运行结果

改进方向

        插入的算法不需要大幅度地移动数组数据来腾出空位来向数组插入数据后再从数组输入数据给文件。

        依题目需求可知关键在于文件里的数据顺序而并非数组里的数据情况

        因此可以先找到插入位置,然后将位置之前的数据向文件输入,再向文件输入插入数据,最后向文件输入位置之后的数据。

反思与总结

        ①擅于使用函数完成需求

         使用函数逻辑更清晰,程序可读性强,且算法设计过程、程序排错过程具备独立性,效率更高、效果更好。                 

        ②要有数据可视化的验证方法

        通过将磁盘文件数据打印显示可视化来快速验证此程序运行结果是否正确,以便及时做出修正。

       

        ③算法的思考方式

        先用自然语言勾勒出算法实现的大致框架,然后利用N-S盒图可视化具体设计每一步骤(不需要详细代码),最后对核心步骤使用伪代码设计实现。

        一点小小的个人感受:

        每个好习惯都能带来惊喜,走好这一步能更容易地迈出下一步。

        在这个案例里:

        我设置了函数。在后头发现有类似的步骤,稍作修改即可扩大通用性。

        对函数新增了参数。一开始以为牵扯太多,成本过高,没什么意义。但在后头发现是解决学生数量不同情况的关键一招。

        运用了动态内存分配。到中间一步发现原本可以通过定义足够元素数量的数组来更轻易地解决问题,但仔细分析这么做的灵活性更高,性能更好,是很不错的尝试。

                

欢迎“指针”与分享,谢谢!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值