赞
踩
在MFC中,有时候需要以特定编码格式(比如ANSI、UTF-8、UTF-16)保存文本文件。为了能够深入理解MFC读写文件的操作原理,先讲解BOM头和代码页的概念。
在Windows操作系统中,当你使用Unicode时,需要熟悉的一个概念是BOM(byte-order-mark)。BOM一般是指文本文件开头的几个字符,根据这几个字符,文本编辑器能够决定如何对文本文件进行解码。
下表显示不同编码的BOM。
在微软的官方文档中,是这样描述BOM的
BOM用于指示处理器如何将序列化文本放入字节序列中。如果最低有效字节位于初始位置,则称为“小端序”,而如果最高有效字节位于起始位置,则该方法称为“大端序”。
同时,BOM也可以用作文件的指示器,以确定文本文件的编码方式以及字节序列。比如记事本就可能根据文件保存时所选的编码,在文件开始处添加相应的文件指示器。此签名允许记事本稍后重新打开文件,将字节正确解释为Unicode,而不是某些未知、隐式和不明确的代码页。
现在问题来了,微软自家的文档会加上BOM头,可是其他家的文档未必会加BOM头啊,怎么办?实际上,对很多文字编辑器来说,它会自动识别,这个时候,文件的编码格式就要根据文件的内容逐个去分析了,也就是说没有绝对的方式能够完全正确的知道文件的编码格式,因此可能不准。
在MFC中,文本文件可以保存为ANSI的编码格式,此时不需要BOM头。但是,ANSI到底代表什么呢?以下进行初步讲解。
在Windows平台下,进入DOS窗口,输入:chcp,可以得到操作系统的代码页信息,你可以从控制面板语言选项中查看代码页对应的详细的字符集信息。
例如:我的活动代码页:936,它对于的编码格式为GBK。
一个代码页是所选字符集的列表。一个代码页通常定义一个或者一组具有相同书写系统的语言。通常这些语言的前127个码点相同,和ASCII码相同。但是高位的128位码点(128-255)却有显著区别。
例如,代码页1253提供了希腊书写系统的字符集;而1252提供了拉丁书写系统的字符集,这包括英语、德语、法语等。高位的128个代码点包含重音字符或希腊字符。因此,除非包含与文本分离的某种类型的标识符,以指示顶部使用的代码页,否则不能将希腊语和德语存储在同一代码流中。
当处理亚洲文字的时候,情况变得更为复杂。中文、日文、韩文包含超过256个字符,需要发展一种基于字节的代码页新方案。因此双字节(DBCS)和更通用的MBCS诞生了。在DBCS和MBCS中,许多字符是用两个或者2个以上字符表示的。例如中国,使用两个字节表示汉字,产生了GB2312编码,后来又升级出新的编码GBK编码。台湾因为使用繁体字,不和GBK兼容,于是又自己弄了个繁体字编码-大五码(Big-5)。韩国人自己搞的编码叫韩EUC-KR编码.
因为各个国家有自己的文字,对自己的文字有自己的编码方式。Windows为了保证能在不同语言的地区使用,就采用了标准代码页(code page)的方法,即把全世界的编码方式都聚集到一起并编上号,在不同的地方采用对应地方的编码方式,比如简体中文GBK编码就是936代码页,繁体中文 Big5编码就是950代码页。
当记事本或者软件采用Windows代码页中对应的编码方式就是“ANSI”编码,在不同的地区“ANSI”编码是不同的。比如在中国,“ANSI”就是GBK编码;在韩国就是EUC-KR编码。默认的“ANSI”编码方式可以通过修改Windows的区域来修改。
从ANSI编码的特点可以看出,ANSI注定必须和代码页相关,那么现在新的问题来了:如何利用ANSI在一个文件流中既显示中文,又显示日文?答案是不能。为了允许不同语言在同一个数据流中被保存,Unicode被创立了。这种单一编码可以表示64000多个字符,使用替代编码可以表示100多万个字符。使用Unicode可以更容易地创建面向世界的代码,因为您不必再担心正在寻址的代码页,也不必担心是否必须将字符点分组以表示一个字符。
在MFC中,可以利用CFile类进行文件读写。但是需要注意以下几个问题。
1.文本模式还是二进制模式
注意CFile在打开一个文件时可以选择是文本模式还是二进制模式。当按照文本方式向文件中写入数据时,一旦遇到“换行”字符(ASCII码为10,也就是转义字符’\n’),则会转换为“回车-换行”(ASCII码分别为13、10,也就是转义字符’\r’‘\n’)。在读取文件时,一旦遇到“回车-换行”组合(连续的ASCII码为13、10),则会转换为换行字符(ASCII为10)。当按照二进制方式向文件中写入数据时,则会将数据在内存中的存储形式原样输出到文件中。
2. 输出为UNICODE编码格式时增加BOM头
为了防止读取文件出现乱码,在以Unicode编码时应当加上BOM头(这个仅限于Windows系统)。
3. 编译器的执行字符集决定了输出的具体编码
MFC的执行字符集决定了最终文本字符的编码,那么执行字符集是什么呢?
第一种情况:以VC++为例, 如果是窄字符/字符串(以char为单位),那么执行字符集是由系统代码页决定的,比如在中文Windows系统下就采用GBK编码。如果是宽字符/字符串(以wchar为单位),那么执行字符集是UTF-16 LE(这是VS2017下的结果)。另外,VS可以通过/execution-charset设置执行字符集(具体可参见微软官方文档:https://learn.microsoft.com/zh-cn/cpp/build/reference/execution-charset-set-execution-character-set?view=msvc-150)
第二种情况:如果字符/字符串前有指定编码方式,那没什么好说的了,就采用指定的编码方式。
具体请参见下面案例。
void CFileView::OnWrite() { //利用打开对话框实现文件写入 CFileDialog dlgOpen(FALSE); dlgOpen.m_ofn.lpstrTitle=_T("我的文件保存对话框"); dlgOpen.m_ofn.lpstrFilter=_T("绘图文件(*.dwg)\0*.dwg\0Text Files(*.txt)\0*.txt\0All Files(*.*)\0*.*\0\0"); dlgOpen.m_ofn.lpstrDefExt=_T("txt"); if(IDOK==dlgOpen.DoModal()) { CFile file(dlgOpen.GetPathName(),CFile::modeCreate|CFile::modeWrite); //加了这个BOM头之后就不用文本编辑器猜编码格式了 char pch[2] = { 0xff,0xfe }; file.Write(pch, 2); //字符的执行字符编码(VS是UTF-16 LE)是什么,就写入什么。 //因此,下面这个字符串输出到文件以后,编码格式就是UTF-16 LE。 CString str = _T("https://www.baidu.com/我\nThis is second Line!"); file.Write(str,str.GetLength()*sizeof(TCHAR)); /* 以下是一个典型案例,由于Write就是单纯的写数据,所以当前VS执行字符集的执行字符编码是什么,就将这样的 编码写入到文件。刚刚说过了,如果是窄字符串,那么以ANSI(因为的Code Page为936,因此是GBK编码的)编码。 这样,对abc这样的ASCII字符,采用1个字节,对‘我’这样的汉字,采用两个字节,字符串编码是:61 62 63 ce d2 , 占有5个字节,其中'我'是 ce d2.而函数strlen返回的是字符的个数,也就是4,所以下面的函数输出了4个字节 的编码,即:61 62 63 ce,如果再次打开这个文件,解码自然出错。 */ //file.Write("abc我", strlen("abc我") * sizeof(char)); file.Close(); }
文件读取的话,需要注意读取完成后,要在缓冲区末尾加上’\0’。
void CFileView::OnRead() { //利用打开对话框实现文件打开 CFileDialog dlgOpen(TRUE); dlgOpen.m_ofn.lpstrTitle=_T("我的文件打开对话框"); dlgOpen.m_ofn.lpstrFilter=_T("绘图文件(*.dwg)\0*.dwg\0Text Files(*.txt)\0*.txt\0All Files(*.*)\0*.*\0\0"); dlgOpen.m_ofn.lpstrDefExt=_T("txt"); if(IDOK==dlgOpen.DoModal()) { CFile file(dlgOpen.GetPathName(),CFile::modeRead); TCHAR* pBuf; DWORD dwFileLen; dwFileLen=file.GetLength(); pBuf=new TCHAR[dwFileLen/sizeof(TCHAR)+1]; file.Read(pBuf,dwFileLen); pBuf[dwFileLen/sizeof(TCHAR)]=0; file.Close(); MessageBox(pBuf); delete pBuf; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。