【C++】小心使用文件读写模式:回车('r') 换行('n')问题的一次纠结经历

时间:2022-05-08
本文章向大家介绍【C++】小心使用文件读写模式:回车('r') 换行('n')问题的一次纠结经历,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

原来没有仔细注意C++读写文件的二进制模式和文本模式,这次吃了大亏。(平台:windows  VS2012)

BUG出现:

写了一个程序A,生成一个文本文件F保存在本地,然后用程序B读取此文件计算MD5值。

将该文件上传到服务器,再用程序B将文件从服务器上下载下来计算MD5值,神奇的发现两次计算的MD5值不一样,文件被谁改了??

排除问题:

1.首先对比了生成文件F和上传到服务器的文件,发现文件复制过程无差错,是同一个文件。

2.用程序B下载文件F后,保存在本地,发现文件与原文件F不一致,对比二进制发现每行多了一个r。

3.怀疑服务器传输前对文件格式进行了更改,用wireshark抓包,发现文件内容与服务器上文件一致。那么这个多出来的r从何而来呢,行结尾变成了rrn。

4.查看文件F,行结尾是rn,而我记得当初生成文件的时候是以n作为换行符的,纠结一番后想起来了文件读写的模式,只记得是文本与二进制的区别,没有想起来换行符的问题。

5.几经纠结,查阅C++ primer plus后恍然大悟,都是默认使用文本模式读写文件惹的祸:windows下,文本模式会将n输出成rn,读取时也会将rn变成一个n;所以开始程序B读取文件F并且计算MD5时,是以n来计算的。然而当从服务器上下载下来时,文件是以rn作为行结尾的,直接计算MD5会导致值不一样。而将下载下来的文件保存时,由于仍然使用的文本模式,将rn变成了rrn,导致了当初匪夷所思的结果。

总结:

这BUG从出现到调查各方面的原因排除花费了大量的时间,说到底还是因为基础不扎实,这里讲《C++ primer plus》的关键一段话抄下来作为提醒。

“使用二进制文件模式时,程序将数据从内存传递给文件(反之亦然)时,将不会发生任何隐藏的转换,而默认的文本模式并非如此。例如,对于Windows文本文件,他们使用两个字符的组合吧(回车和换行)表示换行符;Mac文本文件使用回车表示换行符;而UNIX和Linux文件使用换行来表示换行符。C++是从UNIX系统上发展而来的,因此也使用换行来表示换行符。为增加可移植性,Windows C++程序在写文本模式文件时,自动将C++换行符转换为回车和换行;Mac C++程序在写文件时,将换行符转换为回车。在读取文本文件时,这些程序将本地换行符转换为C++模式。对于二进制数据,文本格式会引起问题,因为double值中间的字节可能与换行符的ASCII码有相同的位模式。另外,在文件末尾的检测方式也有区别。因此以二进制格式保存数据时,应使用二进制文件模式。”

后续验证:

后来写了一个小程序验证了一下所知,不懂的话可以复制下来跑一下,注意是Windows平台,生成的文件可以用wxHexEditor来查看以二进制形式查看。另外再说一点题外的,不用语言的字符串类型编码可能会不同,例如JavaScript里是UTF-16,而C++默认的是ANSI,下载下来同一个文件计算MD5值的话可能会有问题。

 1 #include <iostream>
 2 #include <fstream>
 3 #include <string>
 4 using namespace std;
 5 int main()
 6 {
 7 
 8     string str1 = "hello!n";
 9     ofstream fout("file1");//默认文本模式
10     fout << str1;
11     fout.close();
12 
13     ifstream fin("file1");
14     char ch = 0;
15     string temp;
16     if (fin) {
17         while (fin.get(ch))
18             temp += ch;
19         cout << "读入file1长度:"<<temp.length() <<endl;
20         fin.close();
21     }
22 
23     string temp2;
24     fin.open("file1", ios::binary);//以n作为换行
25     getline(fin, temp2);
26     cout << "二进制模式getline读入file1的长度(结尾包含了\r):" << temp2.length() << endl;
27     fin.close();
28 
29     ofstream fout2("file2");
30     fout2 << "hello!rn";
31     fout2.close();
32 
33     string temp3;
34     fin.open("file2");
35     if (fin) {
36         getline(fin, temp3);
37         cout << "文本模式getline读入file2的长度(同样多了一个\r):" << temp2.length() << endl;
38     }
39 
40     return 0;
41 }