C语言学习趣事_关于C语言中的输入输出流_续一

      这两天赶上高考,家里有参加高考的,所以没有来园子里面逛逛, 今天高考完了,得闲了,所以出来透透气。

      上次我写了些关于printf()函数的的文字, 感觉自己对输入输出不是很了解, 并且自己表述的也不是很完整,还几处小毛病,因此想接着上次的话题继续瞎掰。

      那么这次瞎掰点什么呢 ? 那就从输入输出说起吧..................

1、流

    我不知道为什么国内要把stream 翻译成流, 不过这个翻译倒是挺形象的。 我们知道C语言是伴随那个伟大的系统——Unix——而成长的,在Unix及其衍生的系统中,

系统资源都是当做文件来看待的。C继承了这个传统,当我们从键盘输入字符的时候,C会把输入设备(即键盘)当做文件来处理。当我们利用C开发工具提供的输入输出函数

来进行输入和输出时,C会把输入输出的内容当做“流”来处理。

    实际上流是一个理想话的对象,实际输入或输出设备处理的内容映射到这个数据流中,而C会中这个数据流中获取自己需要的信息。

2、文件

     文件: 不知道怎么精确定义这个名词, 如果照大部分资料来看,文件是指存储在存储介质上的一堆0101信息的集合,或者可以认为是存储介质上一块存储区域。

    前面说过在C中,系统资源当做文件来处理, 这里我们主要讨论设备文件。

    在C运行后,会默认自动打开三个设备文件: 标准输入文件、标准输出设备和标准错误输出文件。

    即:   STDIN   标准输入文件  (stdin)

            STDOUT  标准输出文件 (stdout)

            STDERR  标准错误输出文件 (stderr)

在有些系统里面应该是这样定义的:  #define  STDIN_FILENO   0    

                                               #define  STDOUT_FILENO  1

                                               #define  STDERR_FILENO  2

有的系统是这样定义的:

                                              #define stdin     (&_iob[0])
                                              #define stdout   (&_iob[1])
                                              #define stderr    (&_iob[2])

3、 缓冲型输入系统和非缓冲型输入系统

     缓冲型输入系统:是指当C要从从输入文件(这里值标准输入设备,通常就是键盘)读入数据流时,只有当用户按下Enter ——(char)13 后用户输入的内容才能被被C使用。

     非缓冲型的系统:是指当C从输入文件读如数据流时, 不需要等待用户按下Enter键,C就能立即使用。

     如下图所示:

对于C而言,或者说对用C实现的执行实体而言,是否是缓冲或不缓冲,这个由系统决定,目前大部分系统对C而言是缓冲型输入系统。

4、  getc()、putc()、getchar()、putchar()

需要说明的是这两组函数应该是在效果上:  getc()== getchar()

                                                      putc()==putchar()

但是他们的原型,或者说定义形式却是不一样的。

首先来看:getc()和getchar()

在C中getc()的函数原型:

Exp:

     char  getc(FILE *);

     就是说如果要使用getch();函数你需要给他传递一个指针。

     那么getchar()是怎么实现的呢? 这里有两种实现方式: 函数和宏;  我们就说说宏的定义:

Exp:

      #define   getchar()    getc(stdin)

同样来看:putc()和putchar(); 同样putc()需要一个文件指针,即其函数原型如:

Exp:

      int putc(FILE *);

于是putchar()的宏定义就是:

Exp:

    #define  putchar()  putc(stdout)

5、 EOF

     在用getchar()时,我们会从标准输入设备一个一个字符的读取所输入的字符,包括空白字符,例如:空格 、制表符 和 回车;在进行判断的时候可以用一个EOF

来判断是否是输入的结束,这样可以输入任何想输入的字符。

Exp:

       char  chInPut;

       while(EOF != (chInPut=getchar() ))

         {

               putchar(chInPut);

         }

当我们输入:a  b  c  dd 

就会有输出:a  b  c  dd

6、getc()

     在VC 6.0中有两个get()的定义, 一个是宏,一个是函数

宏的定义如下:

    #define getc(_stream)     (--(_stream)->_cnt >= 0  ? 0xff & *(_stream)->_ptr++ : _filbuf(_stream))   

函数定义如下:

   _CRTIMP int __cdecl getc(FILE *);           

   在C语言的各家编译器提供厂商里面有一个不成为的“潜规则”,那就是如果一个标识符前面如果是以下划线开头,这样的标识符通常是编译器预定义的宏,或者预定义的标志符。   

   我们看宏定义;这里用到的宏实际还用到了一个预定义的函数:

                     _CRTIMP int __cdecl _filbuf(FILE *)

    从这个函数可以看出在getc()宏中使用的:_stream是一个具有文件指针类型性质的预定义标识符。为了弄明白这个宏我们需要知道FILE类型的各个域;

其实FILE类型是另一个类型的重定义:  

Exp :

struct _iobuf

{

        char *_ptr;    
        int   _cnt;     
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
};

typedef  _iobuf  FILE;

  从上面可知FILE类型的结构体具有一个_cnt的域;这样我们就来解析getc()的宏;首先看:

           --(_stream)->_cnt >= 0

   在C语言的规范里面,我们知道 -> 的预算优先级与后缀 --的一样,同时高于前缀 -- 的优先级, 因此这里就是每次执行这个宏的时候,就是先判断_stream->cnt - 1

后其值是否大于等于0;如果大于或等于0的话,那么这个宏就返回:0xff & *(_stream)->_ptr++ ; 如果小于0的话,那么这个宏就返回: _filbuf(_stream)当大于等于

0的时候就说明缓冲区内还有文本流字符,这时取出当前字符并与0xFF进行位与作为getc(_stream)的返回值, 并且将字符指针后移一位。

  当 _stream->cnt - 1的值小于0的时候就表示缓冲区内的字符已经全部遍历过。但这时缓冲区内的内容还存在,只是指向缓冲区的指针已经移动到缓冲区后面,因此这

时就需要更新缓冲区内的内容,通过函数_filbuf(_stream)来实现, 函数_filbuf(FILE *) 同时将_stream的各个域重新赋值。

  细心的朋友也许可以发现: #define stdin     (&_iob[0])   中的_iob[0];其实也是一个FILE类型的指针数组,其引用是通过: extern FILE _iob[]; 来实现的。

就像前面说的 va_list 一样_stream 标识符由编译器的提供商定义,并且由编译器解释。这里我们就不在讨论这个FILE类型的预定义标识符。

如果那位大侠需要自己尝试做个C的编译工具,可以研究研究。

原文地址:https://www.cnblogs.com/volcanol/p/2076907.html