C++输入输出详解

任何一个工具,它的一个最重要的、同时也是最难以做到的方面就是对那些学习使用这个工具的人在使用习惯上的影响。如果这个工具是一种编程语言,那么,这种影响——不管我们是否喜欢——将是一种思考习惯上的影响。          ——  Edsger Dijkstra, 计算机科学家

目录

目录

目录

前言

一、输入输出方法

1.cin与cout

2.scanf与printf

二、输入输出优化

1.快读

快读函数的实现

2.快写

快写函数的实现:

3.快读/快写综合应用

4.更高级的读写优化

2)fread和fwrite

3)使输入输出变得更通用

总结

参考


前言

C++是一个运用广泛的面向对象语言,本文梳理了C++中输入输出的基本语法以及关于输入输出相关知识的总结与分析,并对输入输出的多种优化进行进一步的探索。


一、输入输出方法

1.cin与cout

cin与cout是C++编程语言中的标准输入输出流对象,即 istream/ostream 类的对象。cin主要用于从标准输入读取数据,cout则用于向标准输出输出数据。

在C++中,输入输出流均被定义为类,C++的I/O库中的类为流类,其定义的对象称作流对象。表示流的符号是 << (插入运算符)和 >>(提取运算符)。 代码如下:

#include 
using namespace std;

int main() {
    int x, y;                          // 声明变量
    cin >> x >> y;                     // 读入 x 和 y
    cout << y << endl << x;            // 输出 y,换行,再输出 x
    return 0;                          // 结束主函数
}

cin读取数据是从缓冲区中获取数据。当缓冲区为空时,cin的成员函数会阻塞等待数据的到来,一旦缓冲区中有数据,就触发cin的成员函数去读取数据。

cin的条件状态标识符号:

goodbit          //无错误
eofbit           //已到达文件尾
failbit          //非致命的输入/输出错误
badbit           //致命的输入/输出错误


设置、读取和判断条件状态的流对象的成员函数:

s.eof()            //若流s的eofbit置位,则返回true;
s.fail()           //若流s的failbit置位,则返回true;
s.bad()            //若流s的badbit置位,则返回true;
s.good()           //若流s的goodbit置位,则返回true;
s.clear(flags)     //清空状态标志位,并将给定的标志位flags置为1,返回void。
s.setstate(flags)  //根据给定的flags条件状态标志位,将流s中对应的条件状态位置为1,返回void。
s.rdstate()        //返回流s的当前条件状态,返回值类型为strm::iostate。

清空缓冲区:

//清除输入缓冲区的四种方法

//1
fflush(stdin);

//2
cin.sync();

//3
cin.ignore(std::numeric_limits< streamsize >::max(), '\n');

//4
char c;
while ((c = getchar()) != '\n');

2.scanf与printf

\text{scanf}printf 其实是 C 语言提供的函数。大多数情况下,它们的速度比  cincout 更快,并且能够更方便地控制输入输出格式。

#include                       // C standard input output
using namespace std;

int main() {
    int x, y;                          // 声明变量
    scanf("%d %d", &x, &y);            // 读入 x 和 y
    printf("%d\n%d", y, x);            // 输出 y,换行,再输出 x
    return 0;                          // 结束主函数
}

格式:

scanf("<格式化字符串>", <参量表>);
转换格式为:%[*][宽度][类型长度]类型
例如:scanf("%2d", &x);          // 读入2位整数x

printf("<格式化字符串>", <参量表>);
转换格式为:%[标志][宽度][.精度][类型长度]类型
例如:printf("%.6lf", %x);       // 输出双精度浮点数(保留6位小数)

转换说明符:

转换说明符 意义 转换说明符 意义
%d/%i 有符号十进制32位整数 %lld 有符号十进制64位整数
%o 无符号八进制整数 %u 无符号十进制整数
%c 一个字符 %s 字符串
%e 浮点数,e-计数法 %% 打印一个百分号
%E 浮点数,E-计数法 %g 根据数值选择%f或%e
%f 单精度浮点数 %G 根据数值选择%f或%E
%lf 双精度浮点数 %llu 无符号十进制64位整数
%x 使用16进制数字0f的无符号16进制整数 %X 使用16进制数字0F的无符号16进制整数

二、输入输出优化

在默认情况下, cin/cout 是极为迟缓的读入/输出方式,而 \text{scanf}/printfcin/cout 快得多。通常而言,对于1e7的数据,cin的读入需要约6.4s,而 \text{scanf}却只需要1.2s。这就充分体现了 \text{scanf}的优势。那么,为什么  cin/cout 的速度如此之慢呢?原因是这样:cin/cout 是默认与 stdin 和 stdout 同步的,且需要判定许多条件,所以时间较长。我们可以使用函数关掉同步提速。

