Loading web-font TeX/Math/Italic
NOIP学习小站
西安交通大学附属中学航天学校

使用循环输入输出多个数据

编写程序解决问题,往往涉及到大量数据的处理,可能要输入批量的数据,也有可能要批量输出大量数据,这个时候往往要借助循环结构来处理。

将输入输出语句放在循环体中,可以很容易实现批量数据的输入输出。这里给出几种竞赛常用的输入输出结构,首先介绍输入输出重定向到文件(从文件读入数据和将结果输出到文件)的方法。

一、竞赛时输入输出重定向到文件

到前面为止,介绍并使用的输入输出方式都是标准输入输出(stdin、stdout),运行程序时会弹出命令行窗口,在命令行窗口中输入数据,运行过程中会在命令行窗口中显示程序输出的结果。包括我们推荐使用的洛谷平台在内,大多数OJ都是使用这样的方式进行程序测评的(只不过测评过程是由测评程序通过调用相应的命令自动完成的)。

但是在程序竞赛中,特别以NOI系列比赛(NOIP、NOI等)为例,要求的是使用文件输入输出——也就是程序要处理的数据从文件读进去,程序运行的结果也输出到文件中保存起来。参加这样的竞赛时,如果没有按照要求使用文件输入输出,即使算法、程序完全正确能够很好地解决问题,也不会获得分数!

首先来看NOIP2017竞赛原题扉页内容:

还有问题描述页:

可以看到图中红色标识出的内容,提出了程序输入文件名和输出文件名要求。CSP J/S、NOIP测评和洛谷不同,需要选手在源程序中通过程序代码从指定文件读取数据(也就是cin/scanf等输入语句输入的数据来自文件,而不是在命令行里输入)并将结果输出到指定文件(也就是cout/printf等输出语句输出到文件中,而不是打印在命令行里)。如果源代码中没有这样的语句,那么测评会出现问题,选手也不能获得分数。

竞赛现场会说明选手目录的位置,进入选手目录,会发现里面按照题目名称已经建立好了子目录,题目子目录下还提供有输入输出样例文件(或者需要按比赛现场要求自行建立选手目录):

↑ 选手目录下按照题目组织的子目录 ↑
↑ 题目子目录中提供的输入输出样例文件 ↑

上面的竞赛真题我们现在解决还有一定的难度,下面通过一个简单的问题,说明NOIP竞赛时的解题过程:

1.新建并保存源代码文件

新建源代码,输入程序框架,然后保存文件。保存文件的时候要保存到选手目录要求的子目录下(本题应该保存到选手目录的min子目录下),并且使用题目要求的文件名保存文件(本题应该保存为min.cpp)

2.新建输入数据文件并在文件中填写好输入样例数据

进入选手目录的题目子目录(本题进入选手目录min子目录),按照题目要求新建一个与题目名称相同的.in文件(本题文件名应该是min.in):其实就是新建一个记事本文本文件,然后重命名文件名即可(注意记事本文件默认后缀是.txt,重命名文件的时候需要去掉.txt修改成.in)。用文本编辑器(Windows下用记事本程序就行)打开新建好的.in文件和提供的测试样例文件(例如本题中的min1.in),将测试样例文件内容拷贝到新建的.in文件并保存。当然也可以拷贝一份提供的样例文件并按题目要求重命名。

3.理清问题、分析问题、设计算法

仔细阅读问题,首先理清输入数据和输出数据的格式和要求(重点阅读“输入格式”和“输出格式”部分内容);然后理清究竟要求解什么问题(有些问题的描述内容很多,需要一步步理清);接下来是分析问题、设计算法,就像求解数学题一样,理清问题后,我们应该从自己接触、掌握的知识和题型库中去寻找要解决问题的模型,根据相关知识和已有算法去设计解决问题的算法。本例就可以使用“打擂台”求极值的算法来计算最小的圆形纸板的半径,然后根据最小半径计算最小面积。

4.根据设计的算法编写程序

