算法竞赛:输入

专栏:算法竞赛

上一篇:算法竞赛:Online Judge介绍

下一篇:

1. 数据的读取方式

1.1 自行处理输入

  目前在大部分的 OJ 网站中,用户需要编写一个完整的程序。此时需要用户在提交的代码中自行读取输入

  程序要读取的数据被保存在文件中,内容以文本的形式给出,用户可以通过标准输入输出来进行读取。

  如果数据是保存在文件中的话, 为什么不需要用文件操作就可以直接用标准输入读取呢?标准输入不是从终端读取的吗?

  • 这是因为使用了输入重定向,将标准输入重定向到文件,所以使用标准输入将会从文件中读取,在下文中会讲解。

在这里插入图片描述
如上图所示的输入格式,C 只需要调用如下代码便可读取

int a, b;
scanf("%d %d", &a, &b);

如果使用C++,则为

int a, b;
std::cin >> a >> b;

头文件包含

函数或对象CC++
scanf()<stdio.h><cstdio>
std::cin<iostream>

1.2 从函数参数中获取

LeetCode 题目:两数之和

  一小部分 OJ 网站(如 LeetCode )已经做好了整个程序的框架,完成了数据的读取,并将数据传到函数参数中。用户需要按题目要求补充函数或类的内容。

  用户需要选用算法,实现函数功能,从函数参数中获取数据,计算出正确值并返回。

  如下所示,是 LeetCode 中的 两数之和 题目。
在这里插入图片描述
  在代码编辑区域中,可以看到默认生成了个twoSum() 函数。用户需要按题目要求,在函数中实现相应功能,并将结果通过 函数返回值函数参数 返回。
在这里插入图片描述

2. 变量及其输入

  这里讲解在需要自行读取输入的情况。

2.1 数据的输入:字符流

  题目给出的数据是 以字符的形式存储在文件中,程序在读取数据的时候,读取到的也是一个个字符,也就是说,数据是以字符流的形式被程序读取。

在这里插入图片描述

例如,对于 输入样例:20 30 来说:

  • 程序读取的内容相当于是字符序列: "20 30"
  • 保存的内容并不是理解上的数字,直接就对应整型值 0x00000014 0x0000001E,而是字符序列 "20 30" 中字符按顺序所对应的 ASCII码0x32 0x30 0x20 0x33 0x30,可以使用 getchar() 函数来进行验证。

  既然数据是以字符流的形式被读取,那么读取到的应该是字符,为什么像 std::cin 或能 scanf() 直接读取数值存储到变量中呢?

----------------------------
//包含头文件
#include <stdio.h>
#include <iostream>
----------------------------

int a, b;
//C语言 标准输入
scanf("%d %d", &a, &b);

//C++ 标准输入
std::cin >> a >> b;

  这是因为标准输入对从字符流中读取到的内容进行了解析,根据变量类型将字符串转成对应的数值,并存储到变量中。

  例如:对于字符串 "1234",如果想要解析成数值:
  当以十进制数解析时,只需要在每接收到一个字符时,原累计值 乘以基数10 并加上字符对应的数值(与字符'0' 的ASCII码差值)即可,结果为 123 4 ( 10 ) 1234_{(10)} 1234(10) ( ( 1 ∗ 10 + 2 ) ∗ 10 + 3 ) ∗ 10 + 4 = 1234 \mathrm{((1 * 10 + 2) * 10 + 3) *10 + 4 = 1234} ((110+2)10+3)10+4=1234  当以十六进制数解析时,只需改成乘以基数16即可,结果为 123 4 ( 16 ) 1234_{(16)} 1234(16) ,即十进制的 4660 4660 4660 ( ( 1 ∗ 16 + 2 ) ∗ 16 + 3 ) ∗ 16 + 4 = 4660 \mathrm{((1 * 16 + 2) * 16 + 3) * 16 + 4 = 4660} ((116+2)16+3)16+4=4660

  总之,在算法竞赛中,数据的输入是以字符流的形式,而不是以字节流的形式,数值读取是对字符序列进行解析的结果。如果字符序列中内容与数据类型的格式不符,可能就会造成读取错误,与期望的结果相悖。

