不同语言中的标准输入输出重定向(freopen)

309 篇文章 5 订阅

转自:http://blog.watashi.ws/2357/freopen-summary/

通常在Online Judge上的题目要求程序从标准输入流(standard input, stdin)读入输入数据,将答案输出到标准输出流(standard output, stdout),并通常会无视标准错误输出流(standard error, stderr)的输出。当然,过多的stderr输出必然会占用许多额外的时间,导致TLE,所以即使把debug信息输出到stderr,有时也要注意通过注释和条件编译去掉for循环中的debug语句。不过更多的时候,我们主要需要关心的还是stdin和stdout。

不过有时候,某些Online Judge的某些题目就会要求从给定文件读入测试数据,或将结果输出到给定文件中,或二者皆有。大多数编程语言在提供文件IO操作API的同时,都提供有许多直接操作标准输入输出的API。而很多初学者对这些API都要比对文件操作的API熟悉得多。所以如果在此时,能用标准输入输出的API来替代文件输入输出的API无疑是很方便的。在本地,这可以通过shell的重定向轻松办到

./p.exe < input.txt > output.txt 2> log.txt

不过在Online Judge上,这一点就行不通了。好在很多语言都提供有一些API,能实现类似的功能。

除了希望使用标准输入输出的API来实现文件输入输出外。在本地调试程序的时候,有时候通过终端(屏幕)进行交互会方便得多,即使要进行文件输入输出也可以借由shell轻松自如的选择文件。所以,程序在本地使用stdio,而在Online Judge上才使用指定的文件会方便不少。

当然,前面还漏了一点,用标准输入输出的API除了可以节省力气外,还有一个好处是:如果同样一道题,在多数OJ上要用标准输入输出,但有些OJ要求文件输入输出时,代码无需经过太多修改就能AC。嗯,其实我就是在做Codeforces上新加的Andrew Stankevich Contests想到可以做这么一个总结的。下面的代码都用Think Positive这道题测试过,可以AC的完整代码请通过github访问。

C

首先是大家再熟悉不过的C语言的例子,直接通过"stdio.h"中的freopen函数实现标准输入输出的重定向,用#ifndef实现条件编译。

1 int main(void) {
2 #ifndef __WATASHI__
3   freopen("whatasha.in""r", stdin);
4   freopen("whatasha.out""w", stdout);
5 #endif
6  
7   /* ... */
8  
9   return 0;
10 }

理想的情况,我们希望新加的代码尽量独立而不影响其它部分。不过C语言标准中并不允许我们通过如下方法:

/* initializer element is not constant */
FILE* __dummy_in = freopen("whatasha.in""r", stdin);

freopen移到main之外。而C语言虽然可以根据平台来实现在main之前执行一段代码,不过方法都不可移植并且略微复杂,采用这样的方法来改写就本末倒置了。

C++

在C++中,我们就可以把这部分代码移到main之外了,而且还可以做得更C++一点:

1 #ifndef __WATASHI__
2 struct $ {
3   $(const char* input, const char* output) {
4     freopen(input, "r", stdin);
5     freopen(output, "w", stdout);
6   }
7 } $("whatasha.in""whatasha.out");
8 #endif

这段代码虽然不符合C++标准,不过GNU C++, GNU C++0x和MS VC++都允许’$'符号作为标识符的一部分。选用$的目的是为了避免重名。当然依个人喜好也可以将$替换成_,再加上namespace什么的。

Pascal

Pascal中直接对inputoutput进行assign即可。在Free Pascal中,如果打开了-So开关(Borland TP 7.0 compatible),那么相应的resetrewrite是必须的,否则会得到Error 103 - File not open

1 begin
2   {$IFNDEF __WATASHI__}
3   assign(input, 'whatasha.in');
4   assign(output, 'whatasha.out');
5   reset(input);
6   rewrite(output);
7   {$ENDIF}
8  
9   (* ... *)
10 end.
Python

许多语言没有条件编译,视其为不必要的,甚至是邪恶的。这时候,我们可以使用环境变量。

