1 关于EOF
可以查看EOF的宏定义
函数fgetc
如果读取失败就返回-1,对于文本文件而言,以为着读取结束,因此-1可以作为结束的标志。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
FILE* fp = fopen("a.txt", "r");
if (NULL == fp)
{
perror("open");
return -1; //在main函数中,按照程序开发的一般惯例,成功则返回0,失败则返回-1
}
char ch;
while ((ch = fgetc(fp))!=-1)
{
printf("%c", ch);
}
printf("\n");
return 0;
}
刚刚查看宏定义可知EOF就是-1,因此上面的循环判断可以用EOF替代
这里需要注意的一点是,可以用fgetc的返回值是否为-1来判断文件是否读取结束,但并不意味着文件最后一个字符为-1
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
FILE* fp = fopen("a.txt", "r");
if (NULL == fp)
{
perror("open");
return -1; //在main函数中,按照程序开发的一般惯例,成功则返回0,失败则返回-1
}
char ch;
while ((ch = fgetc(fp))!=EOF) //这里原来是-1,现在改成了EOF
{
printf("%c", ch);
}
printf("\n");
return 0;
}
2 文件中包含二进制数字
假如-1(EOF对应数字)被写进了文件中,那么读取的时候,也会被正常读出来,如果仍然用fputc的返回值是否为-1来判断文件是否读取结束,那么就会出现bug。
例如:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
FILE* fp = fopen("a.txt", "w");
if (NULL == fp)
{
perror("open");
return -1; //在main函数中,按照程序开发的一般惯例,成功则返回0,失败则返回-1
}
char a[10] = { 97, 98, 99, -1, -2, 100 };
int i = 0;
while (a[i]!=0)
{
fputc(a[i], fp);
i++;
}
return 0;
}
上面的程序是把字符数组写入文件,字符数组a中,有一个元素是-1,写入之后,打开a.txt文件,内容如下:
-1和-2无法根据ASCII码确定字符,因此乱码
我们再以EOF作为文件结束标志,读取文件
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
FILE* fp = fopen("a.txt", "r");
char ch;
while ((ch = fgetc(fp)) != EOF) printf("%c", ch);
return 0;
}
输出
abc
也就是说,当读到-1的时候,就退出了while
循环,后面的不会正常打印
3 feof函数
为了避免上述问题的产生,可以使用 feof函数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
FILE* fp = fopen("a.txt", "r");
char ch;
while (!feof(fp))
{
ch = fgetc(fp);
printf("%c", ch); //feof(fp)返回0表示没有到文件末尾,继续读
}
printf("\n");
return 0;
}
5 一个程序中多个文件指针打开同一个文件
上面的程序,假如写和读在同一个源程序中,但写指针和都指针使用不同的名字,程序如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
FILE* fp1 = fopen("a.txt", "w");
if (NULL == fp1)
{
perror("open");
return -1; //在main函数中,按照程序开发的一般惯例,成功则返回0,失败则返回-1
}
char a[10] = { 97, 98, 99, -1, -2, 100 };
int i = 0;
while (a[i]!=0)
{
fputc(a[i], fp1);
i++;
}
FILE* fp2 = fopen("a.txt", "r");
char ch;
while ((ch = fgetc(fp2)) != EOF) printf("%d", ch);
printf(" %d\n", ch);
return 0;
}
输出
-1
查看a.txt文件,可以发现成功将97, 98, 99, -1, -2, 100对应的字符写入,但后面却无法通过fp2读取,并且ch取到的只有-1,即读取失败。具体原因我不知道,但一个程序中,不能出现多个文件指针指向同一个文件
5 文件复制
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
FILE* fp1 = fopen("a.txt", "r");
if (NULL == fp1)
{
perror("open");
return -1; //在main函数中,按照程序开发的一般惯例,成功则返回0,失败则返回-1
}
FILE* fp2 = fopen("b.txt", "w");
if (NULL == fp2)
{
perror("open");
return -1; //在main函数中,按照程序开发的一般惯例,成功则返回0,失败则返回-1
}
char ch;
while (!feof(fp1))
{
ch = fgetc(fp1);
fputc(ch, fp2);
printf("%d\t", ch);
}
fclose(fp1);
fclose(fp2);
return 0;
}
输出:
97 98 99 -1 -2 100 -1
文件结束符-1也会被写进去
如果不想写进去那么必须在feof函数调用之前先读一次,如:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
FILE* fp1 = fopen("a.txt", "r");
if (NULL == fp1)
{
perror("open");
return -1; //在main函数中,按照程序开发的一般惯例,成功则返回0,失败则返回-1
}
FILE* fp2 = fopen("b.txt", "w");
if (NULL == fp2)
{
perror("open");
return -1; //在main函数中,按照程序开发的一般惯例,成功则返回0,失败则返回-1
}
char ch;
while (1)
{
ch = fgetc(fp1);
if (feof(fp1))
break;
fputc(ch, fp2);
printf("%d\t", ch);
}
fclose(fp1);
fclose(fp2);
return 0;
}
输出
97 98 99 -1 -2 100
上面两个程序的区别在于,前者是先判断后读,后者是先读后判断。
6 使用ftell验证feof的行为
为什么先读后判断,和先判断后读,会出现上面的差别?
好的,我们现在就来找原因
现有一个名为a.txt的文件,内容如下:
现在使用先判断后读的方式,查看光标的变化,设计如下程序
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void test_feof1() {
FILE* fp1 = fopen("a.txt", "r");
char ch;
long i = 0;
while (!feof(fp1))
{
i = ftell(fp1);
printf("%d\t", i);
ch = fgetc(fp1);
i = ftell(fp1);
printf("%d\t", i);
printf("%d\n", ch);
}
fclose(fp1);
}
int main() {
test_feof1();
return 0;
}
输出
0 1 51
1 2 43
2 3 53
3 4 61
4 5 56
5 7 10
7 7 -1
最后读到的字符是\n(ASCII码为10),接下来进行了下一轮的循环,随后fgetc读取失败返回-1,当读取换行符\n后文件光标向后移动两格(后面会介绍),即从从位置5到位置7。读完\n后,feof仍然认为文件没有结束,否则最后一轮循环就不会执行,因此上面的猜测是错误的。
现在使用先读后判断的方式,查看光标的变化
void test_feof2() {
FILE* fp1 = fopen("a.txt", "r");
char ch;
long i;
while (1)
{
i = ftell(fp1);
printf("%d\t", i);
ch = fgetc(fp1);
i = ftell(fp1);
printf("%d\t", i);
if (feof(fp1))
break;
printf("%d\n", ch);
}
return 0;
}
int main() {
//test_feof();
test_feof2();
return 0;
}
输出:
0 1 51
1 2 43
2 3 53
3 4 61
4 5 56
5 7 10
7 7
现在可以得到结论了:feof函数什么时候判断文件结尾,只有fputc读取失败(返回-1)后,这个函数才会判定文件结束,哪怕是读完最后一个字符也不行,必须fputc读取失败才行。
至此,先读后判断和先判断后读的差别,原因也找到了。
7 feof不要和fgets配合使用,建议只和fgetc配合使用
使用函数feof判断文件是否读取结束,与fgetc和fgets两种读取函数配合时的表现是不一样的。当feof与fgetc配合使用时,fgetc只有读取失败返回-1之后,feof才认为文件读取结束,而feof与fgets配合时,只需要最后一行读完,feof就认为文件读取结束,无需等到读取失败。
我们来看一下feof与fgets配合使用时的的情形。新建一个名为test_line.txt的文件,内容如下:
新建一个统计行数(文件有多少行)的函数,内容如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int line_number1()
{
FILE* fp = fopen("./test_line.txt", "r");
if (!fp)
{
perror("open");
exit(0);
}
int i = 0;
char temp[256];
while (1)
{
fgets(temp, sizeof(temp), fp);
if (feof(fp))
{
fclose(fp);
return i;
}
i++;
}
}
main函数如下:
int main()
{
int lines1;
lines1 = line_number1();
printf("lines1=%d\n", lines1);
}
输出
lines1=2
while循环只执行了两次,因为读完第三行之后,feof就认为文件读取结束,后面的i++没来得及执行,就退出了循环。
如果把i++移动到if语句前面,也是不行的,因为当文件是一个空文件时,i++也会执行,会造成即使是空文件,其行数也为1。
那么当使用fgets读取文件时,如何判定读取结束呢?答案是使用fgets的返回值,fgets读到文件尾或出错返回的是NULL,因此可以用返回值是否为NULL判定是否读取结束。代码如下:
int line_number2()
{
FILE* fp = fopen("./test_line.txt", "r");
if (!fp)
{
perror("open");
exit(0);
}
int i = 0;
char temp[256];
char* p = NULL;
while (1)
{
p = fgets(temp, sizeof(temp), fp);
if (NULL == p)
{
fclose(fp);
return i;
}
i++;
}
}
main函数如下:
int main()
{
int lines1, lines2;
lines1 = line_number1();
//printf("lines1=%d\n", lines1);
lines2 = line_number2();
printf("lines1=%d lines2=%d\n", lines1, lines2);
}
输出
lines1=2 lines2=3
结果符合要求。
8 关于Windows中换行被当成两个字符的问题
现在还有一个疑问,为什么读到换行符时,光标会向后移动两格?
新建b.txt,内容如下:
void test_n() {
FILE* fp1 = fopen("b.txt", "r");
char ch;
long i;
while (1)
{
i = ftell(fp1);
printf("%d\t", i);
ch = fgetc(fp1);
i = ftell(fp1);
printf("%d\t", i);
if (feof(fp1))
break;
printf("%d\n", ch);
}
}
int main() {
//test_feof();
//test_feof2();
test_n();
return 0;
}
输出:
0 1 97
1 3 10
3 5 10
5 6 98
6 8 10
8 10 10
10 10
因为在Windows中,从内存往磁盘存储时,换行符被当成两个字符处理,因此在读取的时候,使用fgetc读是只读到一个字符,但光标却是移动两格
这个不用深究,只需要知道在Windows系统读取磁盘中的存在这种现象就行。