2.2 基本数据类型

   C++ 的基本数据类型有以下几种,数据类型所占的字节大小与平台有关。下面列出的各数据类型大小是目前在32位和64位平台上数据类型的通常实现。

对于各平台上数据类型 实际所占的字节数,建议使用 sizeof 关键字来确定: sizeof(type)

2.2.1 整数类型

各整数类型的范围被定义在 <limits.h> 头文件中。

  C++标准实际上只确保各 int 相关类型的最小长度,而并没有确切地规定各类型的长度。所以下面类型所占的字节大小只是各32位, 64位平台的通常实现,在算法竞赛中基本按照下表来认定。

基本数据类型大小最小值最大值字面量表示
char8 位 − 128 -128 128 127 127 127‘\xFF’
unsigned char8 位 0 0 0 255 255 255
short16 位 − 32768 -32768 32768 32767 32767 32767
unsigned short16 位 0 0 0 65535 65535 65535
int32 位 − 2147483648 -2147483648 2147483648
(即: − 2 31 -2^{31} 231)
2147483647 2147483647 2147483647
(即: 2 31 − 1 2^{31}-1 2311)
123
unsigned int32 位 0 0 0 4294967295 4294967295 4294967295
(即 2 32 − 1 2^{32}-1 2321)
123U
long32 位 − 2147483648 -2147483648 2147483648
(即: − 2 31 -2^{31} 231)
2147483647 2147483647 2147483647
(即: 2 31 − 1 2^{31}-1 2311)
123L
unsigned long32 位 0 0 0 4294967295 4294967295 4294967295
(即 2 32 − 1 2^{32}-1 2321)
123UL
long long64 位 − 2 63 -2^{63} 263 2 63 − 1 2^{63}-1 2631123LL
123i64(MSVC)
unsigned long long64 位 0 0 0 2 64 − 1 2^{64}-1 2641123ULL
123Ui64(MSVC)

  鉴于上面整数类型位数的不确定性,C99 和 C++11 标准还在<stdint.h>头文件中另外定义了一些确保位数的整数类型:

符号8位16位32位64位
有符号int8_tint16_tint32_tint64_t
无符号uint8_tuint16_tuint32_tuint64_t

2.2.2 浮点类型

浮点类型的数值范围定义在 <float.h> 头文件中。

  对于浮点类型的来说,float型尾数有24位,可以精确表示二进制24位以内的整数,当有效数字超过这个值时,会因为位数不足而造成误差,低位将被近似舍去
  对于十进制小数来说,大部分是无法用二进制小数精确表示的,所以就会有一个误差,通过增加浮点数的位数能提高精度,减小误差。
  下面则是常用的两个浮点类型: floatdouble

基本数据类型大小精度(二进制)精度(十进制)最小值最大值
float32 位24位6 ~ 7 位1.175494351e-38F3.402823466e+38F
double64 位52位15 ~ 16 位2.2250738585072014e-3081.7976931348623158e+308

   long double 是在 C99 引入的,且标准里并没有规定 long double 的位数,只要求不小于double, 如同intlong 的关系。在 MSVC 中,long doubledouble 是一样的。

基本数据类型
大小 (字节)
long double   long double 型主要依赖各编译器的实现,比如在MSVC中,long double 和 double一致,大小为8字节。
  而在GCC中,实现也各不相同,有 10字节、12字节 和 16字节,所以大小需要实际测试。

2.2.3 布尔型

  布尔型只有 falsetrue 两个值,用于逻辑判断。

基本数据类型名称大小 (字节)大小
bool布尔型未指定(通常为1字节)未指定(通常为8位)falsetrue