ios::sync_with_stdio(false);              //不兼容 stdio
cin.tie(nullptr);cout.tie(nullptr);       //解除 cin 与 cout 的绑定

使用上面代码优化后的   cin/cout  效率基本与 \text{scanf}/printf 相当,但要注意禁用同步是CSP/NOI系列算法竞赛中是不能使用的。尽管如此,在算法竞赛中,很多时候光是输入输出就有可能会超时,所以我们还需要一种比  \text{scanf}/printf 还要快的方法。这就是我们重点要讲的内容了:快读/快写

1.快读

众所周知,getchar 是用来读入 1 byte 的数据并将其转换为 char 类型的函数,且速度很快,故可以用“读入字符并转换为整型”来代替较为缓慢的读入。

每个整数由符号和数字两部分组成,+ 号通常是省略的,且不会对后面数字所代表的值产生影响,而 '-' 号不可省略,因此要进行判定。

10 进制整数中是不含空格或除 0~9 和正负号外的其他字符的,因此在读入中不应存在于整数中的字符(通常为空格或换行符)时,就可以判定已经读入结束。

C++ 语言分别在 cctype 头文件中,提供了函数 isdigit(当然 bits/stdc++.h 万能头也有), 这个函数会检查传入的参数是否为十进制数字字符,是则返回 true,否则返回 false。对应的,在下面的代码中,可以使用 isdigit(ch) 代替 ch >= '0' && ch <= '9',而可以使用 !isdigit(ch) 代替 ch <'0' || ch> '9'。

快读函数的实现

inline int read() {
    int x = 0, w = 1;                     //x是要读的数,w表示符号
    char ch = 0;
    while (ch < '0' || ch > '9') {        // ch 不是数字时
        if (ch == '-') w = -1;            // 判断是否为负
        ch = getchar();                   // 继续读入
    }
    while (ch >= '0' && ch <= '9') {      // ch 是数字时
        x = x * 10 + (ch - '0');          // 将新读入的数字’加’在 x 的后面

        /* x 是 int 类型,char 类型的 ch 和 ’0’ 会被自动转为其对应的
        ASCII 码,相当于将 ch 转化为对应数字
        此处也可以使用 (x<<3)+(x<<1) 的写法来代替 x*10 */

        ch = getchar();                   // 继续读入
    }
    return x * w;                         // 数字 * 正负号 = 实际数值
}

2.快写

同样众所周知的是,putchar 是用来输出单个字符的函数,速度较快。因此将数字的每一位转化为字符输出以加速。要注意的是,负号要单独判断输出,并且每次 %mod 取出的是数字末位,因此要倒序输出。

快写函数的实现:

方法一(递归写法,常数交大):

inline void write(int x) {
    if (x < 0) {               // 判负 + 输出负号 + 变原数为正数
        x = -x;
        putchar('-');
    }
    if (x > 9) write(x / 10);  // 递归,将除最后一位外的其他部分放到递归中输出
    putchar(x % 10 + '0');     // 已经输出(递归)完 x 末位前的所有数字,输出末位
    return;
}

方法二(栈模拟,推荐使用):

inline void write(int x) {
    static int sta[35];                    //如果是long long应改为sta[65]
    int top = 0;
    if(x<0){
		putchar('-');	
		x = -x;
	}
    do {
        sta[top++] = x % 10, x /= 10;
    } while (x);
    while (top) putchar(sta[--top] + '0');
}

3.快读/快写综合应用

 以下是快读&快写的实例:

#include 
using namespace std;

const int N=1e6+5;
int n, a[N], b[N];                      // 声明变量

inline int read() {                     // read() 函数本身
    int x = 0, w = 1;
    char ch = 0;
    while (ch < '0' || ch > '9') {
        if (ch == '-') w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + (ch - '0');
        ch = getchar();
    }
    return x * w;
}

inline void write(int x) {              // write() 函数
    static int sta[35];
    int top = 0;
    if(x<0){
		putchar('-');	
		x = -x;
	}
    do {
        sta[top++] = x % 10, x /= 10;
    } while (x);
    while (top) putchar(sta[--top] + '0');
}

int main() {                       
    n = read();                  // 调用 read() 函数,读入n
    for(int i = 1;i <= n;i++){   // 从 1 循环到 n
        a[i] = read();           // 读入a[i], b[i]两个数
        b[i] = read();
    }
    sort(a+1, a+n+1);            // 对a,b进行升序排列
    sort(b+1, b+n+1);
    for(int i = 1;i <= n;i++){
        write(a[i]);             // 调用 write() 函数,输出 a[i]
        putchar(' ')             // 输出空格
        write(b[i]);             // 输出 b[i]
        putchar('\n');           // 换行
    }
    return 0;                    // 结束主函数
}

