MFC中如何以UNICODE编码格式读写文件

在MFC中,有时候需要以特定编码格式(比如ANSI、UTF-8、UTF-16)保存文本文件。为了能够深入理解MFC读写文件的操作原理,先讲解BOM头和代码页的概念。

一 BOM(Byte order mark)

在Windows操作系统中,当你使用Unicode时,需要熟悉的一个概念是BOM(byte-order-mark)。BOM一般是指文本文件开头的几个字符,根据这几个字符,文本编辑器能够决定如何对文本文件进行解码。
下表显示不同编码的BOM。
在这里插入图片描述
在微软的官方文档中,是这样描述BOM的

BOM用于指示处理器如何将序列化文本放入字节序列中。如果最低有效字节位于初始位置,则称为“小端序”,而如果最高有效字节位于起始位置,则该方法称为“大端序”。
同时,BOM也可以用作文件的指示器,以确定文本文件的编码方式以及字节序列。比如记事本就可能根据文件保存时所选的编码,在文件开始处添加相应的文件指示器。此签名允许记事本稍后重新打开文件,将字节正确解释为Unicode,而不是某些未知、隐式和不明确的代码页。

现在问题来了,微软自家的文档会加上BOM头,可是其他家的文档未必会加BOM头啊,怎么办?实际上,对很多文字编辑器来说,它会自动识别,这个时候,文件的编码格式就要根据文件的内容逐个去分析了,也就是说没有绝对的方式能够完全正确的知道文件的编码格式,因此可能不准。

二 Code pages(代码页)与ANSI的关系

在MFC中,文本文件可以保存为ANSI的编码格式,此时不需要BOM头。但是,ANSI到底代表什么呢?以下进行初步讲解。

1 如何查看代码页

在Windows平台下,进入DOS窗口,输入:chcp,可以得到操作系统的代码页信息,你可以从控制面板语言选项中查看代码页对应的详细的字符集信息。
例如:我的活动代码页:936,它对于的编码格式为GBK。

2 Code Page的作用

一个代码页是所选字符集的列表。一个代码页通常定义一个或者一组具有相同书写系统的语言。通常这些语言的前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的区域来修改。
在这里插入图片描述

3 从ANSI到UNICODE

从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;
	}
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Santiago

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值