2.3 变量类型的选取

  数值小于 1 0 8 10^{8} 108 的整数可以用 int 型读取,当数值大于 1 0 8 10^{8} 108 时,考虑使用 long long 或者 unsigned int,如果数值非常大,无法用基本数据类型来存储,则考虑使用 大数运算

  浮点类型通常使用 double ,如果精度要求不高则可以使用 float, 当需要更高精度时可以使用 long double

  同时,double 类型的精度有52位,比 int 高, int 可以转换成 double 而不丢失精度。float 则不可以, float 的精度只有24位,比 int 小,int 数值过大时容易丢失精度。

  字符型数据可以使用 char 型,通常输入的是一个字符串,此时使用 char 型数组存储。

2.4 变量的输入

cppfeference 中 scanf()说明

  当使用 C 中的 scanf() 来进行输入时,需要使用格式说明符来说明变量的类型,并传入变量的地址。

在这里插入图片描述

  对于 C++ 中的 std::cin ,由于本身是面向对象的,所以可以自动根据变量类型进行解析。当设定输入的值为十六进制,十进制和八进制时,可以使用I/O操纵符

cppreference : I/O操纵符

如:std::hex, std::dec, std::oct,示例如下:

int a, b, c;
//第1个数为 十六进制格式,第2个为 八进制格式, 第3个数为 十进制
std::cin >> std::hex >> a >> std::oct >> b >> std::dec >> c;

2.4.1 scanf()的返回值

  scanf() 函数的返回值是正确读取并赋值的变量个数。特别的,当因读取到文件末尾而无法读取到变量时,不再返回0,而是返回 EOF(-1)

int a, b;

while (scanf("%d %d", &a, &b) != 2) {
}

2.4.2 C++ cin 转bool

  C++中的 std::cin 转成bool 时,如果读取时出现错误,就会返回 false ,如读取到文件末尾、输入与变量类型不匹配等。可以借此来判断是否读取到文件末尾。

当读取出现错误后,后续读取将无效,需要做一些清除错误的操作才能继续读取。

int a, b;

while (std::cin >> a >> b) {
}

3. 输入格式

  题目输入的数据通常以空格和换行隔开,这些数据有着不同的排列格式,需要按照不同的方式去读取。

在输入中,规范的格式是:

  • 行内的数据用一个空格隔开
  • 每一行末尾都有一个换行符

3.1 输入数据个数固定

  对于有着固定数量的输入:

在这里插入图片描述

输入样例

1 2 3 4 1 \quad 2 \quad 3 \quad 4 1234

(1) 可以定义同样数量的变量或数组,在格式字符串中按顺序指定。

int a, b, c, d;
scanf("%d %d %d %d", &a, &b, &c, &d);

(2) 定义同样长度的数组,根据数据个数循环读取

//定义数组
const int N = 4;
int a[4];

//循环读取
for (int i = 0; i < N; i++) {
	scanf("%d", &a[i]);
}

3.2 指定行内数据个数

  输入首先给出 数据个数 n n n, 然后给出 n n n 个数据: a 0 , a 1 , a 2 , a 3 , ⋯   , a n − 1 a_{0}, a_{1}, a_{2}, a_{3}, \cdots, a_{n-1} a0,a1,a2,a3,,an1

在这里插入图片描述

输入样例

6 6 6
20 30 0 12 99 54 20\quad30 \quad 0 \quad 12 \quad 99 \quad 54 20300129954

(1) 如果题目中给出的 n n n 的范围较小,假定不大于 N N N,则可以先配一个可以容纳N个元素的数组。

  1. 定义变量 n 和数组 a[N]
  2. 先读取 n n n 到变量 n,然后循环 n n n 次将 n n n 个输入读取到数组a[]中。

int a[N];

//读取 n
int n;
scanf("%d", &n);

//读取n个输入到数组中
for (int i = 0; i < n; i++) {
	scanf("%d", &a[i]);
} 

(2) 如果题目中给出的 n n n 的范围较大则可以使用动态分配。

  1. 定义变量 n 并读取数值 n n n
  2. 动态分配一个长度为 n n n 的数组,并循环 n n n 次读取输入到数组中。
  3. 使用结束后销毁数组
int n;
scanf("%d", &n);

//动态分配
int* a = new int[n];

