Skip to content

C的输入输出

本文不讨论流,文件,错误等,也不讨论他们的原理和特性。仅仅针对标准输入输出流,总结一些常见的函数,尤其是在算法竞赛中常用的几个。

概述

相对于C,C++库提供的IO函数更多,更复杂,但也更加智能。

字符IO:最基本的IO

C

C语言中getchar用于读取一个字符,putchar用于把单个字符打印。

C++
int getchar(void);
int putchar(int character);

他们都返回被转为int的字符。如果任何原因导致函数失败,他们返回EOF

C++

而在C++中,cin类的成员函数get可以提供单字符读取:

C++
int cin.get(void);
istream& cin.get(char& character);

无参的cin.get,将输入字符转换为int型返回,为EOF表示读取失败;

带单个参数的cin,get,读取下一个字符赋值给参数,并返回一个流对象,它可以转换为bool值用于判断读入是否成功,也就是说可以套while(cin.get(ch))。

如果是读取并丢弃换行之类的操作,用无参的cin.get(),如果读取的字符有用,就用含参的。

输出:

c++
ostream& cout.put(char ch);

用于输出单个字符。没什么用。

字符串IO:未格式化的行IO

可以认为IO函数每次开始处理读入都发生在遇到\n,回车符的时候。以\n结尾的字符串的字符,一个个被送给IO函数处理。所以叫做行IO。

行IO分为两种,未格式化和格式化的。说人话就是,在读入的时候进不进行类型校验。前者未经格式化,读进来就是一个字符串了,后者则可以在读入的时候判断类型,比如一个整数,一个浮点数等。下面介绍字符串类型,即直接读入的行IO。

C

读入:

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的区别在于,它读取了换行,把它丢弃掉而非存入结果中

输出:

C++
int fputs(char *buffer);
int puts(char *buffer);

他们两个也几乎没有区别。他们输出一个字符数组,并且要求这个字符数组以\0结尾——它必须存的是一个字符串。如果不符合条件返回一个EOF

他们的区别在于,puts会在末尾自动的再输出一个\n换行。

C++

C++的处理比较智能。

输入:

C++
istream& cin.get(char *buff, int buff_size);
istream& cin.getline(char *buff, int buff_size);

和C中一样,他们在换行的时候开始读入一个字符串到字符数组中。和C不同的一点在于对于换行符的处理:

  • 他们两个都在遇到换行的时候停止读入,并且都不把那个换行符写到目标字符数组中
  • cin.get会在遇到换行的时候什么都不做,直接终止函数,即换行还在输入中,等待被下一次读取并处理。
  • cin.getline会在遇到换行的时候抽取掉它并丢弃,然后直接终止函数。即换行被丢弃了。 这就比较智能。
C++
istream& getline(istream& is, string& str);

C++的为string单独实现了一个字符串读取。默认和上面也一样,string的getline遇到换行符停止,并且默认是读取并丢弃换行符

输出:

C++的输出基本上就只有cout常用了。

小结:

可以看到,除了C中的一个fgets()把换行存入目标数组中外,其他的函数全都选择不存入目标数组。而在这些函数当中,cin.getline(char*)getline(string)不仅不存换行,而且还会把换行读取并默默处理掉,让下次读取不以换行为开头。

格式化行IO

常见的输入输出

C++
//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
C++
scanf, printf; // 常见输入与输出语句
getchar();  // 用于读取并丢弃换行
puts("-1"); // 用于在输出-1并换行
  • C++
C++
cin cout; // 常见输入和输出语句
cin.get(); // 用于读取并丢弃换行
cin.getline(char *); //用于读取一个字符串到字符数组(不包括换行),并丢弃换行
getline(cin, s); //用于读取一个字符串到string类(不包括换行),并丢弃换行

快读函数

c++
#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;
}