1 if os.getenv('USER') != 'watashi':
2   sys.stdin = open('whatasha.in''r')
3   sys.stdout = open('whatasha.out''w')
Perl

Perl中提供了BEGINUNITCHECKCHECKINITEND块,他们将按特定顺序在特定阶段被执行。将重定向代码放到BEGIN块中好处是,即使把它放到程序末尾,它也会最先执行。Codeforces曾经不能正确处理带BEGIN块的代码,不过INIT就没有问题,用INIT还有一个好处就是,它可以节省一个字节。

1 INIT {
2   if ($ENV{'USER'} ne 'watashi') {
3     open STDIN, '<''whatasha.in';
4     open STDOUT, '>''whatasha.out';
5   }
6 }

需要注意的是,Perl中<><STDIN>其实是不同的,前者等价于<ARGV>,不过由于通常@ARGV都为空,二者的效果是一样的。

Ruby

Ruby中也有类似的BEGIN块。

1 BEGIN {
2   if ENV['USER'] != 'watashi'
3     $stdin = open('whatasha.in')
4     $> = open('whatasha.out''w')
5   end
6 }

需要说明的是,Ruby默认从$<读入,输出到$>。前者等价于ARGF,是个只读变量,试图给它赋值将会得到$< is a read-only variable (NameError)。后者就是$stdout的alias,所以我们也可以把代码改成。

$stdout = open('whatasha.out''w')

当ARGV为空时,从$>读入就会变成从$stdin读入。在这一方面Ruby借鉴了Perl的许多行为。

D
1 void main() {
2   if (getenv("USER") != "watashi") {
3     stdin = File("whatasha.in""r");
4     stdout = File("whatasha.out""w");
5   }
6  
7   // ...
8 }
Go
1 func main() {
2   if (os.Getenv("USER") != "watashi") {
3     os.Stdin, _ = os.Open("whatasha.in")
4     os.Stdout, _ = os.Create("whatasha.out")
5   }
6  
7   // ...
8 }
Scheme

Codeforces并不支持Scheme,倒是ZOJ支持。