//读取n个输入到数组中
for (int i = 0; i < n; i++) {
	scanf("%d", &a[i]);
}

...

//使用结束后销毁数组
delete[] a;

3.3 行内数据数量任意

  输入中并未给出一行的输入数量,规定了最大长度或没有规定。
在这里插入图片描述

输入样例

20 30 0 12 99 54 20\quad30 \quad 0 \quad 12 \quad 99 \quad 54 20300129954

这种形式的数据一般以两种方式处理:

  • 一种是直接读取,检测到换行符则停止;
  • 另一种是先读取一行内容,把字符串存储到字符数组中,然后再对字符串读取(或者将字符串按空格分割后再读取)。

3.3.1 直接读取,检测到换行符'\n'后停止

  数据之后紧邻着的是一个 空格 或者一个 换行符'\n',标准输入在读取数值时会自动跳过空格。在读取时可以将数据后的一个字符读出,当检测到是换行符时停止。

这种做法要求输入格式规范,如果换行符 之前存在空格,检测将失效。

  • 如果题目规定了最大长度,并且最大长度较小,可以直接分配一个大小为 m a x l e n maxlen maxlen 的数组。
  • 如果数据量范围较大且最大长度较大,则可以采用动态分配方式,当数组长度达到最大容量时使用 realloc() 重新分配一个更大的数组。
  • 或者使用 C++标准库 中可以自行增长的 vector

① 直接开辟可容纳最大数据长度的数组

int a[128];
int length = 0;

while (true) {
	//赋初值,防止被ch没读取成功的情况干扰
	char ch = '\0';
	int readVarNum;

	//读取数值到变量中,并将数据后的一个字符赋给ch,用于检测是否换行
	readVarNum = scanf("%d%c", &a[length], &ch);

	//判断变量是否读取成功
	if (readVarNum >= 1) {
		length++;
	}

	//读取不到足够的变量,或者间隔符非空格则停止读取读取
	if ((readVarNum != 2) || (ch != ' '))
		break;
}

② 动态分配,自动扩充

#include <stdio.h>
#include <stdlib.h>

int maxLen = 8;
int* a = (int*)malloc(maxLen * sizeof(int));
int length = 0;

while (true) {
	//赋初值,防止被ch没读取成功的情况干扰
	char ch = '\0';
	int readVarNum;
	int temp;

	//读取数值到变量中,并将数据后的一个字符赋给ch,用于检测是否换行
	readVarNum = scanf("%x%c", &temp, &ch);

	//判断变量是否读取成功
	if (readVarNum >= 1) {
		a[length++] = temp;

		//这里扩充分配的内存大小
		//如果使用vector<int>的话,直接push_back()即可,内部会自动扩充
		//这里则使用普通的数组作为示例
		if (length >= maxLen) {
			maxLen = 2 * maxLen;
			int* a = (int *) realloc(a, maxLen * sizeof(int));
		}
	}

	//读取不到足够的变量,或者间隔符非空格则停止读取读取
	if ((readVarNum != 2) || (ch != ' '))
		break;
}

② 使用Vector自动管理

std::vector<int> vec;

while (true) {
	//赋初值,防止被ch没读取成功的情况干扰
	char ch = '\0';
	int readVarNum;
	int inData;
	
	//读取数值到变量中,并将数据后的一个字符赋给ch,用于检测是否换行
	readVarNum = scanf("%d%c", &inData, &ch);
	vec.push_back(inData);

	//读取不到足够的变量,或者间隔符非空格则停止读取读取
	if ((readVarNum != 2) || (ch != ' '))
		break;
}

3.3.2 先读取一行,再对字符串进行处理

  在读取一整行后,可以使用相关函数对字符串进行分割或者直接读取。

3.3.2.1 读取一整行

  读取一整行可以使用 C 中的 fgets(), 或者C++中的std::cin.getline()getline() 函数。
  如果使用 char 数组来保存字符串,那么需要提前分配好内存,最好在正常情况下可以容纳一整行的字符,否则就要写比较多的相关处理代码。

C式写法:

#include <stdio.h>

char str[1024];
if (fgets(str, 1024, stdin) == NULL) {
	读取失败
}

  可以通过检测字符串 str 的最后一个字符是否为 '\n'来判断是否读取完了一整行。如果返回值ptrBuff为空指针,说明读取失败,这种情况下 str 的内容不会被改变。

C++式写法1:

#include <string>
#inclde <iostream>

std::string line;
if (!std::getline(std::cin, line)) {
	读取失败
}

C++式写法2:

#include <string>
#inclde <iostream>

char str[1024];
if (!std::cin.getline(str, 1024)) {
	读取失败
}
3.3.2.2 从字符串中读取数据
3.3.2.2.1 stringstream

  C++可以使用 stringstreamstring 创建一个流,然后从流中读取数据到变量中。

#include <string>
#include <sstream>
#include <vector>

//假设line存储了一整行的字符
std::string line; 

std::stringstream strstream(line);

//此时stringstream可以和cin类似的操作
int x;
vector<int> vec;

while (strstream >> x) {
	vec.push_back(x);
}
3.3.2.2.2 strtok() 分割字符串

  C中的 strtok() 函数可以对函数进行分割,函数原型如下,str 为要进行分割的字符串 (需要允许被修改,不能是const)delimiter是由分割字符串的分隔符组成的字符串 (一个或多个)

#include <string.h>
char* strtok( char* str, const char* delimiter);

  如果字符串的分隔符之间没有其它字符,即空字符串,会被跳过,不会返回。如"1, , , , 2"" ,"(空格和逗号)分割时,只会返回子串 "1" 和子串 "2"

先看一个简略的示例:

//传入要分割的字符串,以及分隔符,得到分割出的第一个子串
char* subStr = strtok(str, " ");

//循环直到返回NULL
while (subStr != NULL) {
	//传入NULL,继续分割出下一个子串
	subStr = strtok(NULL, " ");
}

下面是使用方法:

  1. 第一次调用传入要分割的字符串 str字符串内容需要允许被修改,因为strtok()会将字符串中的分隔符改成'\0'。返回的则是分割后的第一个子串。
char* subStr = strtok(str, " ,;");
  1. 后续调用,字符串传入NULL,因为内部已经保存有之前字符串的地址了,传入NULL表示继续分割。
char* subStr = strtok(NULL, " ,;");
  1. 当返回的子串 subStr == NULL时,表示已经分割完毕。

  因为数据之间是用空格分隔的,所以分隔符使用" "就好,如果 空格逗号,分号; 都有,那么分隔符可以加三个字符 " ,;"
  下面是实际的操作,假设数据已经被读入 str[] 中。

#include <string.h>

char str[1024];
...//读取一整行数据

int a[128];
int length = 0;

//第一次分隔,传入字符串str和分隔符集合
char* subStr = strtok(str, " ");

//分割出的字符不为`NULL`则说明没有分割完成
while (subStr != NULL) {
	//在保证输入的数据无误的情况下,可以直接从子串读
	//否则还得通过sscanf()的返回值判断一下是否读取成功
	sscanf(subStr, "%d", &a[length++]);
	
	//继续分割
	subStr = strtok(NULL, " ");
}

  经过 strtok() 分割后,字符串 str 中每个子串后面的第一个分隔符会变成'\0',如果分隔符是连续的,这些连续的分隔符中只有第一个会被修改成'\0'

如: "1, ,2,,,3"" ,"分割后会变成 "1\0 ,2\0,,3"

3.3.2.2.3 利用字符查找

 还有其它一些通过字符查找操作来确定数据位置,从而进行读取。



4. C++ iostream提速

  C++ 中 的 iostream 在实际使用时会比 C 中的 stdio 慢一些,这在有些需要大量读写且时间限制得很紧的题目中可能会成为瓶颈。

  在需要提速时,可以使用以下代码进行提速,即取消 iostream 与 studio的同步,以及解除输入输出流的关联。

#include <iostream>

std::ios::sync_with_stdio(false);
std::cin.tie(NULL);