4.更高级的读写优化

1)将快读快写写进命名空间

#include
using namespace std;

namespace Fast_IO {                          //定义命名空间
	
	inline int read() {                     // read() 函数
	    int x = 0, w = 1;
	    char ch = 0;
	    while (ch < '0' || ch > '9') {
	        if (ch == '-') w = -1;
	        ch = getchar();
	    }
	    while (ch >= '0' && ch <= '9') {
	        x = x * 10 + (ch - '0');
	        ch = getchar();
	    }
	    return x * w;
	}
	
	inline void write(int x) {              // write() 函数
	    static int sta[35];
	    int top = 0;
        if(x<0){
		    putchar('-');	
		    x = abs(x);
	    }
	    do {
	        sta[top++] = x % 10, x /= 10;
	    } while (x);
	    while (top) putchar(sta[--top] + '0');
	}	
}
using namespace Fast_IO;                    //引用命名空间

int main(){
	int n=read();
	write(n);
}

2)fread和fwrite

通过 fread 可以实现更快的读入。

fread 能将需要的文件部分一次性读入内存缓冲区。一次性读入缓冲区的操作比逐个字符读入(getchar,putchar)又要快的多。因为硬盘的多次读写速度是要慢于直接读取内存的,所以先一次性读到缓存区里再从缓存区读入要快的多。

fread 类似于更为快速的参数为 "%s" 的 scanf,且可以一次性读入若干个字符(包括空格换行等制表符),如果缓存区足够大,甚至可以一次性读入整个文件。

对于输出,我们还有对应的 fwrite 函数。

std::size_t fread(void* buffer, std::size_t size, std::size_t count,
                  std::FILE* stream);
std::size_t fwrite(const void* buffer, std::size_t size, std::size_t count,
                   std::FILE* stream);

使用示例:

//从 stdin 文件流中读入 SIZE 个大小为 1 byte 的数据块到 Buf 中
fread(Buf, 1, SIZE, stdin);

参考代码:

namespace Fast_IO {
	const int MAXSIZE = 1 << 20;
	char buf[MAXSIZE], *p1, *p2;
	#define gc()                                                               \
	  (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin), p1 == p2) \
	       ? EOF                                                               \
	       : *p1++)
	
	inline int rd() {
        int x = 0, f = 1;
	    char c = gc();
	    while (!isdigit(c)) {
	        if (c == '-') f = -1;
	        c = gc();
	    }
	    while (isdigit(c)) x = x * 10 + (c ^ 48), c = gc();
	    return x * f;
	}
	
	char pbuf[1 << 20], *pp = pbuf;
	
	inline void push(const char &c) {
	    if (pp - pbuf == 1 << 20) fwrite(pbuf, 1, 1 << 20, stdout), pp = pbuf;
	    *pp++ = c;
	}
	
	inline void write(int x) {
	    static int sta[35];
	    int top = 0;
        if(x<0){
		    putchar('-');	
		    x = -x;
	    }
	    do {
	      sta[top++] = x % 10, x /= 10;
	    } while (x);
	    while (top) push(sta[--top] + '0');
	}
}
using namespace Fast_IO;                    //引用命名空间

3)使输入输出变得更通用

如果你的程序使用多个类型的变量,那么可能需要写多个输入输出优化的函数。下面给出的代码使用C++中的 template 实现了对于所有整数类型的输入输出优化。

template inline T read() {          //将read()函数定义为template型
	T x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
	    if (ch == '-') w = -1;
	    ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
	    x = x * 10 + (ch - '0');
	    ch = getchar();
	}
	return x * w;
}
	
template inline void write(int x) {
	static int sta[129];
	T top = 0;
	if(x<0){
		putchar('-');	
		x = abs(x);
	}
	do {
	    sta[top++] = x % 10, x /= 10;
	} while (x);
	while (top) putchar(sta[--top] + '0');
}

使用方法:

T num=read();               //快读:read,T为变量类型
//eg
long long n=read(); //读入一个64位整数n

write(n);                   //快写:write,T为变量类型
//eg
write(n);           //读出一个64位整数n

总结

以上就是今天要讲的内容,本文梳理了C++中输入输出的基本语法以及关于输入输出相关知识的总结与分析,并对输入输出的多种优化进行进一步的探索。

参考

[oi-wiki] C++语法基础

[oi-wiki] 读入、输出优化

你可能感兴趣的:(C++,c++,算法,开发语言)