1 (define (freopen input output)
2   (set-current-input-port (open-file input "r"))
3   (set-current-output-port (open-file output "w")))
4  
5 (if (not (equal? (getenv "USER""watashi"))
6   (freopen "whatasha.in" "whatasha.out"))
PHP

从下面的语言开始,就没有那么freopen的解决方案了。PHP不允许我们重新定义常量,所以不能修改STDIN(php://stdin)和STDOUT(php://stdout)于是我们只能定义个变量$STDIN再做个全局替换了。对于输出到php://outputprintecho,还要用ob_start处理一下。

1 if (getenv('USER') == 'watashi') {
2   $STDIN = STDIN;
3 else {
4   $STDIN fopen('whatasha.in''r');
5   $STDOUT fopen('whatasha.out''w');
6   ob_start(function($buffer) {
7     global $STDOUT;
8     fwrite($STDOUT$buffer);
9   });
10 }
11  
12 // replace STD{IN,OUT} with $STD{IN,OUT}
Scala

Scala写一个”main”的方法比较多,不过都可以通过下面的代码来设置scala.Console.{in,out}scala.Predef._中的各种IO函数都是scala.Console._的alias。

1 if (util.Properties.userName != "watashi") {
2   scala.Console.setIn(
3     new BufferedInputStream(new FileInputStream("whatasha.in")))
4   scala.Console.setOut(
5     new PrintStream(new FileOutputStream("whatasha.out")))
6 }

不过scala.Console.{in,out}java.lang.System.{in,out}并不像std::c{in,out}std{in,out}那样会保持同步。所以如果用到了其它的IO函数,就可能会有一些问题,需要额外处理。

Java

理想的情况,我们只需要在静态代码块中调用System.set{In,Out}就好了。这也是一个非常freopen的解决方案。

1 public class Main {
2   static {
3     try {
4       System.setIn(new BufferedInputStream(new FileInputStream("whatasha.in")));
5       System.setOut(new PrintStream(new FileOutputStream("whatasha.out")));
6     catch (IOException ex) {
7     }
8   }
9 }

可遗憾的是由于Codeforces的权限限制,上面的代码会抛出java.security.AccessControlException: access denied (java.lang.RuntimePermission setIO)。于是我们只好退而求其次,定义两个变量inout,将原来的System.inSystem.out全局替换掉。

1 public class Main {
2   static InputStream in;
3   static PrintStream out;
4  
5   static {
6     if (System.getProperty("ONLINE_JUDGE") == null) {
7       in = new Scanner(System.in);
8       out = System.out;
9     else {
10       String file = "whatasha";
11       try {
12         in = new BufferedInputStream(new FileInputStream(file + ".in"));
13         out = new PrintStream(new FileOutputStream(file + ".out"));
14       catch (IOException ex) {
15       }
16     }
17   }
18  
19   // replace System.{in,out} with {in,out}
20 }

这里套一层BufferedInputStream有时候是必要的,否则可能会TLE。更好的办法也许是伪造一个System,毕竟全局替换是很容易有所遗漏的。

1 final class System {
2   public static InputStream in = java.lang.System.in;
3   public static PrintStream out = java.lang.System.out;
4   public static PrintStream err = java.lang.System.err;
5  
6   public static void exit(int status) {
7     java.lang.System.exit(status);
8   }
9  
10   static {
11     if (java.lang.System.getProperty("ONLINE_JUDGE") != null) {
12       try {
13         String file = "whatasha";
14         in = new BufferedInputStream(new FileInputStream(file + ".in"));
15         out = new PrintStream(new FileOutputStream(file + ".out"));
16       catch (IOException ex) {
17       }
18     }
19   }
20 }

当然,Codeforces上那些专用Java的选手早有模板来处理Java的各种IO问题了。

C#

类似Java,理想的情况我们可以这么做:

1 public class Main {
2   static Main() {
3     Console.SetIn(new StreamReader("whatasha.in"));
4     Console.SetOut(new StreamWriter("whatasha.out"));
5     (Console.Out as StreamWriter).AutoFlush = true;
6   }
7 }

不过与Java可以同时有多个静态代码块不同,C#只能有一个静态构造函数。所以可以考虑写成下面这样,避免和已有的静态构造函数冲突。

1 #if __WATASHI__
2   public static bool __freopn = ((Func<stringstringbool>)(
3       (input, output) => {
4         Console.SetIn(new StreamReader(input));
5         Console.SetOut(new StreamWriter(output));
6         (Console.Out as StreamWriter).AutoFlush = true;
7         return true;
8       }))("whatasha.in""whatasha.out");
9 #endif

不过类似java的情况,SetInSetOut会得到Runtime error。于是我们还可以类似地伪造一个Console

1 public class Main {
2 #if !__WATASHI__
3   class FakeConsole: StreamWriter {
4     StreamReader reader;
5  
6     public FakeConsole(string input, string output): base(output) {
7       reader = new StreamReader(input);
8       AutoFlush = true;
9     }
10  
11     public int Read() {
12       return reader.Read();
13     }
14  
15     public string ReadLine() {
16       return reader.ReadLine();
17     }
18   }
19  
20   static FakeConsole Console = new FakeConsole("positive.in""positive.out");
21 #endif
22  
23   // ...
24 }

顺带一提,之前在Codeforces一旦用C#进行文件操作就会抛出System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.FileIOPermission。看来MikeMirzayanov同学已经fix了这个bug。

OCaml

OCaml并没有类似freopen功能的函数,不过如果有Unix模块的支持的话,通过openfiledup2可以实现同样的功能。事实上glibc的freopen就是通过dup2系统调用实现的,而msvcrt的freopen的源代码则较为复杂一些。

1 #load "unix.cma";;
2 open Unix;;
3  
4 let freopen = fun input output ->
5   let fin = openfile input [O_RDONLY] 0 in
6   dup2 fin stdin;
7   let fout = openfile output [O_WRONLY; O_CREAT; O_TRUNC] 0o644 in
8   dup2 fout stdout;
9 ;;
10  
11 let _ =
12   if getlogin () <> "watashi" then
13     freopen "whatasha.in" "whatasha.out"
14 ;;

按照上面的规律,你应该已经猜到了这个方法在Codeforces上是不work的。如果所有的输入输出都是通过Scanf.scanfPrintf.printf完成的话,还可以做一个类似的全局替换。但如果还调用了Pervasives中的IO函数,那就要麻烦不少了,基本就是ad-hoc地修改了。

Haskell

Haskell也没有类似freopen功能的函数(别说FFI)。但在Haskell中为了避免超时,通常要把String换成Data.ByteString.Char8,甚至有时候还要把getLine换成getContents。同时,作为纯函数式语言,IO Monad天然地把IO操作给隔离了出来,所以许多情况改起来还不算太麻烦。

假如原来的程序用的是interact,那么事情就更简单了。

1 {-# LANGUAGE CPP #-}
2 import Control.Monad
3 import Data.Maybe
4 import qualified Data.ByteString.Char8 as C
5  
6 interact:: (C.ByteString -> String-> IO ()
7 #ifndef __WATASHI__
8 interact= hInteract "whatasha.in" "whatasha.out"
9  
10 hInteract :: FilePath -> FilePath -> (C.ByteString -> String-> IO ()
11 hInteract input output f =
12   liftM f (C.readFile input) >>= writeFile output
13 #else
14 interact= interact . (. C.pack)
15 #endif

63

//main.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>


void main()
{
#ifndef __WATASHI__
		freopen("whatasha.in", "r", stdin);
		freopen("main_c.out", "w", stdout);
#endif
	int n;
	scanf("%d", &n);
	printf("n=%d\n",n);
	printf("out=1024\n");

}
//main.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

#ifndef __WATASHI__
struct $ {
	$(const char* input, const char* output) {
		freopen(input, "r", stdin);
		freopen(output, "w", stdout);
	}
} $("whatasha.in", "whatasha.out");
#endif

void main()
{
	int n;
	cin >> n;
	cout << "n="<<n <<endl;
	cout << "out=1024" <<endl;

}
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;

public class Test_Reopen {
	static {
		//init();
	}

	public static void main(String[] args) throws IOException {
		init();
		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
		System.out.println(reader.readLine());
		System.out.println("java=1024");

	}

	private static void exit() {
		try {
			System.out.println("exit");
			System.in.close();
			System.out.flush();
			System.out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private static void init() {
		try {
			System.out.println("start");
			System.setIn(new BufferedInputStream(new FileInputStream("whatasha.in")));
			System.setOut(new PrintStream(new FileOutputStream("java.out")));
			Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
				
				@Override
				public void run() {
					
					exit();
					
				}
			}));
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
}





  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言标准有一个函数叫做freopen(),它可以用于重定向C程序的输入和输出流。通过调用freopen()函数,我们可以将标准输入流stdin标准输出流stdout重定向到文件或者管道。 要将C程序的标准输入流重定向到管道,可以使用如下命令: ``` freopen("input.txt", "r", stdin); ``` 这会将程序的标准输入流重定向到名为"input.txt"的文件。这样,程序执行时会将文件的内容作为输入。 同样地,要将C程序的标准输出流重定向到管道,可以使用如下命令: ``` freopen("output.txt", "w", stdout); ``` 这会将程序的标准输出流重定向到名为"output.txt"的文件。这样,程序执行后的输出会被写入到文件。 需要注意的是,freopen()函数也可以对C的cin和cout进行重定向。使用方法与上述类似,只需要将文件名替换为管道的名称即可。 通过这种方式,我们可以在C程序实现输入输出重定向,从而实现输入和输出的管道操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++输入输出重定向(3种方法)](https://blog.csdn.net/weixin_39536630/article/details/116964030)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值