算法竞赛宝典(第一部):语言及算法入门
上QQ阅读APP看书,第一时间看更新

文件读写

通常在比赛中,一道题会有多组测试数据,只有全部测试数据通过,该题才能得满分。那么试想一下,如果在评测时,每道题都是手工输入测试数据再手工检查结果是否正确,那一定是一件让人崩溃的事情。所以比赛要求选手编写的代码必须按要求能自动读取文件中的测试数据,并将运算结果按指定格式以文件格式保存,评测软件如Cena等可以自动比较所有选手的提交答案文件和标准答案文件是否相同而给予相应的分数。这种做法极大地提高了工作效率并且保证了结果的准确性。现以一道简单的比赛仿真题来具体说明。

【例题描述】 求中间数(mid. cpp/c/pas)

对于任意输入的三个整数中,找出大小顺序处于中间的那个数。

【输入格式】

输入文件为mid.in,共一行,即三个整数,以空格间隔。

【输出格式】

输出文件为mid.out,共一行,即中间数。

【输入样例】

    1 2 3

【输出样例】

    2

【时间限制】

1秒

这道题表达的意思如下:

对于Pascal选手来说,提交的源程序名为mid.pas,C选手提交的程序名为mid.c,C++选手提交的程序名为mid.cpp。不允许提交可执行文件。

源程序编译运行时,读取mid.in文件里的数据,输出结果需写到mid.out文件中。每组测试数据必须一秒钟之内出解。另外要注意的是:一定要严格按格式输出,不允许增减任何字符包括空格,输出的最后一行一般应以换行符结束。

读写文件主要有以下几种方法,初学者,只要先熟练掌握其中的一两种方法即可。

方法一(freopen)。

此方法是利用freopen将输入、输出重定向到文件中。例如下面的程序从sum.in文件中读取两个整数,计算出两整数之和后输出到sum.out文件中。

1 //freopen方式读写文件
2 #include <iostream>
3 #include <cstdlib>     //此行必加,否则linux下可能出问题
4 using namespace std;
5 int main()
6 {
7  int a,b;
8  freopen("sum.in","r",stdin);   //从sum.in中读取数据
9  freopen("sum.out","w",stdout); //输出到sum.out文件
10  cin>>a>>b;
11  cout<<a+b<<endl;
12  //system("pause");一定要将此句注释掉
13 }

只需加入第8、9行语句,即可很方便地实现读写文件数据的功能。源代码及读写文件名需根据题意严格定义。

注意:要成功运行该程序,一定要用记事本软件事先建立一个输入文件即文件名为“sum.in”的文件放在该程序的同一文件目录下,否则程序运行时,会因无法找到输入文件而报错。此外,sum.in文件里应该有两个用空格分隔的整数用于程序的读取和计算。

一定要把system("pause");这句代码去除,否则输出文件中会多输出“请按任意键继续...”这一个字符串而导致评测错误。

千万注意文件名别保存为“sum.in.txt”了。

上例是已知输入文件中的数据个数,如果在不知道文件中有多少个数据的情况下,如何正确读取数据并输出呢?请看下例:

1 //读取文件到末尾
2 #include <iostream>
3 using namespace std;
4 
5 int a[500000],i,n;
6 int main()
7 {
8  freopen("in.txt","r",stdin);     //从in.txt中读取数据
9  freopen("out.txt","w",stdout); //输出到out.txt文件
10  for(i=0;cin>>a[i];i++); //如果读到文件末,没数据读取就退出,注意句末有分号
11 
12  n=i;
13  for(i=0;i<n;i++)
14   cout<<a[i]<<' ';
15  return 0;
16 }

方法二(FILE*):

此方法虽然要比方法一复杂,但该方法不属于文件重定向语句,且读取速度快,因此在比赛中推荐使用此方法。

1 //读写文件方法2 
2 #include <iostream>
3 #include <cstdlib>
4 int main()
5 {
6   int i,len=0,temp[100];
7   FILE *in=fopen("a.txt","r");      //指针指向输入文件
8   FILE *out=fopen("b.txt","w"); //输出文件格式
9   for(i=0;!feof(in);i++) //如未到文件末尾
10   {
11    fscanf(in,"%d",&temp[i]); //读取文件中的数据
12    len++;
13   }
14   for(i=0;i<len-1;i++)  //写入文件
15    fprintf(out,"%d ",temp[i]);
16   fclose(in); //关闭文件流
17   fclose(out); //关闭文件流
18 }

FILE的说明:每个被使用的文件都在内存中开辟一个区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个结构体类型的变量中的。该结构体类型由系统定义,取名为FILE。文件读写完毕后需使用fclose函数关闭文件流。

feof函数的说明:feof(in)中in是文件指针。它只有两个返回值。当遇到文件结束符(EOF)时返回1,否则返回0。所以第9行中!feof(in)的意思是,若feof(in)未遇到EOF,则一直执行for循环。

fprintf函数和fscanf函数的说明:fprintf函数和fscanf函数是格式化读写函数,但读写对象为文件。

方法三(fstream):

此方法使用了fstream头文件,推荐C++学习者使用。请看下例:

1 //读写文件方法3
2 #include <iostream>
3 #include<fstream>
4 using namespace std;
5 int main()
6 {
7  int a,b,c;
8  ifstream fin("in.txt");
9  ofstream fout("out.txt");
10  fin>>a>>b>>c;
11  fout<<a*b*c<<endl;
12  fin.close();
13  fout.close();
14  return 0;
15 }

方法四(fread函数):

fread函数原型:size_tfread(void*buffer,size_tsize,size_tcount,FILE*in);

(1)buffer:用于接收数据的地址(指针)。

(2)size:单个元素的大小。单位是字节而不是位。

(3)count:元素个数。

(4)in:提供数据的文件指针。

(5)返回值:读取的元素的个数。

参考程序如下:

1 //fread读取文件1
2 #include <iostream>
3 #include <stdlib.h>
4 char a[1100000];
5 
6 int main()
7 {
8  FILE *in=fopen("a.in","rb");
9  FILE *out=fopen("a.out","w");
10  int n=fread(a,1,1100000,in);
11  for(int i=0;i<n;i++)
12   fprintf(out,"%c",a[i]);
13  return 0;
14 }

fread()是按照char类型读取的,因此当读取的数据类型为数值时,需要再次转化为数值。参考程序如下:

1 //fread读取文件2
2 #include <iostream>
3 using namespace std;
4 FILE *in=fopen("i.txt","rb");
5 int mark=0;
6 char a[10];
7 
8 int getnum()            //将char类型转换为int类型
9 {
10   int obj=0;
11   while(!(a[mark]>='0' && a[mark]<='9'))
12    mark++;
13   while(a[mark]>='0' && a[mark]<='9')
14     obj=obj*10+a[mark++]-'0';
15   return obj;
16 }
17 
18 int main()
19 {
20  freopen("p.txt","w",stdout);  //文件输出
21  int s=1,i;
22  fread(a,6,1,in);  //第二、三个参数依据数组大小而定,够用就好
23  for(i=0;i<3;i++)
24   s*=getnum();
25  cout<<s<<endl;
26  return 0;
27 }

需要注意的是,无论何种文件读写方法,最后一行万万不可写getchar()或system("pause")之类的语句,因为这类语句是在等待用户再输入一个字符。而文件读写形式的程序,是不需要暂停的。

四种文件读写方法的比较如表3.1所示:

表3.1

读取10万个数据(0≤数据≤399)并重新写入文件的测试比较如表3.2所示(其评测环境为Cena)。

表3.2