通俗解释流的概念

作者: zsh2517 分类: 未分类 发布时间: 2020-04-02 17:46

通俗解释流的概念

0x00 写在前面

为什么要写这个?

Q: 你看看这个题,怎样把数据全都进去然后处理?
A: 为什么要全都读进去?
Q: 看这个输入输出,输入是连着的,然后才统一输出…
因为经常有人问我比如某个题目,输出是在一块的(输入一个区域,输出一个区域),怎么才能全读进去/是不是必须全都读进去啊?

如果上面的对话没看明白,看一下比如一个例题

题目描述
第一行输入一个n,是数据的组数
之后每行输入两个数a_i, b_i (1\leq i\leq n)
输出每行两个数的和
输入
2
1 4
2 8
输出
5
10
题干不是这么给的输入输出 (→| 输入 ←| 输出)
→|2
→|1 4
←|5
→|2 8
←|10
那么能不能写一个程序,读入组数之后,每次读入两个数,然后输出一个结果,读入两个数,然后输出一个结果。类似于下面这样
而不是把2 1 4 2 8全部存下来再运算一块输出5 10呢?

说明
由于这个是面向初学编程的,因此尽量减少了专业性,同时避免了一些特殊情况的出现,如果有不准确的还望指正。

0x10 什么是流

简单来说是有顺序的一串字节的序列。这串序列可以连续地读写。
(类似于磁带,只能按顺序播放,否则就要倒带等等)

0x20 输入流与输出流

在学过的C/C++/python(视情况)中,先不考虑文件读写,那么一个程序的所有的交互就是通过键盘输入屏幕显示这两块实现的。
例如 C 语言的 printf scanf,C++ 的 cin cout,python 的 input print 实现的。(后面的例子均提供C语言和python的,C++参考C语言)
这时如果把程序看作一个黑匣子(不考虑程序的实际功能),那么可以认为是下面的结构

               |        | 
============== |        | ==============
  键盘输入  ->  |  程序  |  -> 屏幕输出
============== |        | ==============
               |        | 

即对于我们的程序来说,只有一个输入,一个输出口。程序从输入口读进去,然后从输出口写(输出)出来。
无论是键盘输入的数据,还是程序输出的数据,本质都是一串字节序列。即上面提到的“流”。
记住这个图形,输入和输出不同的口(或者说是两个不同的方向)

0x30 两个口是分离的

也就是说,输出和输入互不干扰。而我们看到的那个黑框,只是提供了一个交互的环境,即上面的图形可以变成

键盘输入 -> 黑框 -> 输入流 -> 程序
屏幕 <- 黑框 <- 输出流 <- 程序

这个黑框帮我们把输入和输出混合到了一起。形成了一种交互式的感觉。
输入输出二者本身就是分离的,这个也就是为什么OJ(在线评测系统)可以把输入输出分离开写的原因。
当然部分题目/教材,为了方便理解,可能会采取比如下划线、标注等等,将输入输出写在一块,这样也没问题。
交互?也就是你输入一些东西,然后它有一个反映

0x40 缓冲区

这里首先举个例子

#include <stdio.h>
int main(){
    int x, y, i;
    printf("请输入一个整数: \n");
    scanf("%d", &x);
    printf("你输入的是%d\n", x);
    printf("请输入另外一个数字,虽然可能光标未显示,但是你相信你输进去了,然后按回车\n");
    for(i = 0; i < 2000000000; i++){
        // 仅仅一个延时的效果
        // 具体根据自己电脑运行速度调整循环的数字
    }
    printf("现在循环结束了,如果你还没输入,那么重新开始一下\n");
    scanf("%d", &y);
    printf("你输入的是%d\n", y);
    return 0;
}

首先输入一个数字,然后会立刻显示出来我们输入的东西。之后,显示让我们输入第二个数字的时候,按下比如 34 Enter,屏幕上可能没有回显(如果有提示已经循环结束的话,那么调大这个循环次数)
等一会,循环结束之后,会显示输入的内容,和应该的输出。

所以…
emm…

虽然没显示出来,但是这个现象表明,数字已经输入进去了,只是我们的程序还没接收到而已。
因此很容易想到,这里面应该是有一个临时存储的区域

这个区域实际就是缓冲区

0x50 缓冲区2

简单来说,是为了提高程序的效率。

比如下面的代码片段

scanf("%d", &a);
printf("%d\n", a);
scanf("%d", &b);
printf("%d\n", b);

