C++文件读入与输出简洁教程

既然是“简洁教程”,当然就只讲用法不讲原理啦233

C++从文件中读入一般有两个函数:freopenfopen,这两个函数都在stdio.h(也就是include中所写的cstdio)里面,使用也都很方便,下面我就分别讲一下

(还有fstream相关的说明,但是因为我太菜了,所以说明极度简略)

(大概2/3处还有一个丧心病狂的演示实验qwq)

freopen

用法与理解

一般在算法竞赛中我们使用freopen,代码是这样写的:

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
//write your code here...
return 0;
}

我们知道上面代码中的test.intest.out是文件名,这没问题

但是,中间的"r"``"w"和最后的stdin``stdout又是什么意思呢?

我们先来看看freopenstdio.h中的定义:

1
2
3
FILE *freopen(const char *filename,const char *mode,FILE *stream); //C99 前
FILE *freopen(const char *restrict filename,const char *restrict mode,FILE *restrict stream); //C99 起
errno_t freopen_s(FILE *restrict *restrict newstreamptr,const char *restrict filename, const char *restrict mode,FILE *restrict stream); //C11 起

三种版本基本一致,我们以笔者MinGW中的定义为准,参照上面的第二行(C99 起)

首先讲中间那个"r""w",在定义中是mode,大概也可以猜到了,就是访问文件的模式

那么模式有哪几种呢?

文件访问模式字符串 含义 解释 若文件已存在的动作 若文件不存在的动作
“r” 打开文件以读取 从头读 打开失败
“w” 创建文件以写入 销毁内容 创建新文件
“a” 后附 后附到文件 写到结尾 创建新文件
“r+” 读扩展 打开文件以读/写 从头读 错误
“w+” 写扩展 创建文件以读/写 销毁内容 创建新文件
“a+” 后附扩展 打开文件以读/写 写到结尾 创建新文件

(上述表格和定义来自cppreference.com原文地址

freopen可以被理解为重定向输入输出流,换句话说,这个函数会帮你把输入流和输出流重新指向某些文件,以某种模式进行读入和输出

注意!在使用freopen的"r""r+"模式时,请确保相关文件存在,否则会造成读入空字符串或者一些其他玄学的东西

注意!在使用freopen的"w""w+"模式时,如果文件已存在会销毁文件

最后还有一个参数stream,这个就是指流。一般来说就只有stdin(输入流,控制输入)和stdout(输出流,控制输出)两种

我们平常写完代码运行之后的黑底白字的窗口叫做控制台,也就是Console

在游戏中读写存档

那么,又有一个问题了,比如我想做一个游戏,然后存档用freopen读写,那么怎么办呢?

你可能会说,这还不简单

(下面仅仅是一个实例代码,其中的score表示从存档score.txt读入,最后写入存档的分数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int score = 0;
//读入存档
freopen("score.txt","r",stdin);
scanf("%d",&score);
//这里开始游戏
//...
//游戏结束,score发生变化
freopen("score.txt","w",stdout);
printf("%d",score);
return 0;
}

看起来似乎很可行,不是吗?

且慢,首先我们得搞清楚一个事情。前面提到过,"r"选项如果没有文件就会打开失败,甚至可能导致程序乱码/胡乱输入等,如果用户是第一次玩游戏,那岂不是就Bug了?

好的,有人会说,那我就加个判断呗,弄个消息框,问用户是否需要读档

(或者可以用这篇文章这篇文章里的许许多多中方式检测文件是否存在)

(我记得也可以通过把"r"换成"a+"解决,因为上面的表说了,"a+"也是支持读写的)

好的,这没问题

但是,你忘了一个问题

开始游戏了以后,游戏难道不需要用户输入?一个字符都不需要?

我不相信

但是,读档后就会从文件里读入啊

你控制台输再多字符不也没用吗…

(试想一下,你的用户第一次玩游戏没问题,从第二次开始读档就无法输入)

那这个问题怎么解决呢?

很多人会说,这还不简单,我让用户输不就完了

但是,如果你的程序最后想做成软件,有安装程序的那种,你难道让用户去安装目录里找文件?

还有,如果游戏图形化了,图片和资源文件(比如GC Server里触发某个彩蛋会播放Something just like this,这个mp3文件肯定也要存到本地啊,一首歌又没多大,总不可能让用户听歌还卡顿吧qwq)(我又透露了什么…)也会很多啊,找一个文件也会很麻烦啊

既然上面说freopen重定向输入输出流,定向过去肯定可以定向回到控制台吧

恭喜你,猜对了

(关于重定向到控制台的内容是我当初写游戏的时候百度了半天都没找到,最后问教练解决的)

很简单,还是freopen一行代码:freopen("CON","r",stdin);"CON"表示控制台)

同样的道理,你也可以把输出流定义回来:freopen("CON","w",stdout);(重定义到控制台就不用选模式了,一般来说直接"r"``"w"就可以了)

