你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

C语言的输入输出流

[复制链接]
gaosmile 发布时间:2020-8-1 22:44
前言

有没有发现,基本上所有的C语言入门书籍,或者是我们的教程里面,第一个C语言程序实体,都是“Hello World!”;我不知道这是不是行业的“潜规则”,总之,它把无数的程序员带进了计算机的世界,步入了代码的大坑里,所以你好,世界!

一件趣事

我记得大学学习计算机的时候,就是在电脑这样的一个程序,不知道经过了什么过程,就能在计算机上显示出"Hello World!"。后来我把这个"Hello World!"改成了"I Love xxx!",哦买噶,真是惊呆我了,有一种立马在女神面前炫耀的感觉了.

那么,这其中有什么奥妙呢,我们从C语言的输入输出流开始说起.

hello world 是怎么显示出来的

对的,就是这样的一个程序

#include <stdio.h>
int main(int argc,char **argv)
{
  //printf("Hello World!");
  printf("I Love xxx!");
}

在我们日常生活中,总看到电子显示屏上面显示着"欢迎某位领导莅临我校观察","宾馆"之类的,那是不是"Hello World!"也是这样编译出来的呢?

我们看到程序中的printf();由系统或者编译器提供商提供的一个应用接口,是格式化输出函数, 一般用于向标准输出设备按规定格式输出信息。一般其函数原型应该是这样的:

int  printf(const char *,...);
int _EXFUN(printf, (const char *__restrict, ...)
    _ATTRIBUTE ((__format__ (__printf__, 1, 2))));

经过预处理,编译,汇编,链接四个过程,借助了相应的缓冲区来进行输入与输出,就会显示出来

微信图片_20200801223447.png

输入输出流

流是什么

“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据的抽象描述。

在计算机系统中是指信息从外部输入设备向计算机内部输入,或者从内存向外部输出设备输出的过程。这种输入输出的过程被形象的比喻为“”。

输入输出

什么是输入输出呢?C语言中我们用到的最频繁的输入输出方式就是scanf()与printf()。

微信图片_20200801223451.png

scanf():从标准输入设备(键盘)读取数据,并将值存放在变量中。

微信图片_20200801223454.png

printf():将指定的文字/字符串输出到标准输出设备(屏幕)。注意宽度输出和精度输出控制。

微信图片_20200801223457.png

字符输入输出(getchar/putchar),字符串输入输出函数 (gets与puts),与gets/puts类似的还有fgets与fputs,它们一般用于对文件的操作.

缓冲区定义

缓冲区是内存空间的一部分,也就是说在内存空间中预留了一定大小的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,根据其对应的是输入设备还是输出设备,分为输入缓冲区输出缓冲区

原理介绍

当调用输入函数scanf()时,输入函数会将我们输入的数字输入到输入缓冲区,而当我们的输入缓冲区有内容时,再次输入将不会被执行,而是直接跳过执行,将输入缓冲区的内容赋给变量。

引入缓冲区的意义

缓冲区就是一块内存,用来做数据的一个临时存放点,在输入输出操作中起着至关重要的作用,在百度百科定义如下

比如我想把一篇文章以字符序列的方式输出到计算机显示器屏幕上,那么我的程序内存作为数据源而显示器驱动程序作为数据目标,如果数据源直接对数据目标发送数据的话。数据目标获得第一个字符,便将它显示。然后从端口读取下一个字符,可是这时就不能保证数据源向端口发送的恰好是第二个字符(也许是第三个,而第二个已经在数据目标显示时发送过了)。这样的话就不能保证输出的数据能完整的被数据目标所接受并处理。

缓冲区的类型

缓冲区有三种,我一个一个地说下:

1、全缓冲

内存中有一段存储区域,比如有1024个字节大小,有一个程序会从这段存储区域中读取数据。现在系统把一个文件的内容放入这个存储区,只要1024个字节都放满了,那么程序会立即来读取这1024个字节的数据。只要1024个字节没有放满,哪怕只放了1023个字节,程序都不会来读取,这就是全缓冲的意思。

