我正在尝试学习 C,目前正在编写玩具脚本。 现在,它只是打开一个文本文件,一个字符一个字符地读取它,然后 将其吐出到命令行上。
我查看了如何查看文件的大小(使用 fseek() 然后是 ftell()), 但它返回的结果与我在遍历文件时在 while 循环中计算字符数得到的数字不匹配。
我想知道差异是否是由于 Windows 使用\r\n 而不仅仅是\n,因为差异似乎是 #newlines+1。
下面是我正在处理的脚本:
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE * fp = fopen("test.txt", "r");
fseek(fp, 0, SEEK_END);
char * stringOfFile = malloc(ftell(fp));
printf("allocated %d characters for file\n", ftell(fp));
fseek(fp,0,SEEK_SET);//reset pointer
char tmp = getc(fp); //current letter in file
int i=0;
while (tmp != EOF) //End-Of-File (defined in stdio.h)
{
*(stringOfFile+i) = tmp;
tmp = getc(fp);
i++;
}
fclose(fp);
printf("Turns out we had %d characters to store.\nThe file was as follows:\n", i);
printf("%s", stringOfFile);
}
我得到的输出(带有一个简单的测试文件,您可以从输出中看到)是:
allocated 67 characters for file
Turns out we had 60 characters to store.
The file was as follows:
line1
line2
line3
line4
line5
(last)line6
lmnopqrstuvw▬$YL Æ
由于为字符串分配了太多内存,打印的尾部似乎是垃圾。
在此先感谢您提供的任何帮助/回答!
最佳答案
如果您运行的是 Windows:
FILE * fp = fopen("test.txt", "r");
以文本模式打开文件,这意味着 \r\n
转换为 \n
因此,如果您的文件有 7 行,则转换会删除 7 个字符(也就是说,如果文件使用的是 Windows 样式的行终止)
修复是以二进制模式打开它
FILE * fp = fopen("test.txt", "rb");
所以 ftell
和逐个读取字符应该匹配。
当然,这会浪费空间并且在文本中使用 \r
字符不是很方便,因此您可以像现在这样分配,最后执行 realloc
用实际字符数缩小分配的内存(因为它更小,没关系)
stringOfFile = realloc(stringOfFile,i+1);
请注意,由于我已经考虑到需要添加空终止符,所以我在字符数中添加了 1,因此如果没有任何 \r
字 rune 件,realloc
可以将 block 的大小增加 1。
所以,正如我所暗示的,不要忘记以 nul 终止您的字符串,否则 printf
不会正确停止:
stringOfFile[i] = '\0';
(除非您不关心创建 C 字符串,因为存储字符串大小 + 逐字符显示也是正确的)
我们已经看到 ftell
方法很棘手,在某些情况下,例如当流是命令的输出时(popen
返回一个 FILE *
但你不能 fseek
它)或套接字,无论如何,这个原则不能应用,因为我们事先不知道数据的大小。
在一般情况下,最好:
- 分配一个小缓冲区
- 逐个读取一个字符并存储
- 如果缓冲区已满,调用
realloc
将大小增加一些(不是在每个字符上,性能会很差) - 最后再次调用
realloc
来更精确的调整大小
(也透明地解决了二进制/文本问题)
请注意,如果您正在处理大文件 (>4GB),则必须使用 64 位无符号整数作为位置和 fopen64
风格的 I/O 函数(以及所有偏移量变量,例如 i
应该是无符号的/符合 ftell
的返回类型,否则你将在 2GB 时开始遇到问题)。好吧,我想在处理中等规模的文本文件时这并不重要。
另外,检查 David 的回答。对于文本文件,将 getc
的结果放在 char
中应该可行,但在一般情况下对于二进制文件则不行。
关于C 文件大小差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50154719/