正常来说应该是这样输入输出

→|1234<Enter>
←|1234
→|2234<Enter>
←|2234

但是如果输入1234 2234 <Enter>呢?

→|1234 2234<Enter>
←|1234
←|2234

会变成这样,也就是第一个输入的 1234 被读进去了,而第二个输入的 2234 没有被读入。等到下次需要读入的时候,才继续从缓冲区的部分继续读入。(等到缓冲区没有可以读入的时候,等待用户输入)

如果换成这样

scanf("%d", &a);
printf("%d\n", a);
fflush(stdin);
scanf("%d", &b);
printf("%d\n", b);

里面的fflush(stdin);是刷新标准输入缓冲区(即清空)
之后再尝试就会像这样

→|1234 2234<Enter>
←|1234
 |<等待输入>

0x60 为什么要有缓冲区

一方面是方便使用。首先在有缓冲区的情况下,完全不影响交互式地操作。而除此之外,还额外地支持了一次性输入数据,然后让程序自己运行。

另一方面,在后面的重定向部分,还有着更多的考虑。

0x70 重定向

0x71 准备工作

前面提到了,对于程序,输入流和输出流就像两个管子一样,一边进去一边出去。如果两个管子都是指向了那个黑框,则是从键盘获取输入然后输出到屏幕上。
那么如果把管子接到别的地方会发生什么?

重定向,就像字面所说的那样,把输入/输出流,重新定向到某个位置(可以是一个程序,可以是另外一个蓝框(实际上属于前面的程序类型),可以是文件,甚至可以是网络和打印机)

首先写一个简单的 A+B problem (输入整数 A B,然后输出 A + B 的值,不需考虑数据范围等等问题)

#include <stdio.h>
int main(){
    int a, b;
    scanf("%d %d", &a, &b);
    printf("%d\n", a + b);
    return 0;
}

这个时候,在放着程序的文件夹里面,按着 Shift 右键,应该是有个 在此处打开 powershell/命令提示符/cmd 的东西。如果是powershell 那么进去之后输入 cmd 打开 cmd 操作。

假设我们的程序叫做 a.exe

首先输入 a.exe

C:\Users\zsh2517\projects\blog>a.exe
12 23
35

0x72 输入重定向

现在假如有一个 input.txt ,里面存放的是 12 23 这个数据,怎么办?

C:\Users\zsh2517\projects\blog>a.exe < input.txt
35

这个 < 就是把后面的内容(字节集/字节的流),输出给前面的程序(重定向到前面程序的标准输入流)

0x73 输出重定向

如果要输出到 output.txt 呢?

C:\Users\zsh2517\projects\blog>a.exe > output.txt
12 23

运行之后等待输入,输入完了什么也没显示就结束了。然后打开文件夹下面的 output.txt 发现 35 在文件里面。

0x74 二者可以结合使用

C:\Users\zsh2517\projects\blog>a.exe < input.txt > output.txt

就这样一行,然后就可以把 input.txt 的内容重定向到 a.exe 的标准输入里面,然后把 a.exe 的标准输出重定向到 output.txt
在某种意义上实现了文件读写

0x75 输出的追加和覆盖

运行两次 a.exe > output.txt,每次结束后看一下里面的内容

C:\Users\zsh2517\projects\blog>a.exe > output.txt
1 2

看一下 output.txt 的内容,是 3

C:\Users\zsh2517\projects\blog>a.exe > output.txt
2 3

看一下 output.txt 的内容,是 5

可以看到,第一次结束后, output.txt 是3,然后第二次结束之后覆盖了。

如果不想让他覆盖呢?

>> 是文本追加,即输出到文件的下一行。

删掉 output.txt,之后再试一次,使用a.exe >> output.txt

C:\Users\zsh2517\projects\blog>a.exe >> output.txt
1 2

C:\Users\zsh2517\projects\blog>a.exe >> output.txt
2 3

文件的内容是

3
5

这次对了。

0x76 管道重定向

管道重定向,就是在两个程序之间搭一个管道。从第一个程序的标准输出 连接到 第二个程序的标准输入
例如 写一个 generate.exe 是 输出两个数字的,然后写一个 solve.exe 是求解 A+B 的

// generate.cpp
#include <stdio.h>
int main(){
    printf("2 3");
    return 0;
}

// solve.cpp
#include <stdio.h>
int main(){
    int a, b;
    scanf("%d %d", &a, &b);
    printf("%d", a + b);
    return 0;
}