注意:这里要在程序里通过程序代码从指定文件读取数据和将结果输出到指定文件中,这样的操作称为输入输出重定向到文件,实现的方法很多,这里介绍一种最简单的(使用freopen函数重定向输入输出):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#include<cstdio>
int main()
{
//从指定文件(min.in)读取输入数据的最简单写法
freopen("min.in","r",stdin); //固定写法,不同的问题仅第一部分文件名不同
//将结果输出到指定文件(min.out)的最简单写法
freopen("min.out","w",stdout); //固定写法,不同的问题仅第一部分文件名不同
int n;
double r,min;
scanf("%d%lf",&n,&min);
for(int i=2;i<=n;i++){
scanf("%lf",&r);
if(r<min) min = r;
}
printf("%.4f",3.14*min*min);
return 0;
}
#include<cstdio> int main() { //从指定文件(min.in)读取输入数据的最简单写法 freopen("min.in","r",stdin); //固定写法,不同的问题仅第一部分文件名不同 //将结果输出到指定文件(min.out)的最简单写法 freopen("min.out","w",stdout); //固定写法,不同的问题仅第一部分文件名不同 int n; double r,min; scanf("%d%lf",&n,&min); for(int i=2;i<=n;i++){ scanf("%lf",&r); if(r<min) min = r; } printf("%.4f",3.14*min*min); return 0; }
#include<cstdio>
int main()
{
	//从指定文件(min.in)读取输入数据的最简单写法 
	freopen("min.in","r",stdin);     //固定写法,不同的问题仅第一部分文件名不同
	//将结果输出到指定文件(min.out)的最简单写法 
	freopen("min.out","w",stdout);   //固定写法,不同的问题仅第一部分文件名不同
	
	int n;
	double r,min;
	scanf("%d%lf",&n,&min);
	for(int i=2;i<=n;i++){
		scanf("%lf",&r);
		if(r<min) min = r;
	}
	printf("%.4f",3.14*min*min);
	return 0;
}

如上面示例程序,竞赛时只需要在平时编程的基础上,在main开头部分使用freopen函数就能很方便地指定程序从文件读数据和将结果输出到文件,其他内容不用做任何修改。其实也有其它的处理方法,要繁琐一些,感兴趣的话可以自行百度。

5.编译运行程序,测试程序

编译运行程序,在Dev C++平台会发现运行情况如下图所示:

只有Dev C++平台的运行提示信息,没有任何输入输出的迹象。这是因为我们已经用代码重定向输入输出到指定文件了,这个时候程序中的输入会自动从文件中去“读”数据,我们打开之前自己准备好的输入文件(min.in,内容其实是从输入样例文件中拷贝来的),会发现以下内容:

如果没有重定向从文件输入数据,在运行程序时,这些其实就是需要我们在命令行输入的数据。现在我们把要输入的数据组织在文件中,让程序从文件中自行读数据。

注意:接触到循环语句后,我们编写的程序往往需要输入大量的数据,测试的时候使用上面重定向从文件输入数据的策略,可以大大提高程序运行时输入数据的效率。

在运行结果命令行窗口中,没有看到任何输出的数据。我们回到选手目录问题子目录下,会发现多了一个.out文件(本例是min.out文件):

用记事本打开.out文件,可知原来程序所有输出的数据都输出到了这个文件中(这就是重定向输出到文件的效果):

我们可以对比.out文件中的输出结果和输出样例.ans文件中的内容,如果相同那么可以初步认为程序没有问题。当然还需要对其他样例进行测试(将其它样例输入文件内容复制粘贴到min.in文件中,再次运行程序,对比.out文件和输出样例.ans文件的内容),甚至还需要自己设计输入输出样例进行更有针对性的测试。

其实NOIP测评的时候,也是按照上面的环节来测试每个数据点的,测试点的输入数据通过文件输入到程序,运行后输出结果会自动保存到输出文件中,再将输出文件与标准答案文件内容对比判断是否通过该点。只不过整个过程是用测评程序自动完成的。

注意:有些选手为了能够更方便看到程序运行结果,会注释掉程序中的重定向输出的语句。但是在竞赛结束前的检查环节,一定要重点检查输入输出重定向语句,如果没有按要求重定向输入输出或者语句被注释,那么该题肯定没有得分!

