在Windows编程中,程序默认的文字编码是
UTF-16 LE,使用16位
wchar_t类型的字符串保存,字符串常量有L前缀,即
L"字符串常量"。这种编码的结构十分简单,并且可以表示世界上的所有字符。使用UTF-16可以大大简化字符串的编程。
UTF-16字符分为基础平面字符、扩展平面字符。基础平面为16位,扩展平面32位,扩展平面保存为两个特定范围的16位值。UTF-16的码位定义如下:
0000 - D7FF 基础平面
D800 - DBFF 扩展平面的前导字
DC00 - DFFF 扩展平面的尾随字
E000 - FFFF 基础平面
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接收转换的字符串,以防止该字符串过早地被释放:
CStringA ansi_str = CW2A(unicode_str);
CStringW unicode_str = CA2W(ansi_str);
CStringA utf8_str = CW2A(unicode_str, CP_UTF8);
CStringW unicode_str = CA2W(utf8_str, CP_UTF8);
CStringA big5_str = CW2A(unicode_str, 950);
CStringW unicode_str = CA2W(big5_str, 950);
在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),或者这样使用:
int oldmbcp = _getmbcp();
_setmbcp(_MB_CP_SBCS);
_setmbcp(oldmbcp);
在MFC或ATL中,CString也可以作缓冲区使用。预分配缓冲区使用
str.GetBufferSetLength(分配字符个数),释放缓冲区(并确认字符串)使用
str.ReleaseBufferSetLength(确认字符个数)。其中ReleaseBufferSetLength(-1)表示缓冲区内是传统C字符串,CString将使用strlen或wcslen确定字符串长度。
CStringA bufstr;
char *buf = bufstr.GetBufferSetLength(readlen);
readlen = file.Read(buf, readlen);
bufstr.ReleaseBufferSetLength(readlen);
我们从其它地方读取的数据中,经常会出现一些等于0的字符,这在C字符串中是不允许的,CString的某些成员函数在处理这些不规则字符串时,也会产生错误的结果,比如MakeUpper。CString中删掉0字符的方法很简单,
str.Replace('\0', ' '),即可将0替换成空格。注意Replace有两种形式,其中参数为两个C字符串的形式str.Replace("string 1", "string 2"),是无法用于替换0字符的。
bufstr.Replace('\0', ' ');
因为UTF-8或MBCS字符可能是变长的(实际上,世界上大多数现代化的字符编码都是变长的,包括UTF-16),因此按块读入很容易导致单个字符被截断,按流读入的话编程又过于复杂,所以个人建议每次读取时将整个文件全部读入内存(毕竟大于2GB的文本文件很少见,现在内存大小也不是问题),这是操作上最方便的选择。
读取文件:
void CMFCApplication2Dlg::OnBnClickedButton1()
{
_setmbcp(_MB_CP_SBCS);
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);
bufstr.Replace('\0', ' ');
readstr = CA2W(bufstr, CP_UTF8);
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();
}
}
写入文件比较简单:
void CMFCApplication2Dlg::OnBnClickedButton2()
{
_setmbcp(_MB_CP_SBCS);
CFile file1;
CString writestr;
try
{
file1.Open(L"sss.txt", CFile::modeWrite|CFile::modeCreate);
m_edit.GetWindowText(writestr);
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();
}
}
运行截图:
200字以内,仅用于支线交流,主线讨论请采用回复功能。