在程序输出较为复杂时,常常采取输出到文件再使用文件比较工具检查答案的方式。然而,使用C的文件操作fwrite(), fread()或C++的文件流操作需要修改程序中每一个输入输出语句,非常不方便。此时,采用重定向方法就可以用二三行代码解决问题。
freopen()
其原型是
freopen(
char* filename,//目标文件名
char* mode,//打开方式
FILE* stream//流正在关联的文件。
//main()被执行时自动打开标准流文件stdin, stdout
)
该函数首先尝试关闭已与stream
(第三个参数)关联的任何文件并取消关联。然后,无论该流是否成功关闭,都会打开由filename
指定的文件,并将其与流关联,这和fopen()
相同。
文件名 其值应遵循运行环境的文件名规范,并且可以包含路径(如果系统支持)。
打开模式
值 | 意义 | 注释 |
---|---|---|
“r” | read | 只读模式。为输入打开一个文本文件,该文件必须存在。 |
“w” | write | 只写模式。为输出新建/打开一个文本文件,若文件已经存在则覆盖其内容。 |
“a” | append | 追加模式。新建/打开一个文件,向末尾输出。 |
“r+” | read/update | 读写模式。为输入输出打开一个文件,该文件必须存在。 |
“w+” | write/update | 读写模式。为输入输出新建/打开一个文件,若文件已经存在则覆盖其内容。 |
“a+” | append/update | 读写模式。为输入输出新建/打开一个文件,向末尾输出。 |
“xb” | binary | 作为二进制打开。x可为以上六个值,也可写为"rb+",“wb+”,“ab+”。 |
流 一般是标准流文件stdin,stdout,stderr等。
调用此函数后,就可以像平常一样使用cin, cout, scanf(), printf()了。
例 从文件data.in中输入两个正整数,输出其最大公约数到同目录下的data.out中
int main(){
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
int a,b;
scanf("%d %d", &a, &b);
//求最大公约数
while(a!=0&&b!=0){
int larger=(a>b)?&a:&b,smaller=(a>b)?&b:&a;
larger%=smaller;
}
printf("%d",a?a:b);
return 0;
}
注意:希望在程序执行中回到键盘输入/控制台输出时,不能用
fclose(stdin);
fclose(stdout);
fclose(FILE* stream)
会关闭输入/输出流。而应该再次重定向
freopen("CON","r",stdin);
freopen("CON","w",stdout);
在Dos/Windows中,控制台的名字是con
freopen("con", "r", stdin);
在Linux中,控制台设备是/dev/console
freopen("/dev/console", "r", stdin);
另外,在类unix系统中,也可以使用dup系统调用来预先复制一份原始的stdin句柄。
在C++ 中引入了流的概念,我们很方便的通过流来读写文本数据和二进制数据,那么流对象的数据究竟是怎么存储的呢,为了搞清这个问题,先来看一看C++ 的IO体系:
图 C++的IO体系
以下是一些背景介绍:
由图可以看出,在stream 的实现中,除了虚基类IOS_BASE之外,所有的类内部都有一个streambuf, streambuf 是一个虚基类(不能被实例化,因此所内部包含streambuf(这个虚基类而非其子类)的类也是虚基类),代表流对象内部的缓冲区,就是我们流操作中输入输出的内容在内存中的缓冲区。
Streambuf有两个子类,分别是stringbuf 和 filebuf,这两个子类可以被实例化,我们常用的文件流和字符串流,内部的缓冲区就是这两个类。
我们平常使用到的流基本是标准输入输出流,文件流和字符串流。在每个流初始化的时候都会初始化相应的streambuf(其实是它的子类)用来缓冲数据。
当我们用文件或者字符串初始化流的时候,流内部会保存该文件和字符串的信息,而在内部实例化一个streambuf用来缓冲数据,些数据时,当缓冲区满的时候再将数据写到文件或者字符串,读数据时当缓冲区没有数据时从文件或字符串读数据到缓冲区。
在文件流这种情况下,streambuf 是为了避免大量的IO 操作;
在字符串流的情况下,streambuf (其实是套在上面的流对象)是为了提供字符串的格式化读取和输出操作。(想象字符串是你从键盘输入的数据)
所以streambuf 可以看作一块缓冲区,用来存储数据。在这种情况下,我们常常在程序中用的 char数组缓冲区是不是可以被替代呢?答案是of course not .
而且,有了streambuf ,缓冲区的管理和写入写出都非常方便,最好的是流对象有复制拷贝等构造函数可以方便参数传递等需要拷贝的情景。
但是streambuf 本身是个虚基类,不能实例化,所以要用streambuf 就需要自己继承streambuf 写一个新的类出来才能用,这个实现方法最后介绍,好在c++ 标准类库实现了两个子类stringbuf 和 filebuf ,所以我们可以选stringbuf 来作为我们的数据缓冲对象(不选filebuf 是因为它的实现和文件紧耦合的,只适合文件流)。
C++中,对流重定向有两个重载函数:
streambuf* rdbuf()const;//get streambuf*
streambuf* rdbuf(streambuf*);//set streambuf*
于是只需要照常使用文件流ifstream fin
streambuf *backup;
ifstream fin("data.in");
backup = cin.rdbuf(); // back up cin's streambuf
cin.rdbuf(fin.rdbuf()); // assign file's streambuf to cin
// ... now cin will read from file
cin.rdbuf(backup); // restore cin's original streambuf
就可以把输入重定向为文件“data.in”
;需要返回键盘输入时再次重定向即可。
Ref:
C/C++标准输入输出重定向;
Rogn的博客;
C++流对象之streambuf*;