能提速多少?
  这和编译器有关,简单地在MSVC 和 GCC上进行100000个数据的读取测试,得到粗略的时间数据:

MSVC:cin 耗时GCC:cin 耗时
默认50 ms115 ms
取消同步50 ms20 ms
取消同步并解除关联43 ms20 ms
对比scanf() 耗时19 ms63 ms

在这里插入图片描述

  可以看到,GCC上 cin 经过两个步骤后,提速十分明显,速度甚至比 scanf() 快得多,而在MSVC上,则达不到 scanf()的速度。

4.1 取消iostream与stdio的同步

  C++ 中 的 iostream 为了确保能和 C 中的 stdio在同一文件操作时按顺序进行,而进行了一些额外的同步操作,这些操作会对性能造成一些影响,可以使用 std::ios::sync_with_stdio(false)取消同步。
  取消同步后,性能可能会得到提升,但之后不应再与 stdio 混用,否则读写顺序上不能保证。

#include <iostream>

//取消iostream对stdio的同步
std::ios::sync_with_stdio(false);

4.2 解除输入输出流的关联

  C++ 中的 输入流 std::cin 默认是关联到 输出流 std::cout 的,任何试图从输入流 读取数据的操作都会先刷新关联的 输出流,这样输出流的缓冲区就会在输入前得到刷新。

  关联输入输出流意味着所有输出(包括用户提示等)都会在输入前被打印出来,而不会有在进行输入时之前调用的输出没有得到显示的情况发生。

  可以通过使用 cin.tie()解除输入输出流的关联来提升性能。

#include <iostream>

//解除cin 与 cout的关联
std::cin.tie(NULL);

  可以使用std::cin.tie() 重新关联。

#include <iostream>

//关联 cin 与 cout
std::cin.tie(&std::cout);

  在解除关联之后,如果想刷新输出流的缓冲区,可以通过调用 std::cout << std::flush 来手动刷新,以确保输出流中的内容在输入前显示。

std::cout << "output text." << std::flush;

  flush 会影响性能,不应过多使用。

5. 输入输出重定向

  之前提到,我们之前明明使用的是标准输入输出,应该是在控制台进行输入输出的,怎么在OJ平台上就变成了对文件进行读取写入呢?
  原因是OJ平台在执行我们的程序时,对程序的输入输出进行了重定向。

5.1 输入输出重定向符

  在Shell脚本中,可以使用符号: >>>对输出内容进行重定向,将内容输出到文件中,这两个符号的区别是>>不会将文件原有的内容清空,是通过追加的方式进行写入,而>则是会清空原有内容。如果文件不存在,那么会自动创建。
  符号< 则可以对输入进行重定向,由文件进行输入。

输入重定向符作用
<将输入重定向到文件
输出重定向符作用备注
>将输入重定向到文件,清空原内容如果文件不存在,则自动创建
>>将输入重定向到文件,在原文件末尾追加内容如果文件不存在,则自动创建

命令示例:
  将程序program.exe的输入重定向到 input.txt 文件,输出重定向到 output.txt

program <input.txt >output.txt

  这里我们编写了一个对两个数进行求和的程序:

#include <stdio.h>

int main()
{
	int a, b;
	printf("请输入两个数:");
	scanf("%d %d", &a, &b);
	printf("两数之和为:%d\n", a + b);
	return 0;
}

  假设编译出的程序名为 twoSum.exe,,那么在命令行下,我们可以跳转到twoSum.exe所在的目录,调用命令twoSum.exe 或者./twoSum.exe来执行程序(命令中的.exe可以省略)
  命令执行过程如下图所示:
在这里插入图片描述
  现在我们将输入输出分别重定向到 input.txt 和 output.txt 文件中。
  首先创建文件 input.txt,输入内容:10 20,并保存。
在这里插入图片描述
  然后执行以下命令:

twoSum.exe <input.txt >output.txt

  可以看到,程序虽然执行了,但是命令行上并没有任何输出。
在这里插入图片描述
  但是我们打开文件夹,可以看到多了个 output.txt 文件。
