当前位置:首页 > 公众号精选 > C语言与CPP编程
[导读]前言 有没有发现,基本上所有的C语言入门书籍,或者是我们的教程里面,第一个C语言程序实体,都是“Hello World!”;我不知道这是不是行业的“潜规则”,总之,它把无数的程序员带进了计算机的世界,步入了代码的大坑里,所以你好,世界! 一件趣事 我记得大学学习计


前言

有没有发现,基本上所有的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))));

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

流是什么

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

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

输入输出

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

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

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

字符输入输出(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; 
}

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

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

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

再次敲一下回车键,窗口变为如下:  此时再打开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可以交替按下一些字符,编译结果如下:

当按到第4096个字符时,提示您不能再按下去,说明行缓存的大小是4k,此时按下回车键,返回第一个字符是‘a’继续敲下回车键,缓存区的其他字符就全部输出

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

最后

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

   

叨叨一下,最近建了一个学习群,用于学习交流,需要加群的,加微信号:cyuyan2020,备注:加群


点【在看】是最大的支持 

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

先说答案:不行。通常,reg[7:0] mem[1:0]这种写法是作为内部二维寄存器使用的,比如定义一个1KB的存储器,可以用reg[7:0] memory[0:1023],或者reg[7:0] memory [1023...

关键字: 端口 输入输出

PLC应用方便,可靠性高。很多设备的的电气控制,主要以开关量的I/O电气接口为主,PLC的输入端通常与现场仪表、各种开关等电器元件进行连接,PLC的输出端通常与各种类型的执行机构进行连接,如电动

关键字: 电气元件 PLC 输入输出 开关

  变频器的输入侧的测量方法   变频器的输入电源是交流50Hz电源,其测量基本与标准的工业交流电源的测量相同,但是,由于变频器的逆变侧PWM波形的影响,应注意以下所述几个问题。

关键字: 变频器 输入输出 输出电流 功率因数

卓大大 您能发篇推文讲解一下舵机控制的传递函数如何求解吗 在做智能车仿真时卡在这里了。 ▲ 舵机的传递函数如何建立 的确,宅在家里手里没有实际的智能车模,也无法实际调试。利用所学习的理论知识来对智能车建模仿真也不失为一种...

关键字: 舵机 建模仿真 输入输出 传递函数

模拟量输入/输出量程转换的概念 实际工程中,我们要面对很多工程量,如压力、温度、流量、物位等,他们要使用各种类型传感器进行测量,传感器再将测量值通过输出标准电压、电流、温度或电阻信号供

关键字: 模拟 数据类型 西门子PLC 输入输出

▲ 舵机的传递函数如何建立 的确,宅在家里手里没有实际的智能车模,也无法实际调试。利用所学习的理论知识来对智能车建模仿真也不失为一种锻炼的方法,这更能够将课内课外结合起来。 在仿真智能车控制中,有很多环节需要建模,其中的...

关键字: 舵机 建模仿真 输入输出 传递函数

支持位带操作的两个内存区的范围是:0x2000_0000‐\0x200F_FFFF(SRAM区中的最低1MB)0x4000_0000‐\0x400F_FFFF(片上外设区中的最低1MB)位带操作可以在不加任何特殊指令时实...

关键字: gpio STM32 位带操作 输入输出

(1)GPIO_Mode_AIN模拟输入(2)GPIO_Mode_IN_FLOATING浮空输入(3)GPIO_Mode_IPD下拉输入(4)GPIO_Mode_IPU上拉输入(5)GPIO_Mode_Out_OD开漏输...

关键字: STM32 输入输出

GPIO的输出背景:应用在点亮LED。LED串联电阻再上拉,另一端与GPIO口相连。因此,GPIO输出高电平则LED灭,输出低电平则LED亮。第一步工作是初始化GPIO,使能外设时钟,因为GPIO是挂载在APB2中线上的...

关键字: gpio STM32 输入输出
关闭
关闭