C的输入输出
本文不讨论流,文件,错误等,也不讨论他们的原理和特性。仅仅针对标准输入输出流,总结一些常见的函数,尤其是在算法竞赛中常用的几个。
概述
相对于C,C++库提供的IO函数更多,更复杂,但也更加智能。
字符IO:最基本的IO
C
C语言中getchar
用于读取一个字符,putchar
用于把单个字符打印。
int getchar(void);
int putchar(int character);
他们都返回被转为int的字符。如果任何原因导致函数失败,他们返回EOF
。
C++
而在C++中,cin
类的成员函数get
可以提供单字符读取:
int cin.get(void);
istream& cin.get(char& character);
无参的cin.get
,将输入字符转换为int
型返回,为EOF
表示读取失败;
带单个参数的cin,get
,读取下一个字符赋值给参数,并返回一个流对象,它可以转换为bool
值用于判断读入是否成功,也就是说可以套while(cin.get(ch))。
如果是读取并丢弃换行之类的操作,用无参的cin.get()
,如果读取的字符有用,就用含参的。
输出:
ostream& cout.put(char ch);
用于输出单个字符。没什么用。
字符串IO:未格式化的行IO
可以认为IO函数每次开始处理读入都发生在遇到\n
,回车符的时候。以\n
结尾的字符串的字符,一个个被送给IO函数处理。所以叫做行IO。
行IO分为两种,未格式化和格式化的。说人话就是,在读入的时候进不进行类型校验。前者未经格式化,读进来就是一个字符串了,后者则可以在读入的时候判断类型,比如一个整数,一个浮点数等。下面介绍字符串类型,即直接读入的行IO。
C
读入:
char *fgets(char *buff, int buff_size);
char *gets(char *buff); //弃用
fgets尝试读入整个字符串存进buff
字符数组。
它有几个特性:
- 如果读入过程中字符串长度太长(末尾的
\n
字符也算在内),超过了buff_size - 1
(-1是因为它要在末尾补上\0
,以表示字符串结尾),他将不会继续读取。剩下的字符在流中,等待下一次读取。 - 正如第一点提到的,为了保证字符数组存储的是一个字符串,所以无论如何都会保证在读入完成后加上空字符
\0
。 - 如果读入不为空(即至少有一个字符被读入了buff数组),那么函数返回一个指向buff的指针。否则返回一个空指针。由于这个特性,该函数的返回值只用于检查是否到达了文件尾部:只有在文件尾部才可能出现读入为空的情况。
gets函数和fgets几乎一模一样,只是传参不要求传入字符数组的长度了:
- gets由于不知道数组长度,就不会去检查读入是否越界了,所以读入长度可以大于字符数组的长度:这是很危险的。所以gets被不建议再被使用。这一点对于在算法竞赛中使用并没有什么负面影响,因为算法竞赛的输入数据范围都是约定好的。
- gets的一个很有用的、和fgets的区别在于,它读取了换行,把它丢弃掉而非存入结果中。
输出:
int fputs(char *buffer);
int puts(char *buffer);
他们两个也几乎没有区别。他们输出一个字符数组,并且要求这个字符数组以\0
结尾——它必须存的是一个字符串。如果不符合条件返回一个EOF
。
他们的区别在于,puts会在末尾自动的再输出一个\n
换行。
C++
C++的处理比较智能。
输入:
istream& cin.get(char *buff, int buff_size);
istream& cin.getline(char *buff, int buff_size);
和C中一样,他们在换行的时候开始读入一个字符串到字符数组中。和C不同的一点在于对于换行符的处理:
- 他们两个都在遇到换行的时候停止读入,并且都不把那个换行符写到目标字符数组中。
- cin.get会在遇到换行的时候什么都不做,直接终止函数,即换行还在输入中,等待被下一次读取并处理。
- cin.getline会在遇到换行的时候抽取掉它并丢弃,然后直接终止函数。即换行被丢弃了。 这就比较智能。
istream& getline(istream& is, string& str);
C++的为string单独实现了一个字符串读取。默认和上面也一样,string的getline遇到换行符停止,并且默认是读取并丢弃换行符。
输出:
C++的输出基本上就只有cout常用了。
小结:
可以看到,除了C中的一个fgets()把换行存入目标数组中外,其他的函数全都选择不存入目标数组。而在这些函数当中,cin.getline(char*)
和getline(string)
不仅不存换行,而且还会把换行读取并默默处理掉,让下次读取不以换行为开头。
格式化行IO
常见的输入输出
//c
int scanf(char const *format, ...);
int printf(char const *format, ...);
//c++
istream& operator>>(type&); // cin
ostream& operator<<(type&); // cout
他们可以说是最最常见的了。下面补充了几点细节:
- cin读入的类型如果是一个单字符,则不会跳过空白字符(如空格、回车、换行);其余情况均跳过空白字符,从第一个非空白字符开始读取,遇到空白字符“空格”、“TAB”、“回车”就结束。
- cin不会对换行进行任何处理——正如上面小结所说的,只有getline有处理换行的行为。
- scanf也不会对换行进行任何处理。下面是行为细节:
- scanf("%s"):该读取更基于获取单词(get world)而不是获取字符串(get string);scanf()是以第一个非空白字符开始读入的,遇到空白字符(如空格,回车)结束读入,并且不会读入空白字符。
结语
绝大多数的读入函数,都默认以非空白字符作为读入的开头(一个特例就是,读入单个字符的行为,它会读入任意字符。),以空白字符作为读取结束,并且大多数函数不会去读入这个空白结尾符(一个特例就是,fgets读入这个符号并存入结果,另一个特例是,cin.getline智能的抽取掉这个结尾符并丢弃)。
我的经验是,不要去混用cin和scanf。也就是说,不要去混用C库提供的函数和C++库提供的函数,输出还挺容易混用,这里尤其是针对于输入而言。因为两者的行为是不同的,前者设计更加底层,后者设计更加智能。我个人更喜欢用C++的读入,它更加智能,易上手易用。
算法竞赛
常见函数
- C
scanf, printf; // 常见输入与输出语句
getchar(); // 用于读取并丢弃换行
puts("-1"); // 用于在输出-1并换行
- C++
cin cout; // 常见输入和输出语句
cin.get(); // 用于读取并丢弃换行
cin.getline(char *); //用于读取一个字符串到字符数组(不包括换行),并丢弃换行
getline(cin, s); //用于读取一个字符串到string类(不包括换行),并丢弃换行
快读函数
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N = 1e6 + 10;
template<typename T>void in(T &x) {
char ch = getchar(); bool flag = 0; x = 0;
while (ch < '0' || ch > '9') flag |= (ch == '-'), ch = getchar();
while (ch <= '9' && ch >= '0') x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
if (flag) x = -x;
return;
}
int main() {
return 0;
}