#include <fstream>
using namespace std;

int main()
{
    //创建文件test.txt并打开
   ofstream outfile("test.txt");

    //向test.txt文件中写入4096个字符’a’
   for(int n=0; n < 4096; n++)
    {
        outfile << 'a';
    }

    //暂停,按任意键继续
   system("PAUSE");

    //继续向test.txt文件中写入字符’b’,也就是说,第4097个字符是’b’
   outfile << 'b';

    //暂停,按任意键继续
   system("PAUSE");

  return 0;
}

编译并执行,运行结果如下:

微信图片_20200801223500.png

此时打开工程所在文件夹下的test.txt文件,您会发现该文件是空的,这说明4096个字符“a”还在缓冲区,并没有真正执行I/O操作。敲一下回车键,窗口变为如下:

微信图片_20200801223503.png

此时再打开test.txt文件,您就会发下该文件中已经有了4096个字符“a”。这说明全缓冲区的大小是4K(4096),缓冲区满后执行了I/O操作,而字符“b”还在缓冲区。

再次敲一下回车键,窗口变为如下:

微信图片_20200801223506.png   

此时再打开test.txt文件,您就会发现字符“b”也在其中了。这一步验证了文件关闭时刷新了缓冲区。

2、行缓冲

内存中有一段存储区域,比如有1024个字节大小,有一个程序会从这段存储区域中读取数据。现在系统把一个文件的内容放入这个存储区,假如放了10个字节的数据,你敲了回车键,那么程序就马上来读取了。假如放了20个字节,你敲了回车奖,程序也会来读取。所以即使1024个字节没有放满,但是你敲了回车键,程序就会来读取,这个就叫做行缓冲。

使用键盘操作演示行缓冲,先介绍getchar()函数。函数原型:

int getchar(void) ;

说明:当程序调用getchar()函数时,程序就等着用户按键,用户输入的字符被存放在键盘缓冲区中,直到用户按回车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar()函数才开始从键盘缓冲区中每次读入一个字符。也就是说,后续的getchar()函数调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完后,才重新等待用户按键。

#include<iostream>
using namespace std;

int main()
{  

    char c;  
  //第一次调用getchar()函数,程序执行时,您可以输入一串字符并按下回车键,按下回车键后该函数返回。返回值是用户输入的第一个字符 (假设用户输入了 abcdef,函数返回a)
   c = getchar();
    //显示getchar()函数的返回值
   cout<< c << endl; // 输出 a  
  // 循环多次调用getchar()函数,将每次调用getchar()函数的返回值显示出来,直到遇到回车符才结束。 这时函数执行不会让用户输入而是顺序读取缓冲区字符内容。第一个字符用户输入结束后已经读取,所以会从第二个字符开始读
   while((c = getchar())!='\n')
   {
       cout<< "," << c <<endl
   }

   return 0;
}

最后输出结果是

a ,b ,c ,d ,e ,f可以交替按下一些字符,编译结果如下:

微信图片_20200801223508.png

当按到第4096个字符时,提示您不能再按下去,说明行缓存的大小是4k,此时按下回车键,返回第一个字符是‘a’ 微信图片_20200801223511.png

继续敲下回车键,缓存区的其他字符就全部输出

微信图片_20200801223517.png 3、无缓冲

内存中有一段存储区域,比如有1024个字节大小,有一个程序会从这段存储区域中读取数据。现在系统把一个文件的内容放入这个存储区,刚放了1个字节,程序就马上来读取了;又放了一个字节,程序又马上来读取了,这就是没有缓冲。

在C语言中,一般规定是要有行缓冲的。但是使用scanf函数和getchar时,如果行缓冲的换行符没有处理好,程序运行可能会有异常或者闪退等现象。也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。

如错误输出时使用:

cerr<<”错误,请检查输入的参数!” ;

这条语句等效于:

fprintf(stderr, ”错误,请检查输入的参数!”) ;
缓冲区的大小

如果我们没有自己设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲区的大小通常是 512个字节 的大小。