兴趣扩展:为什了洛谷平台不需要像NOIP那样重定向输入输出,也能对选手提交的程序进行测试呢?其实可以通过命令来实现输入输出重定向。我们编译好程序自动生成exe后,进入cmd命令行环境,使用cd命令进入到源程序目录,试试在cmd中输入命令:min.exe < min.in > min.out(注意根据实际情况修改本命令中的文件名,min.exe也可以不写.exe直接用可执行文件的文件名),发现也能从min.in中读取数据并将结果输出到min.out文件。但是注意这个时候程序中不能有freopen语句来重定向输入输出。自动测评程序将提交的程序编译后(也能通过命令来编译),通过类似前面的命令来测评每个点并比较程序输出文件和标准结果文件内容判断是否通过该点。

二、常见的循环输入模式

1.输入C个用空格隔开的数据(或者输入C行每行只有一个数据),C是常数:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int m;
for(int i=1;i<=10;i++){
cin>>m;
}
int m; for(int i=1;i<=10;i++){ cin>>m; }
int m;
for(int i=1;i<=10;i++){
	cin>>m; 
}

输入多个数据时,多个数据间的分隔符可以是空格,也可以是换行,所以左边的程序适用于输入用空格隔开的10个整数,也适用于输入10行每行一个整数。

如果每行输入多个,直接修改循环体中的cin语句即可:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int a,b,c;
for(int i=1;i<=10;i++){
cin>>a>>b>>c;
}
int a,b,c; for(int i=1;i<=10;i++){ cin>>a>>b>>c; }
int a,b,c;
for(int i=1;i<=10;i++){
	cin>>a>>b>>c; 
}

2.先输入正整数n,再输入n个用空格隔开的数据(或者n行每行只有一个数据):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n,m;
cin>>n;
for(int i=1;i<=n;i++){
cin>>m;
}
int n,m; cin>>n; for(int i=1;i<=n;i++){ cin>>m; }
int n,m;
cin>>n;
for(int i=1;i<=n;i++){
	cin>>m;
}
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n,m;
cin>>n;
while(n--){ //用n--,不能用--n
cin>>m;
}
//循环结束后n==0,这样的结构适用于循环结束后不会使用n原来值的情况
int n,m; cin>>n; while(n--){ //用n--,不能用--n cin>>m; } //循环结束后n==0,这样的结构适用于循环结束后不会使用n原来值的情况
int n,m;
cin>>n;
while(n--){ //用n--,不能用--n
	cin>>m;
}
//循环结束后n==0,这样的结构适用于循环结束后不会使用n原来值的情况

3.先输入正整数n,m,紧接着要输入n行数据,每行是m个用空格隔开的数据:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n,m,p;
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>p;
}
}
int n,m,p; cin>>n>>m; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cin>>p; } }
int n,m,p;
cin>>n>>m;
for(int i=1;i<=n;i++){
	for(int j=1;j<=m;j++){
		cin>>p;
	}
}

4.先输入一个正整数n,紧接着要输入n组数据,每组数据占一行是用空格隔开的若干数据,每行第一个正整数是该组数据数量m,后面有m个数据:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n,m,p;
cin>>n;
for(int i=1;i<=n;i++){
cin>>m;
for(int j=1;j<=m;j++){
cin>>p;
}
}
int n,m,p; cin>>n; for(int i=1;i<=n;i++){ cin>>m; for(int j=1;j<=m;j++){ cin>>p; } }
int n,m,p;
cin>>n;
for(int i=1;i<=n;i++){
	cin>>m;
	for(int j=1;j<=m;j++){
		cin>>p;
	}
}

