这两天赶上高考,家里有参加高考的,所以没有来园子里面逛逛, 今天高考完了,得闲了,所以出来透透气。
上次我写了些关于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的编译工具,可以研究研究。