缓冲区大小由 stdio.h 头文件中的宏 BUFSIZ 定义,如果希望查看它的大小,包含头文件,直接输出它的值即可:

printf("%d", BUFSIZ);

缓冲区的大小是可以改变的,也可以将文件关联到自定义的缓冲区,详情可以查看 setvbuf()和 setbuf() 函数。

缓冲区的刷新

下列情况会引发缓冲区的刷新:

  • 缓冲区满时;
  • 执行flush语句,即使用特定函数刷新缓冲区;
  • 执行endl语句,即行缓冲区遇到回车时;
  • 关闭文件。

可见,缓冲区满或关闭文件时都会刷新缓冲区,进行真正的I/O操作。另外,在C++中,我们可以使用flush函数来刷新缓冲区(执行I/O操作并清空缓冲区) 如:

cout << flush; //将显存的内容立即输出到显示器上进行显示

endl控制符的作用是将光标移动到输出设备中下一行开头处,并且清空缓冲区。

cout < < endl;

相当于

cout < < ”\n”< < flush;
强制缓冲区的数字打印/*
输出缓冲区演示
*/
#include <stdio.h>
int main(){
    printf("1\n");
    fflush(stdout); //强制将输出缓冲区的内容显示在屏幕上
    while (1){
    }
    return 0;
}

如何清空输入缓冲区的内容?

如果我想让getchar()每次都能够等待用户输入的话就要清空缓冲区,下面就介绍不同平台的方法

C标准规定 fflush()函数是用来刷新输出(stdout)缓存的。对于输入(stdin),它是没有定义的。但是有些编译器也定义了 fflush( stdin )的实现,比如微软的VC。其它编译器是否也定义了 fflush( stdin )的实现应当查找它的手册。GCC编译器没有定义它的实现,所以不能使用 fflush( stdin )来刷新输入缓存。

对于没有定义 fflush( stdin )的编译器,可以使用 fgets()函数来代替它(比用 getchar()、scanf()等函数通用性好)。可以这样忽略输入流中留下的回车等其它输入,从而使下一次的输入总是保持一个“干净”的状态。(这个是任何平台下都可以的)

char sbuf[1024];
// ...

fgets( sbuf, 1024, stdin );
// ...

在windows 的vc下面就可以这样了:

for(int i=0;i<10;++i)
{
    char ch=getchar();
    fflush(stdin); //每次都会有等待状态了
}

这里说到gcc编译器没有定义fflush的实现,我们一般用getchar();来清除缓冲区

#include <stdio.h>
main()
{
    char c;
   for(;(c=getchar())!='a';)
   printf("%c",c);
   getchar();
    c=getchar();
   printf("%c",c);
}

输入:ssss,回车得到:ssss,光标处(等待输入)

说明: 此时程序没有结束,进行到for循环,因为并没有字符a出现,所以还没跳出for循环.键入回车后,getchar依次从缓冲区内取出(for循环):'s''s''s''s''\n'

如果我们输入:ssssa,回车得到:ssssa,光标处(等待输入)

说明: 程序已经跳出for循环,但是由于我们用getchar();清除了换行'\n',后面第7句c=getchar();需要你输入一个字符(因为ssssa后面并没有新的字符),所以程序仍然没有结束.

如果我们注释掉getchar();这一句,那么得到:ssss,光标处(程序结束) 这个输入ssssa是的回车中的换行符'\n'就被c=getchar();这一句读取并输出了。

总结:键盘输入的字符都存到缓冲区内,一旦键入回车,getchar就进入缓冲区读取字符,一次只返回第一个字符作为getchar函数的值,如果有循环或足够多的getchar语句,就会依次读出缓冲区内的所有字符直到'\n'.

要理解这一点,之所以你输入的一系列字符被依次读出来,是因为循环的作用使得反复利用getchar在缓冲区里读取字符,而不是ge

最后

很多表面的现象看起来可能不能引起我们的注意,但是当我们注意到细节的时候,才能深究其中的奥秘,写代码亦是如此。

收藏 评论0 发布时间:2020-8-1 22:44

举报

0个回答

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版