5.输入数据直到碰到一个特殊值结束(例如输入-1表示处理结束):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n;
cin>>n;
while(n!=-1){
//处理语句
cin>>n;
}
int n; cin>>n; while(n!=-1){ //处理语句 cin>>n; }
int n;
cin>>n;
while(n!=-1){
	//处理语句 
	cin>>n;
}

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n;
for(cin>>n;n!=-1;cin>>n){
//处理语句
}
int n; for(cin>>n;n!=-1;cin>>n){ //处理语句 }
int n;
for(cin>>n;n!=-1;cin>>n){
	//处理语句 
	
}
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n;
while(true){
cin>>n;
if(n==-1) break;
//处理语句
}
int n; while(true){ cin>>n; if(n==-1) break; //处理语句 }
int n;
while(true){
	cin>>n;
	if(n==-1) break;
	//处理语句 
}

6.输入数据直到没有数据为止(或者到文件末尾):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n;
while(cin>>n){
}
int n; while(cin>>n){ }
int n;
while(cin>>n){
	 	
}
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n,m;
while(cin>>n>>m){
}
int n,m; while(cin>>n>>m){ }
int n,m;
while(cin>>n>>m){
	 	
}
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n;
while(scanf("%d",&n)!=EOF){
}
int n; while(scanf("%d",&n)!=EOF){ }
int n;
while(scanf("%d",&n)!=EOF){
	 	
}
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
int n,m;
while(scanf("%d%d",&n,&m)==2){
}
int n,m; while(scanf("%d%d",&n,&m)==2){ }
int n,m;
while(scanf("%d%d",&n,&m)==2){
	 	
}

此时,如果是将输入重定向到了文件,那么会读到文件末尾没有数据为止。如果在命令行运行,会发现无法正常结束输入。在要处理的数据完全输入后,先按住CTRL+Z,然后再按回车,就能退出用于输入数据的while循环,执行后面的语句了。

三、常见的循环输出模式

同循环输入一致,将输出语句置于循环体中就能实现重复输出大量数据。这里需要强调的是,当输出的内容是多个用空格隔开的数据或者是多行数据时,测评时每行末尾的空格和最后额外的空行会被自动忽略。

举例说明:依次输出1~10每个整数的平方,输出时每个数据用一个空格隔开。

我们直接这样编程即可:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#include<iostream>
using namespace std;
int main()
{
for(int i=1;i<=10;i++){
cout<<i*i<<" ";
}
return 0;
}
#include<iostream> using namespace std; int main() { for(int i=1;i<=10;i++){ cout<<i*i<<" "; } return 0; }
#include<iostream>
using namespace std;
int main()
{
	for(int i=1;i<=10;i++){
		cout<<i*i<<" ";
	}
    return 0;
}

仔细分析可知,其实输出了10个整数10个空格,也就是末尾多输出了一个空格,但是测评的时候会自动忽略每行末尾的空格,不用担心测评通不过。

当然就完全没有必要像下面这样编程:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#include<iostream>
using namespace std;
int main()
{
for(int i=1;i<=10;i++){
cout<<i*i;
//避免末尾没有多余空格
//其实这样做没有必要
if(i!=10) cout<<" ";
}
return 0;
}
#include<iostream> using namespace std; int main() { for(int i=1;i<=10;i++){ cout<<i*i; //避免末尾没有多余空格 //其实这样做没有必要 if(i!=10) cout<<" "; } return 0; }
#include<iostream>
using namespace std;
int main()
{
	for(int i=1;i<=10;i++){
		cout<<i*i;
		//避免末尾没有多余空格
		//其实这样做没有必要
		if(i!=10) cout<<" ";
	}
    return 0;
}

同样的,我们打印九九乘法表的程序:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#include<iostream>
using namespace std;
int main()
{
for(int i=1;i<=9;i++){
for(int j=1;j<=i;j++){
cout<<j<<"*"<<i<<"="<<j*i<<" ";
if(j*i<10) cout<<" ";
}
cout<<endl;
}
return 0;
}
#include<iostream> using namespace std; int main() { for(int i=1;i<=9;i++){ for(int j=1;j<=i;j++){ cout<<j<<"*"<<i<<"="<<j*i<<" "; if(j*i<10) cout<<" "; } cout<<endl; } return 0; }
#include<iostream>
using namespace std;
int main()
{
    for(int i=1;i<=9;i++){
        for(int j=1;j<=i;j++){
            cout<<j<<"*"<<i<<"="<<j*i<<" ";
            if(j*i<10) cout<<" ";
        }
        cout<<endl;
    }
    return 0;
}

其实在每一行末尾都有多余的空格,并且在最后还有一个多余的空行,这些在测评的时候都会被忽略,不用担心测评通不过。

登录

注册