好的,现在我们把上面的游戏代码改进一下(我懒得写文件判断,就直接"a+"吧,应该可以的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int score = 0;
//读入存档
freopen("score.txt","a+",stdin); //把"r"改为"a+",解决无存档的问题
scanf("%d",&score);
freopen("CON","r",stdin); //把输入重定向回控制台,不然怎么玩游戏
//这里开始游戏
//...
//游戏结束,score发生变化
freopen("score.txt","w",stdout);
printf("%d",score);
return 0;
}

好的,上面这段代码应该可以解决你的疑问了

缺陷与不足

  1. 同时读写多个文件(或者文件与控制台)很麻烦,需要不停地freopen切换来切换去,很麻烦
  2. 在我的电脑上有的时候多次freopen后会出现一些玄学的Bug,导致换行被显示为特殊字符,输出无法换行,重复在一行里从头输到尾,再从头输到尾,至今仍未找到方法解决(也有可能是我代码写得太垃圾导致的)

好的,那么,我们看一下另外一种文件输入输出的方法—fopen

fopen

用法与理解

注:由于博主仅仅将这种用法用于游戏和工程,本部分内容可能并不准确,在某些地方的代码甚至可能出现CE或者RE,如果您发现此类情况,请在本文底部评论中说明,谢谢!

算法竞赛中应该也有一部分人会用这种写法,相对来说复杂一些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
FILE *stdin = NULL;
FILE *stdout = NULL;
stdin = fopen("test.in","r");
stdout = fopen("test.out","w");
//write your code here...
fclose(stdin);
fclose(stdout);
return 0;
}

(注意,上面代码中间部分的printf和scanf请参照下面方式修改)

这种方式的好处在哪里?

好处就在,它可以同时开多个文件进行读入/输出,而且同时也不影响控制台的读入和输出!

定义什么的,因为博主很懒,所以就省略了,自己去前面给的网站搜去~

用法大概是这样的:

首先,你需要定义FILE *xxx = NULL;,这里的xxx相当于一个变量名,注意,这里的变量名和实际读入的文件名没有关系

然后,就可以开始进行读入了:xxx = fopen("xxx.xxx","r");

最后,别忘了关掉这个文件流,否则有可能会出现一些错误:fclose(xxx);

(当然,你可以把FILE *xxx = NULL;xxx = fopen("xxx.xxx","r");缩略写成FILE *xxx = fopen("xxx.xxx","r");

看起还是很简单的,中间那行命令的"r"你肯定已经猜到了,没错,也是模式,和上面是完全一样的

(为了方便读者并且让本文变得更长,把表再放一遍)

文件访问模式字符串 含义 解释 若文件已存在的动作 若文件不存在的动作
“r” 打开文件以读取 从头读 打开失败
“w” 创建文件以写入 销毁内容 创建新文件
“a” 后附 后附到文件 写到结尾 创建新文件
“r+” 读扩展 打开文件以读/写 从头读 错误
“w+” 写扩展 创建文件以读/写 销毁内容 创建新文件
“a+” 后附扩展 打开文件以读/写 写到结尾 创建新文件

但是,使用fopen怎么读入输出呢?难道还是跟前面一样直接printf``scanf就行了?

并没有那么简单,但是其实也不难

只需要把平常你用的printf,稍微改一下即可。比如,

1
printf("Hello,world! %d",a);

(a是一个int型变量)

你只需要把它改成

1
fprintf(&xxx,"Hello,world! %d",a);

(其中的xxx是指前面那个FILE *后面的所谓变量)

同理,原来的scanf就要改成:

1
fscanf(&xxx,"%d",&a);

也是一样的道理

(注:由于博主太菜了,所以目前还不知道cin``cout如何在fopen中修改成文件读入,反正fin``foutfopen应该是没有关系的)

注意!在我的电脑上编译时,如果使用"r"或者"r+"尝试打开一个不存在的文件,会导致程序无响应

好的,我们再把上面那个“游戏模板代码”用fopen写一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int score = 0;
//读入存档
FILE input;
input = fopen("score.txt","a+"); //把"r"改为"a+",解决无存档的问题
fscanf(&input,"%d",&score); //从文件中读入
fclose(input); //关掉输入流(你完全可以在代码末尾再关闭)
//这里开始游戏,开始游戏后的代码里直接scanf或者printf或者cin cout即可
//...
//游戏结束,score发生变化
FILE output = fopen("score.txt","w"); //如果你想偷懒的话,可以直接这样简写
fprintf(&output,"%d",score);
fclose(output); //关闭输出流
return 0;
}

缺陷与不足

  1. 在正常情况下,代码会比freopen多一些,但是如果需要丧心病狂地同时读入/输出一大堆东西(比如下面这堆代码),它会比freopen好得多
  2. 很容易就忘记fclose,有可能导致出现Bug

丧心病狂案例:

要求:共testa.txt``testb.txttestj.txt10个文件,请依次从第1~10个文件中读入test1``test2test1010个int型变量,再从控制台中读入test0这个int型变量,然后分别将test1``test2test10输出到testb.txt``testc.txttesta.txt,最后将这11个变量之和(保证在int范围内)输出到控制台,保证所有文件全部存在且文件中没有其他内容

