在Windows编程中,程序默认的文字编码是
UTF-16 LE,使用16位
wchar_t类型的字符串保存,字符串常量有L前缀,即
L"字符串常量"。这种编码的结构十分简单,并且可以表示世界上的所有字符。使用UTF-16可以大大简化字符串的编程。
UTF-16字符分为基础平面字符、扩展平面字符。基础平面为16位,扩展平面32位,扩展平面保存为两个特定范围的16位值。UTF-16的码位定义如下:
<code class="lang-text">0000 - D7FF 基础平面
D800 - DBFF 扩展平面的前导字
DC00 - DFFF 扩展平面的尾随字
E000 - FFFF 基础平面</code>
UTF-16看上去很好,但是它有几个问题,导致它的普及困难:
1. 与经典的8位编码不兼容
2. 字节序问题:低字节优先(LE)和高字节优先(BE)
3. 字节错位问题:丢失一个字节,可能导致后续读取全部出错
UTF-16 LE在wchar_t数组这种16位媒介中表现良好,但是在Internet或TXT文件等8位媒介上,是可靠性较低的。Internet或TXT文件等一般可使用UTF-8这种编码,或者老式的MBCS(变长多字节字符集)编码(如GBK、Big-5)。Windows对于UTF-8和MBCS字符串以相同的方式支持,并以代码页区分它们,例如GBK的代码页为
936,Big-5的代码页为
950,而UTF-8的代码页为
65001。
MFC和
ATL是简化Windows编程的C++类库。其中MFC比较强大,使用MFC需要特殊的设置;而ATL则比较灵活,一般包含头文件即可使用(atlbase.h和atlstr.h),可以用于任何Windows程序中。在MFC和ATL中有一些共享的类,提供了字符串分析与编码转换的相关功能。
在MFC或ATL中,我们可以使用
CStringW承载UTF-16 LE字符串,而使用
CStringA承载ASCII、GBK、Big-5、UTF-8等单字节或变长多字节编码的字符串。CStringA和CStringW之间可以通过强制转换来转换编码,但是仅限当前区域的ANSI字符集(如简体中文的GBK、繁体中文的Big-5)与Unicode互相转换,并不支持自定义代码页。
对于任意代码页,在Windows中有MultiByteToWideChar和WideCharToMultiByte可以转换编码,但是需要自己管理内存,使用颇为不便。为了方便转码使用,MFC和ATL中定义了
CA2W和
CW2A两个“函数”(实际上是临时对象)用以进行字符串转码,大大简化了编码转换的过程。使用CA2W和CW2A时,需使用CString接收转换的字符串,以防止该字符串过早地被释放:
<code class="lang-cpp">// 当前代码页 ANSI 字符串与 Unicode 字符串互转(在简体中文系统上是GBK,代码页936)
CStringA ansi_str = CW2A(unicode_str);
CStringW unicode_str = CA2W(ansi_str);
// UTF-8 字符串与 Unicode 字符串互转,CP_UTF8 = 65001
CStringA utf8_str = CW2A(unicode_str, CP_UTF8);
CStringW unicode_str = CA2W(utf8_str, CP_UTF8);
// Big-5 字符串与 Unicode 字符串互转
CStringA big5_str = CW2A(unicode_str, 950);
CStringW unicode_str = CA2W(big5_str, 950);</code>
在MFC或ATL中,默认状态下CStringA使用MBCS算法进行字符串分析(_mbscpy、_mbscat、_mbsstr等),这种算法只适用于GBK或Big-5等DBCS(变长双字节字符集),并不适用于UTF-8。在处理UTF-8字符串时,可能会导致程序产生意外结果。因此如果使用UTF-8的话,务必关闭MBCS算法。关闭MBCS算法的方法是使用
_setmbcp(_MB_CP_SBCS),打开MBCS算法使用
_setmbcp(_MB_CP_ANSI),或者这样使用:
<code class="lang-cpp">int oldmbcp = _getmbcp();
_setmbcp(_MB_CP_SBCS); // 关闭 MBCS 算法
// ... 使用 CStringA 分析 UTF-8 字符串
_setmbcp(oldmbcp); // 恢复 MBCS 算法状态</code>
在MFC或ATL中,CString也可以作缓冲区使用。预分配缓冲区使用
XXXXXXtBufferSetLength(分配字符个数),释放缓冲区(并确认字符串)使用
XXXXXXleaseBufferSetLength(确认字符个数)。其中ReleaseBufferSetLength(-1)表示缓冲区内是传统C字符串,CString将使用strlen或wcslen确定字符串长度。
<code class="lang-cpp">CStringA bufstr;
char *buf = bufstr.GetBufferSetLength(readlen); // 分配缓冲区
readlen = file.Read(buf, readlen); // 读取数据
bufstr.ReleaseBufferSetLength(readlen); // 确认字符串并释放缓冲区</code>
我们从其它地方读取的数据中,经常会出现一些等于0的字符,这在C字符串中是不允许的,CString的某些成员函数在处理这些不规则字符串时,也会产生错误的结果,比如MakeUpper。CString中删掉0字符的方法很简单,
XXXXXXplace('\0', ' '),即可将0替换成空格。注意Replace有两种形式,其中参数为两个C字符串的形式XXXXXXplace("string 1", "string 2"),是无法用于替换0字符的。
<code class="lang-cpp">bufstr.Replace('\0', ' '); // 将0字节替换为空格,防止字符串被截断</code>
因为UTF-8或MBCS字符可能是变长的(实际上,世界上大多数现代化的字符编码都是变长的,包括UTF-16),因此按块读入很容易导致单个字符被截断,按流读入的话编程又过于复杂,所以个人建议每次读取时将整个文件全部读入内存(毕竟大于2GB的文本文件很少见,现在内存大小也不是问题),这是操作上最方便的选择。
读取文件:
<code class="lang-cpp">void CMFCApplication2Dlg::OnBnClickedButton1()
{
_setmbcp(_MB_CP_SBCS); // 关闭MBCS算法
CFile file1;
CString readstr;
try
{
// 打开文件
file1.Open(L"sss.txt", CFile::modeRead);
// 获得文件大小,并验证能否读取
ULONGLONG filelen = file1.GetLength();
if (filelen >= INT_MAX)
{
MessageBox(L"文件超过2GB,无法读取!");
return;
}
// 分配缓冲区
int readlen = (int)filelen;
CStringA bufstr;
char *buf = bufstr.GetBufferSetLength(readlen);
// 读取所有内容
readlen = file1.Read(buf, readlen);
// 确认并释放缓冲区
bufstr.ReleaseBufferSetLength(readlen);
// 替换所有0字节(重要!防止被截断)
bufstr.Replace('\0', ' ');
// 转换为UTF-16
readstr = CA2W(bufstr, CP_UTF8);
// 设置文本框字符串(m_edit是CEdit对象)
m_edit.SetWindowText(readstr);
}
catch (CMemoryException* e)
{
MessageBox(L"内存不足!"); e->Delete();
}
catch (CFileException* e)
{
MessageBox(L"读取文件失败!"); e->Delete();
}
catch (CException* e)
{
MessageBox(L"其它错误!"); e->Delete();
}
}</code>
写入文件比较简单:
<code class="lang-cpp">void CMFCApplication2Dlg::OnBnClickedButton2()
{
_setmbcp(_MB_CP_SBCS); // 关闭MBCS算法
CFile file1;
CString writestr;
try
{
// 打开文件
file1.Open(L"sss.txt", CFile::modeWrite|CFile::modeCreate);
// 获取文本框字符串(m_edit是CEdit对象)
m_edit.GetWindowText(writestr);
// 转换为UTF-8
CStringA bufstr = CW2A(writestr, CP_UTF8);
// 写入所有字节
file1.Write(bufstr, bufstr.GetLength());
}
catch (CMemoryException* e)
{
MessageBox(L"内存不足!"); e->Delete();
}
catch (CFileException* e)
{
MessageBox(L"写入文件失败!"); e->Delete();
}
catch (CException* e)
{
MessageBox(L"其它错误!"); e->Delete();
}
}</code>
运行截图:
200字以内,仅用于支线交流,主线讨论请采用回复功能。