使用 C 语言的 fopen 打开文件时,可以指定的 mode 有 12 个,其中 6 个包含 "b"
使用 C++ 的 fstream 打开文件时,可用的模式组合有 24 个(?),其中 12 个包含 "binary"
使用 python 的 open 打开文件,除了可以使用 C 中的 12 个模式外,还可以使用 "U" 或 "rU"
使用 Qt 库的 QFile 打开文件时,可以指定 QIODevice::Text 或不指定
如此种种,看起来是如此的复杂,难怪很多刚接触编程的网友都不相信(或者不想相信):
这一切仅仅是为了一个小小的换行符!
是啊,一个小小的换行符值得如此大动干戈么?
哎,等等...
你前面提的C中的"b",C++中的"fstream::binary",Qt的"QFile::Text",我都知道啊:不是区分文本和二进制操作的么?和换行符有什么关系?!
那么我们有必须要看看:
所有的文件都是二进制文件(Binary File)
换句话说:本来就不存在 文本文件 这个独立类别,文本文件属于二进制文件。
如果这样,为何C、C++等等打开文件是都提供文本和二进制两种模式么?(暂不解释^_^)
考虑一个例子:打开文件(不管后缀名等等),分别写入:
"/x10/x11/x12/x13/x14" |
不可见字符 |
"/x30/x31/x32/x33/x34" |
"01234" |
而后者由于全部是可打印字符,你可能就会称其为文本文件。
注意区分两个概念:当我们提C、C++打开文件的方式时,我们一直在说 文本模式 和二进制模式,而不是说打开 文件文件 和二进制文件。这中间有很微妙的区别。
任何一个文件,你都可以用文本或二进制模式打开。但是对于 *.png 等这些东西,你用文本模式打开读进来的往往不是你期望的结果。
考虑这样一个文件 hello.txt,其内容:
line1/r/nline2/r/n
如果在windows下:你用文本模式打开,读进来多少个字符?用二进制模式打开,又是多少个字符?为何同一个文件,读进来的不一样?
我们前面提到(C、C++、Python、还有不该和语言并列Qt)的文件操作,都是需要通过系统调用对文件进行操作的。具体一点:
HANDLE WINAPI CreateFile( __in LPCTSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile );
参数很多,每一个参数又有很多标记位组成(具体看MSDN)。但是你可以发现:对它来说,不存在文本文件和二进制文件的区别,你也无法设置text或binary等标记位!!
int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); int creat(const char *pathname, mode_t mode);
同样,这儿可以设置flags和mode,可以设置的标记很多。但是就是没有提供text和binary相关的东西!!
是不是很有意思?
是时候谈 换行符 了:
想象一下,一个文本编辑器打开一个"文本文件",遇到哪个字符开始换行呢?
应用程序和操作系统通常用1到2个字符代表换行:
CR+LF |
Windows、DOS、Symbian、Palm ... |
LF |
GNU/Linux、Mac OS X、FreeBSD ... |
CR |
Mac OS 9(之前)... |
LF+CR |
Acom BBC |
RS |
QNX 在posix之前 |
NEL |
z/OS、i5/OS ... |
... |
... |
这些之中,其实我们也只对 CR+LF 与 LF 这两种换行符感兴趣。
本来一切很正常的:
在Windows下:
调用 CreateFile 打开文件
HANDLE hFile = CreateFile (TEXT("twoline.txt"), GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
调用 WriteFile 写入两行
DWORD dwBytesWritten; WriteFile (hFile, "line1/r/nline2/r/n", 14, &dwBytesWritten, NULL);
调用CloseHandle关闭文件
CloseHandle(hFile);
在Posix系统下
int fd = open("twolines.txt", O_WRONLY|O_CREAT);
write(fd, "line1/nline2/n", 12);
close(fd);
各个平台相安无事,windows下你想换行就用‘/r/n‘,posix下想换行就用‘/n‘
各个平台的换行符不一致,一旦涉及跨平台问题就出来了。
考虑一下,如果使用C语言的binary模式的话,我们想生成一个像前面一样包含两行代码的文件,该怎么办?
#ifdef _WIN fwrite("line1/r/nline2/r/n"); #else fwrite("line1/nline2/n"); #endif
fwrite("line1/nline2/n");
应该就是为了这个吧,引入了一个"文本模式"
正是为了这个换行符,所以C、C++、Python等语言提供的文件操作函数才都有了Text、Binary两种模式:
#include <stdio.h> FILE *fopen(const char * restrict filename, const char * restrict mode);
除了文件名之外,还要传递一个 mode 的字符串作为标记。而这些标记分为带b和不带b两类:
文本 |
二进制 |
||
r |
rb |
只读或只写 |
文件必须存在 |
w |
wb |
文件存在则清空、不存在则创建 |
|
a |
ab |
追加;文件不存在则创建 |
|
r+ |
r+b 或 rb+ |
读写 |
同r和rb |
w+ |
w+b或 wb+ |
同w和wb |
|
a+ |
a+b或 ab+ |
同a和ab |
explicit fstream ( const char * filename,ios_base::openmode mode = ios_base::in | ios_base::out );
除了文件名之外,我们需要传递一个 mode:
app |
(app end) 每次写操作前找到文件尾 |
ate |
(at e nd) 打开文件后立即将文件定位到文件尾 |
binary |
(binary ) 以二进制模式进行IO操作 |
in |
(in put) 允许读操作 |
out |
(out put) 允许写操作 |
trunc |
(trunc ate) 打开文件时清空文件流 |
这样看似乎没神马意思哈?一般都是组合使用的:
out |
只写 |
清空文件内容 |
out|app |
追加 |
|
out|trunc |
等同out |
|
in |
只读 |
|
in|out |
读写 |
|
in|out|trunc |
清空文件内容 |
也就是:带binary和不带binary的组合数目一样多的
bool QFile::open ( OpenMode mode )
这儿是mode又是什么东西?
QIODevice::NotOpen |
QIODevice::ReadOnly |
QIODevice::WriteOnly |
QIODevice::ReadWrite |
QIODevice::Append |
QIODevice::Truncate |
QIODevice::Text |
QIODevice::Unbuffered |
现在国内用linux的似乎越来越多了,很多人有这个问题:
linux下创建了一个包含中文的文件,拷贝到windows下面。 用记事本打开看 ==> 汉字正确,换行的地方出现了黑方块 用写字板打开看 ==> 换行正确,汉字乱码
很有意思?可是如何解决?
如果就用windows系统自带的记事本 和写字板 怎么办?看好了:
http://www.cplusplus.com/reference/iostream/fstream/fstream/
http://blog.csdn.net/dbzhang800/article/details/6430280
原文:http://www.cnblogs.com/findumars/p/5111579.html