IO流

IO流

旨在记录IO流的相关内容,更加熟悉缓冲区(但不会提及最最简单基础的东西),以及文件的输入输出相关的内容
这篇文档前面的部分将会更新C语言的相关内容,后面有空会跟上CPP的IO流

最熟悉的反而是最不熟悉的

对于任何一个cpp初学者来说,最先接触的函数一般除了main()之外就是printf()了,scanf()紧跟其后进入了我们的视野,但一定一直保留有很大的疑惑,首先是入门书不会教我们变长函数是怎么实现的,在参数列表里使用...这样的东西很有意思,在cpp里有其它的语言特性与之有关(形参包),不过这不是我们这篇文档记录的重点。

初学者一定会在scanf()的输入缓冲区上栽跟头,我们就先来聊聊scanf()

scanf()的更多应用

要想更加仔细的探讨scanf(),必然要进入到源码中去咯
scanf()其中会调用_doscan()这一个函数,这个函数主要的目的就是处理那些格式串
对于这个函数我的研究目的是确定是否对于scanf("%d%c",d,c);这样一条语句,c只能接收到空白字符,现在看来这个问题的答案是肯定的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if ((kind != 'c') && (kind != '[') && (kind != 'n'))
{
do
{
ic = getc(stream);
nrchars++;
} while (isspace(ic));
if (ic == EOF)
break;/* outer while */
} else if (kind != 'n')
{/* %c or %[ */
ic = getc(stream);
if (ic == EOF)
break;/* outer while */
nrchars++;
}

上面的代码说明了%后接的不是"c" , "[" , "n"则会在缓冲区里去掉前面的空白字符,而在执行scanf("%d%c",d,c);时候,当变量d接收到数值,在缓冲区中把接收到的d删掉,后面还会跟着一个未删除的空白字符,%c就正好接受到了那个空白字符,因而c永远都是空白字符

要想避免这样一种情况的发生,有几种方法

1.使用非格式串,在%d%c中间加一个空格,这样在输入的时候你必须多输入一个空格,这样并不好,没有包括所有的空白字符,万一你用的是换行呢
2.使用%*c可以跳过一个字符,这里*代表忽略,c代表忽略的大小,如果把c换成一个d那么将会忽略掉第一个输入的整型,将第二个输入的整型存入c
3.使用fflush()清理缓冲区,这样的方法也有一定的弊端

这里我倾向于使用第二种方法,当然这里也可以使用一个getchar()来跳过啦

对于上面这段清除空白字符的代码,我们需要注意,清除空白代码只在一开始匹配才去掉开头的空白字符

仔细看一看发现居然还有格式串和%c这样特殊,%[%n,这是什么东西,从来没听说过嘛,仔细一查,%n会给变量赋值在匹配它时scanf处理的字符数量,比如scanf("%d%d%n",x,y,z)输入10 20那么x,y,z会分别匹配上10 20 5,所有对于这样一个格式串,确实不能清除空白字符

%[就有趣了,这样一个东西,看起来很像是一种正则,它其实是%[]%[]主要用来输入字符串,以控制输入的字符。如:%[123]则只输入集合123中的字符,遇到其他字符输入就结束,%[^123]表示不输入字符集123中的字符,即遇到123中的某个字符就停止输入。比如我们想用scanf实现输入一行,而一行中可能有空格,如果用scanf("%s",str);则在输入遇到空格时就停止了输入,而我们用scanf("%[^\n]",str);则其再遇到换行时才结束输入,遇到其他人是字符都不会停止。

scanf()是有返回值的,除了返回-1表示输入出错以外,输出多少,表示成功匹配了()多少个值,如果一个没匹配上,在其之后匹配的都失败

缓冲区的引入

在上面讲到scanf()时候的第三种方法时谈到了缓冲区,其实这是一块用于临时存放的内存。
我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。

又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。现在您基本明白了吧,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

其实在linux下系统调用read()write()函数参数列表就要给出buf
我们可以把stdout也看作一个文件,对于文件的IO,本质上都是调用了read()write(),以及lseek()等等这样的系统调用
(注解:所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数,使用 lseek 函数可以改变文件的 cfo )
关于缓冲区的具体实现请点击这里

C++ IO流

To be continue