可以查看EOF的宏定义
函数fgetc
如果读取失败就返回-1,对于文本文件而言,以为着读取结束,因此-1可以作为结束的标志。
#define _CRT_SECURE_NO_WARNINGS
#include
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
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;
}
假如-1(EOF对应数字)被写进了文件中,那么读取的时候,也会被正常读出来,如果仍然用fputc的返回值是否为-1来判断文件是否读取结束,那么就会出现bug。
例如:
#define _CRT_SECURE_NO_WARNINGS
#include
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
int main() {FILE* fp = fopen("a.txt", "r");char ch;while ((ch = fgetc(fp)) != EOF) printf("%c", ch);return 0;
}
输出
abc
也就是说,当读到-1的时候,就退出了while
循环,后面的不会正常打印
为了避免上述问题的产生,可以使用 feof函数
#define _CRT_SECURE_NO_WARNINGS
#include
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;
}
上面的程序,假如写和读在同一个源程序中,但写指针和都指针使用不同的名字,程序如下:
#define _CRT_SECURE_NO_WARNINGS
#include
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,即读取失败。具体原因我不知道,但一个程序中,不能出现多个文件指针指向同一个文件
#define _CRT_SECURE_NO_WARNINGS
#includeint 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
#includeint 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
上面两个程序的区别在于,前者是先判断后读,后者是先读后判断。
为什么先读后判断,和先判断后读,会出现上面的差别?
好的,我们现在就来找原因
现有一个名为a.txt的文件,内容如下:
现在使用先判断后读的方式,查看光标的变化,设计如下程序
#define _CRT_SECURE_NO_WARNINGS
#includevoid 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读取失败才行。
至此,先读后判断和先判断后读的差别,原因也找到了。
使用函数feof判断文件是否读取结束,与fgetc和fgets两种读取函数配合时的表现是不一样的。当feof与fgetc配合使用时,fgetc只有读取失败返回-1之后,feof才认为文件读取结束,而feof与fgets配合时,只需要最后一行读完,feof就认为文件读取结束,无需等到读取失败。
我们来看一下feof与fgets配合使用时的的情形。新建一个名为test_line.txt的文件,内容如下:
新建一个统计行数(文件有多少行)的函数,内容如下:
#define _CRT_SECURE_NO_WARNINGS
#includeint 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
结果符合要求。
现在还有一个疑问,为什么读到换行符时,光标会向后移动两格?
新建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系统读取磁盘中的存在这种现象就行。