之后运行 generate.exe | solve.exe

C:\Users\zsh2517\projects\blog>generate.exe | solve.exe
5

可以看到,generate.exe 的输出的 2 3 输入给了 solve.exe ,然后 solve.exe 从这里面读取了输入,之后输出了 5

0x77 输入重定向会影响键盘输入

也就是比如 a.exe < input.txt 的时候,所有输入必须要从 input.txt 里面输入,而黑框内的输入是无效的

0x80 错误流

0x81 错误流是啥

配合重定向食用更好

前面提到了,输入和输出都可以重定向到文件或者其他位置。这样的话,如果程序出错了怎么办?从大量的输出里面找要找的错误信息?
这个时候,就是错误流的使用了,错误流的方向也是输出。即原来的那个图,改成了这样

                    标准错误流
                     ↗
标准输入流 ----> 程序
                     ↘
                    标准输出流

在这种情况下,标准输入流和标准输出流都是重定向到了其他地方,但是还有一个标准错误流可以留在屏幕上,这样输入和输出不会受到影响,而错误消息会立即打印到屏幕上方便反应。

0x82 标准输出流重定向不影响错误流

之后配合上上面的重定向,这里首先向两个流去写入东西。

#include <stdio.h>
int main(){
    fprintf(stdout, "这是一条标准输出流的信息\n");
    fprintf(stderr, "这是一条标准错误流的信息\n");
    fprintf(stdout, "这是又一条标准输出流的信息\n");
    fprintf(stderr, "这是又一条标准错误流的信息\n");
}

运行输出是

C:\Users\zsh2517\projects\blog>a.exe 
这是一条标准输出流的信息
这是一条标准错误流的信息
这是又一条标准输出流的信息
这是又一条标准错误流的信息

没有任何问题,就是按顺序输出的。
之后假如叫做 a.exe,尝试使用 a.exe > output.txt

C:\Users\zsh2517\projects\blog>a.exe > output.txt
这是一条标准错误流的信息
这是又一条标准错误流的信息

发现通过 stderr 输出的内容(输出到标准错误流的内容)依然打印到了屏幕上
而输出到 stdout 的内容(标准输出流)输出到了 output.txt

0x83 错误流重定向

那么怎么重定向错误流呢?

方法是 2> (即在 > 前面加上一个 2)
案例,还是上面的代码

重定向标准错误流

C:\Users\zsh2517\projects\blog>a.exe 2> output.txt
这是一条标准输出流的信息
这是又一条标准输出流的信息

重定向标准输出流
这里可以直接使用 > ,也可以使用 2>

C:\Users\zsh2517\projects\blog>a.exe 1> output.txt
这是一条标准错误流的信息
这是又一条标准错误流的信息

重定向到不同文件
控制台

C:\Users\zsh2517\projects\blog>a.exe 1> stdout.txt 2> stderr.txt

stdout.txt

这是一条标准输出流的信息
这是又一条标准输出流的信息

stderr.txt

这是一条标准错误流的信息
这是又一条标准错误流的信息

可以看到这两个是分开了的。

0x90 控制台

流的概念已经完了,现在来说一下那个“黑框”(其实也叫比如 控制台 等等)

使用过 Visual Studio 或者比如 Codeblocks 的人,应该对 Console, Terminal, 控制台, 终端 这些词不陌生。比如 codeblocks 直接创建的是 Console 程序,VS创建项目一开始都是选择 控制台程序。(还是不考虑文件输入输出的情况下)选择这些类别写的程序,自己写的东西就是从标准输入流中读取,然后写出到输出流中。

双击运行一个 控制台程序/黑框的程序/命令行程序,然后在win7及以上的任务管理器(实际上是 process explorer,自带的任务管理器可能不支持)的进程/详细信息 列表中,除了多了 你的程序.exe ,还有一个进程是 conhost.exe (C:\Windows\System32\conhost.exe) 这个可以不了解
实际上,负责显示、接受输入输出的是那个 conhost.exe。这个程序与你的程序建立管道连接,接收输入、处理缓冲区等等
可以认为这个 conhost.exe 实际上就是接管了你的程序的输入流和输出流。将你在 conhost.exe 上的操作写入标准输入流,将你的程序的输出到标准输出流的程序。而你写的只是一个没有窗口的程序,然后Windows为你的程序提供了一个默认的窗口。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

标签云