fopen版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int test0,test1,test2,test3,test4,test5,test6,test7,test8,test9,test10;
FILE *a,*b,*c,*d,*e,*f,*g,*h,*i,*j;//十个文件
a = fopen("testa.txt","r+");
b = fopen("testb.txt","r+");
c = fopen("testc.txt","r+");
d = fopen("testd.txt","r+");
e = fopen("teste.txt","r+");
f = fopen("testf.txt","r+");
g = fopen("testg.txt","r+");
h = fopen("testh.txt","r+");
i = fopen("testi.txt","r+");
j = fopen("testj.txt","r+");//第7~17行可以缩成一行
fscanf(&a,"%d",&test1);
fscanf(&b,"%d",&test2);
fscanf(&c,"%d",&test3);
fscanf(&d,"%d",&test4);
fscanf(&e,"%d",&test5);
fscanf(&f,"%d",&test6);
fscanf(&g,"%d",&test7);
fscanf(&h,"%d",&test8);
fscanf(&i,"%d",&test9);
fscanf(&j,"%d",&test10);
scanf("%d",&test0);
fprintf(&b,"%d",test1);
fprintf(&c,"%d",test2);
fprintf(&d,"%d",test3);
fprintf(&e,"%d",test4);
fprintf(&f,"%d",test5);
fprintf(&g,"%d",test6);
fprintf(&h,"%d",test7);
fprintf(&i,"%d",test8);
fprintf(&j,"%d",test9);
fprintf(&a,"%d",test10);
printf("%d",test0+test1+test2+test3+test4+test5+test6+test7+test8+test9+test10);
}

(40行)

freopen版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int test0,test1,test2,test3,test4,test5,test6,test7,test8,test9,test10;
freopen("testa.txt","r",stdin);
scanf("%d",&test1);
freopen("testb.txt","r",stdin);
scanf("%d",&test2);
freopen("testc.txt","r",stdin);
scanf("%d",&test3);
freopen("testd.txt","r",stdin);
scanf("%d",&test4);
freopen("teste.txt","r",stdin);
scanf("%d",&test5);
freopen("testf.txt","r",stdin);
scanf("%d",&test6);
freopen("testg.txt","r",stdin);
scanf("%d",&test7);
freopen("testh.txt","r",stdin);
scanf("%d",&test8);
freopen("testi.txt","r",stdin);
scanf("%d",&test9);
freopen("testj.txt","r",stdin);
scanf("%d",&test10);
freopen("CON","r",stdin);
scanf("%d",&test0);
freopen("testb.txt","w",stdout);
printf("%d",&test1);
freopen("testc.txt","w",stdout);
printf("%d",&test2);
freopen("testd.txt","w",stdout);
printf("%d",&test3);
freopen("teste.txt","w",stdout);
printf("%d",&test4);
freopen("testf.txt","w",stdout);
printf("%d",&test5);
freopen("testg.txt","w",stdout);
printf("%d",&test6);
freopen("testh.txt","w",stdout);
printf("%d",&test7);
freopen("testi.txt","w",stdout);
printf("%d",&test8);
freopen("testj.txt","w",stdout);
printf("%d",&test9);
freopen("testa.txt","w",stdout);
printf("%d",&test10);
freopen("CON","w",stdout);
printf("%d",test0+test1+test2+test3+test4+test5+test6+test7+test8+test9+test10);
}

(51行)

就可以很明显地看出区别了。还有,我们的fopen写法中可以把第7~17行缩略为一行写完,这样又剩10行(虽然肯定不会那么好看…)

fstream 相关

好吧,其实这种方法我并不知道,写这篇博文的时候查资料才发现的,大概说一下吧。具体内容请看这里这里(前者只给出了示例,后者非常详细)

方法原理:

  1. 创建一个ofstream对象来管理输出流;(如需管理输入流,请把ofstream改为ifstream,原理参见这里
  2. 将该对象与特定的文件关联起来;
  3. 以使用cout的方式使用该对象,唯一的区别是输出将进入文件,而不是屏幕

示例代码:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <fstream>//需要注意的头文件
#include <cstdio>
using namespace std;
int main()
{
ofstream fout; //声明一个ofstream对象,叫做fout
fout.open("test.txt"); //使用open方法将fout对象和特定的文件关联起来,也可以写作 ofstream fout("test.txt");
fout("hello world!"); //向文件写入
fout.close(); //关闭文件连接
}

这种方法就不过多叙述了qwq

总结

综上,在freopenfopen中,如果需要读入/输出的文件实在少得可怜,用freopen可能会简单一些。但如果需要在不同文件中切换操作,或者运行一遍程序可能需要多次从文件中读入或者输出到文件的,还是用fopen靠谱一些,也简短一些。

(不过我没有试过freopenfopen哪个快,有人愿意试一下嘛…

本文标题:C++文件读入与输出简洁教程

文章作者:code004

发布时间:2019年09月08日 - 15:23:09

最后更新:2019年09月09日 - 23:51:18

原始链接:https://code004.ml/posts/cpp-read-from-file/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。