在这里插入图片描述
  打开 output.txt 文件可以看到这就是本来要输出到命令行上的内容。
在这里插入图片描述
  再重复执行一次 twoSum.exe <input.txt >output.txt 命令,可以看到 output.txt 中的内容并没有改变。

  这次我们将输出重定向符改为 >>,再执行一次命令:

twoSum.exe <input.txt >>output.txt

  可以看到这时输出是追加到文件末尾的,而不会清空。
在这里插入图片描述

  

5.2 使用freopen()函数重定向

   重定向符是在命令行下使用,如果我们想在代码中进行控制,就需要使用其它方法了。
   头文件<stdio.h> 中 的 freopen() 函数可以将标准输入 stdin 和输出 stdout 重定向到文件中。

输出重定向的文件如果不存在,会自动创建。

//标准输入重定向到 input.txt 文件中
freopen("input.txt", "r", stdin);

//标准输出重定向到 output.txt 文件中,会清空原文件内容
freopen("output.txt", "w", stdout);

   如果不想清空原文件的内容,可以将输出文件的模式改为追加:

//标准输出重定向到 output.txt 文件中,内容追加到文件末尾
freopen("output.txt", "a", stdout);

  可以多次调用 freopen() 来重定向输入输出:

int a, b;
reopen("input.txt", "r", stdin);

//先将输出重定向到 out.txt文件
freopen("out.txt", "w", stdout);
printf("请输入两个数:");
scanf("%d %d", &a, &b);

//再将输出重定向到 output.txt文件
freopen("output.txt", "w", stdout);
printf("两数之和为:%d\n", a + b);

  同时,freopen()的返回值类型为 FILE* 可以获取打开的文件,如果打开失败,返回 NULL。

FILE* inFile  = freopen("input.txt", "r", stdin);
FILE* outFile = freopen("output.txt", "w", stdout);

重定向回控制台
  重定向到文件,可能想要转回控制台,Windows 下可以使用以下代码,文件名使用 "CON" (Linux为 "/dev/tty")

freopen("CON", "r", stdin);
freopen("CON", "a", stdout);

安全函数 freopen_s()

  freopen_s() 的返回值不再是 FILE* 类型,而是一个错误码 errno_t,FILE*类型则转移到了参数上,不过改成了它的指针。打开成功返回的码为0,打开错误则返回非0值。

FILE* inFile;
FILE* outFile;
errno_t errno_in  = freopen_s(&inFile, "input.txt", "r", stdin);
errno_t errno_out = freopen_s(&outFile, "output.txt", "w", stdout);

5.3 C++流重定向

  C++的流可以使用成员函数 rdbuf() 进行重定向,文件可以使用 std::ifstream 打开

#include <iostream>
#include <fstream>

//只读模式打开 intput.txt 文件
std::ifstream fin("input.txt", std::ifstream::in);
//以写模式打开output.txt
std::ofstream fout("output.txt", std::ifstream::out);
//用 rdbuf() 重定向输入,保存cin之前的流
std::streambuf* cinbackup = std::cin.rdbuf(fin.rdbuf());
//用 rdbuf() 重定向输出,保存cout之前的流
std::streambuf* coutbackup = std::cout.rdbuf(fout.rdbuf());

重定向回控制台
  成员函数rdbuf()返回值为之前的流,保存后返回值后,可以再次使用rdbuf()进行恢复。

#include <iostream>
#include <fstream>

//打开文件
std::ifstream fin("input.txt", std::ifstream::in);
std::ofstream fout("output.txt", std::ifstream::out);

//保存标准输入输出之前的流
std::streambuf* cinbackup = std::cin.rdbuf(fin.rdbuf());
std::streambuf* coutbackup = std::cout.rdbuf(fout.rdbuf());

//重定向回控制台:
std::cin.rdbuf(cinbackup);
std::cout.rdbuf(coutbackup);

专栏:算法竞赛

上一篇:算法竞赛:Online Judge介绍

下一篇:

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

依稀_yixy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值