转:c语言EOF是什么?(及getchar()和putchar用法)
我学习C语言的时候,遇到的一个问题就是EOF。
它是end of file的缩写,表示"文字流"(stream)的结尾。这里的"文字流",可以是文件(file),也可以是标准输入(stdin)。
比如,下面这段代码就表示,如果不是文件结尾,就把文件的内容复制到屏幕上。
int c;
while ((c = fgetc(fp)) != EOF) {
putchar (c);
}
很自然地,我就以为,每个文件的结尾处,有一个叫做EOF的特殊字符,读取到这个字符,操作系统就认为文件结束了。
但是,后来我发现,EOF不是特殊字符,而是一个定义在头文件stdio.h的常量,一般等于-1。
#define EOF (-1)
于是,我就困惑了。
如果EOF是一个特殊字符,那么假定每个文本文件的结尾都有一个EOF(也就是-1),还是可以做到的,因为文本对应的ASCII码都是正值,不可能有负值。但是,二进制文件怎么办呢?怎么处理文件内部包含的-1呢?
这个问题让我想了很久,后来查了资料才知道,在Linux系统之中,EOF根本不是一个字符,而是当系统读取到文件结尾,所返回的一个信号值(也就是-1)。至于系统怎么知道文件的结尾,资料上说是通过比较文件的长度。
所以,处理文件可以写成下面这样:
int c;
while ((c = fgetc(fp)) != EOF) {
do something
}
这样写有一个问题。fgetc()不仅是遇到文件结尾时返回EOF,而且当发生错误时,也会返回EOF。
(我注:对于二进制文件存在一些问题,因为二进制文件中的数据值可能是‘-1’,为了防止在对二进制文件进行读操作时,误将
数据‘-1’当做文件结束符,ansi c提供了feof函数。如果为1表示文件结束,为0未结束。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp;
int ch;
if((fp=fopen("C:\\1.txt","r+"))==NULL)
{
printf("cannot open this file");
exit(-);
}
while((ch=fgetc(fp))!=EOF)
{
putchar(ch);
}
fclose(fp);
printf("\n");
return ;
}
值得注意的是
int fgetc(FILE *fp);
返回的是int。但由于所读取的字符被保存在低字节当中,所以可以复制一个char型变量。
NULL在stdio.h中定义:
/* Define NULL pointer value */
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
)
)
因此,C语言又提供了feof()函数,用来保证确实是到了文件结尾。上面的代码feof()版本的写法就是:
int c;
while (!feof(fp)) {
c = fgetc(fp);
do something;
}
但是,这样写也有问题。fgetc()读取文件的最后一个字符以后,C语言的feof()函数依然返回0,表明没有到达文件结尾;只有当fgetc()向后再读取一个字符(即越过最后一个字符),feof()才会返回一个非零值,表示到达文件结尾。
所以,按照上面这样写法,如果一个文件含有n个字符,那么while循环的内部操作会运行n+1次。所以,最保险的写法是像下面这样:
int c = fgetc(fp);
while (c != EOF) {
do something;
c = fgetc(fp);
}
if (feof(fp)) {
printf("\n End of file reached.");
} else {
printf("\n Something went wrong.");
}
除了表示文件结尾,EOF还可以表示标准输入的结尾。
int c;
while ((c = getchar()) != EOF) {
putchar(c);
}
但是,标准输入与文件不一样,无法事先知道输入的长度,必须手动输入一个字符,表示到达EOF。
Linux中,在新的一行的开头,按下Ctrl-D,就代表EOF(如果在一行的中间按下Ctrl-D,则表示输出"标准输入"的缓存区,所以这时必须按两次Ctrl-D);Windows中,Ctrl-Z表示EOF。(顺便提一句,Linux中按下Ctrl-Z,表示将该进程中断,在后台挂起,用fg命令可以重新切回到前台;按下Ctrl-C表示终止该进程。)
那么,如果真的想输入Ctrl-D怎么办?这时必须先按下Ctrl-V,然后就可以输入Ctrl-D,系统就不会认为这是EOF信号。Ctrl-V表示按"字面含义"解读下一个输入,要是想按"字面含义"输入Ctrl-V,连续输入两次就行了。
(完)
另外一篇文章:
在阮一峰的网络日志里,阮先生写了一篇关于EOF的文章(http://www.ruanyifeng.com/blog/2011/11/eof.html);该文描述了EOF不是文件的结束符,而是fgetc函数读取文件,到达文件结尾的时候返回一个标志。 在宏定义里,EOF=-1。 Q: 于是有网友问如果文件中有个-1怎么办?
回想到以前学习的一篇文章(http://faq.cprogramming.com/cgi-bin/smartfaq.cgi?answer=1048865140&id=1043284351),里面有关于EOF的解释,于是我根据自己的理解、用工具观察的二进制结果加上程序代码,做拙文一篇, 希望能够回答上面的问题。
首先看看fgetc的说明:
在linux控制台中,执行命令
man 3 fgetc
“fgetc() reads the next character from stream and returns it as an unsigned char cast to an int, or EOF on end of file or error.”
上面说明的意思是“fgetc()每次读取一个unsigned char字符的字符,然后转型为int型返回,如果到达文件的结束或者出错就返回EOF”.
思考:
既然是读取一个unsigned char类型的字符,为什么不返回unsigned char而是int呢?我们知道一个int就是32个bit,而一个char就是16个bit,由于int存储空间比char大,所以一个unsigned char转型为int不可能有负数的情况出现。于是fgetc函数返回int,使得可以用一个负数来表示 EOF 或其它情况。注意:一个unsigned char不可能转型为负数的int值,并不是说signed char也不是转型为负数的int值。请看如下代码

#include <stdio.h>
int main(void){
int i = -1;
signed char sc = 0xff;
unsigned char usc = 0xff;
printf("转成16进制后i是 %x, sc是 %x\n",i,sc);
if (i == sc){
puts("i == sc");
}else{
puts("i != sc");
}
putchar('\n');
printf("转成16进制后i是 %x, usc是 %x\n", i, usc);
if (i == usc){
puts("i == usc");
}else{
puts("i != usc");
}
return 0;
}

假如,我们有一个文件echo "this is my file" > myfile.txt
然后通过ghex编辑二进制文件,于是把this is my file的改成十六进制的表示, 使得十六进制文件包含0xff 0xff
0x74 0x68 0x69 0x73 0xff 0xff 0x73 0x20 0x6d 0x79 0x20 0x66 0x69 0x6c 0x65 0x0a
fgetc每次读一个unsigned char, 于是读的顺序为0x74 0x68 0x69 0x73 0xff 0xff 0x73 0x20 0x6d 0x79 0x20 0x66 0x69 0x6c 0x65 0x0a
用程序验证一下

#include <stdio.h>
#include <stdlib.h> int main(void){ FILE *fp; int c; if ((fp = fopen("myfile.txt", "rb")) == NULL){ perror("myfile.tx"); return 0; }
char buffer[33]; while((c = fgetc(fp)) != EOF){ //printf("%0x \t",c);
//itoa(c, buffer, 16);
sprintf(buffer, "%#x ", c);
printf("%s", buffer);
}
printf("\n"); fclose(fp); return 0; }

从上面程序的运行结果来看,fgetc并没有把文件中的0xffff误当做EOF。如果fgetc每次读取的是一个int,也就是32个bit,那么它可能读到0xffff,有符号的int,就等于-1, 但是这种情况不会出现,因为fgetc每次读的是一个unsigned char。
类型转换
我们再来看看十进制整数-1,在不同存储空间和不同的类型下值分别是什么?
| 十进制 | 十六进制(int) | 十六进制(char) |
| -1 | ffff | ff |
接着看char型从8位的值,补充为32位的值,在不同类型补充的值不同
| 十进制 | 十六进制char | 由unsigned char补充为int的值 | 由signed char补充为int的值 |
| -1 | ff | 00ff | ffff |
于是根据上面的转型规则,一个陷阱出现了。
一个陷阱
单单从函数名上推测,很多人会认为fgetc返回一个是个16位的char类型的字符,所以程序上会写成

#include <stdio.h> int main(void)
{ FILE *fp; unsigned char c; if ((fp = fopen("myfile.txt", "rb")) == NULL)
{ perror ("myfile.txt"); return 0; } while ((c = fgetc(fp)) != EOF)
{ putchar (c); } fclose(fp); return 0; }

文件结束时fgetc函数返回0xffff,但是被以上的程序变成了unsigned char了,于是根据类型把c补全到32位,变成了0x00ff。
由于0x00ff != 0xffff, 所以以上程序会出现一个死循环。。
总结:
fgetc函数每次读一个16位的unsigned char,但是转型为32位的int返回,遇到文件结束返回0xffff,由于int的长度比char长,所以不可能读到负数。
转:c语言EOF是什么?(及getchar()和putchar用法)的更多相关文章
- 基于C语言EOF与getchar()的使用详解
转自:http://www.jb51.net/article/36848.htm 大师级经典的著作,要字斟句酌的去读,去理解.以前在看K&R的The C Programming Langu ...
- Linux C 字符函数 getchar()、putchar() 与 EOF 详解
首先给出<The_C_Programming_Language>这本书中的例子: #include <stdio.h> int main() { int c; c = getc ...
- C语言中最常用的三种输入输出函数scanf()、printf()、getchar()和putchar()
本文给大家介绍C语言中最常用的三种输入输出函数scanf().printf().getchar()和putchar(). 一.scanf()函数格式化输入函数scanf()的功能是从键盘上输入数据,该 ...
- C语言EOF是什么?
C语言 EOF是什么? Linux中,在新的一行的开头,按下Ctrl-D,就代表EOF(如果在一行的中间按下Ctrl-D,则表示输出"标准输入"的缓存区,所以这时必须按两次Ctrl ...
- getchar()、putchar()、gets()、puts()、cin.get()、cin.getline()、getline()
1.getchar: 原型为int getchar(void). 它从stdin里读取一个字符.返回值为用户输入的ASCⅡ码,出错返回-1. eg:c=getchar(). 2.putchar: 原型 ...
- linux c语言 fork() 和 exec 函数的简介和用法
linux c语言 fork() 和 exec 函数的简介和用法 假如我们在编写1个c程序时想调用1个shell脚本或者执行1段 bash shell命令, 应该如何实现呢? 其实在<std ...
- getchar与putchar缓冲区以及字符串数组、指针
getchar与putchar缓冲区 有下面的语句段: while ((s = getchar()) != '\n'){ putchar(s); putchar("\n"); } ...
- 【SQL系列】从SQL语言的分类谈COMMIT和ROLLBACK的用法
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[SQL系列]从SQL语言的分类谈COMMIT和 ...
- C语言EOF
验证表达式getchar()!=EOF的值是1还是0 编写一个打印EOF值的程序 windows下是ctrl-z 就是ctrl和z一起按了,就是结束符linux下是ctrl-d是结束符.这个是一个 ...
随机推荐
- Sql server 事务的两种用法
事务(Transaction)是并发控制的单位,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位. 通过事务,SQL Server能将逻辑相关的一组操作绑定在一起,以便 ...
- 字符串String类
1. String类是一个密封类.用关键字sealed修饰: 2. 字符串的两个特性: ·不可变性:string类型变量,一旦声明就表明它是不会被改变的.因此,string中的方法对strin ...
- Jetty 8.1 安装 运行 部署
链接地址:http://blog.csdn.net/lego2816/article/details/42650545 Java + MySQL 从Jfinal开始,第一步先走完整个流程,主要是发布部 ...
- 条款05:了解C++默默编写并调用哪些函数
每一个class都会有一个或多个构造函数.一个析构函数.一个copy assignment操作符.这些控制着基础操作,像是产出新对象并确保它被初始化.摆脱旧对象并确保它被适当清理.以及赋予对象新值. ...
- [C#参考]委托机制
1. 委托概述 这是一个新的概念,但是其本质并不是什么新鲜的事物,委托本质上就是一个类.只不过一般的类是数据的集合,委托保存的是一个或者多个方法.委托是引用类型,因此委托有引用和对象,同时委托对象中包 ...
- oracle去除字符串中间的空格
update AC01 A set A.AAC003 = REGEXP_REPLACE(A.AAC003, '( ){1,}', '') WHERE A.AAC002 IN (SELECT AAC00 ...
- codeforces 623A. Graph and String 构造
题目链接 给出一个图, 每个节点只有三种情况, a,b, c. a能和a, b连边, b能和a, b, c,连边, c能和b, c连边, 且无重边以及自环.给出初始的连边情况, 判断这个图是否满足条件 ...
- XWalkView+html 开发Android应用
在Android开发中有时候为了开发简洁和方便移植,采用了Html+WebView的开发模式,然而Android自带的WebView控件是调用的本机的浏览器内核,有些版本较老的手机浏览器和手机性能都不 ...
- SSH有端口映射功能(访问本地端口=访问远程端口)
大部分SSH连接软件都有SSH通道转发功能,就是用这个实现的. 如果Delphi在代码上实现的话,用libSSH 或者 SecureBridge都可以. 代码基本不用帖,思路给大家讲一下吧. SSH有 ...
- [K/3Cloud] 如何从被调用的动态表单界面返回数据
在需要返回数据的地方调用表单返回方法完成数据返回 this.View.ReturnToParentWindow(retData); 在调用界面的回调函数中取出返回结果的ReturnData即可使用. ...