加载中
加载中
表情图片
评为精选
鼓励
加载中...
分享
加载中...
文件下载
加载中...
修改排序
加载中...
Visual C++编码转换和读写文本文件
acmilan2015/09/01软件综合 IP:四川
在Windows编程中,程序默认的文字编码是UTF-16 LE,使用16位wchar_t类型的字符串保存,字符串常量有L前缀,即L"字符串常量"。这种编码的结构十分简单,并且可以表示世界上的所有字符。使用UTF-16可以大大简化字符串的编程。

UTF-16字符分为基础平面字符、扩展平面字符。基础平面为16位,扩展平面32位,扩展平面保存为两个特定范围的16位值。UTF-16的码位定义如下:
YAML
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

MFCATL是简化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中定义了CA2WCW2A两个“函数”(实际上是临时对象)用以进行字符串转码,大大简化了编码转换的过程。使用CA2W和CW2A时,需使用CString接收转换的字符串,以防止该字符串过早地被释放:
Java
// 当前代码页 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);

在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),或者这样使用:
Other
int oldmbcp = _getmbcp(); _setmbcp(_MB_CP_SBCS); // 关闭 MBCS 算法                                                // ... 使用 CStringA 分析 UTF-8 字符串                                                _setmbcp(oldmbcp); // 恢复 MBCS 算法状态

在MFC或ATL中,CString也可以作缓冲区使用。预分配缓冲区使用str.GetBufferSetLength(分配字符个数),释放缓冲区(并确认字符串)使用str.ReleaseBufferSetLength(确认字符个数)。其中ReleaseBufferSetLength(-1)表示缓冲区内是传统C字符串,CString将使用strlen或wcslen确定字符串长度。
Other
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字符的。

Other
bufstr.Replace('\0', ' '); // 将0字节替换为空格,防止字符串被截断

因为UTF-8或MBCS字符可能是变长的(实际上,世界上大多数现代化的字符编码都是变长的,包括UTF-16),因此按块读入很容易导致单个字符被截断,按流读入的话编程又过于复杂,所以个人建议每次读取时将整个文件全部读入内存(毕竟大于2GB的文本文件很少见,现在内存大小也不是问题),这是操作上最方便的选择。

读取文件:
Other
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();     } }

写入文件比较简单:
Other
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();     } }

运行截图:

捕获.png

[修改于 10年0个月前 - 2015/09/03 01:58:21]

来自:计算机科学 / 软件综合
1
新版本公告
~~空空如也
acmilan 作者
10年0个月前 修改于 10年0个月前 IP:四川
788672
除UTF-16 LE以外,Windows只支持SBCS单字节编码(如437代码页)和DBCS双字节编码(如GBK、Big5)的字符串算法。Windows不提供MBCS多字节编码(如GB18030和UTF-8)和Esc序列编码(即ISO-2022或HZ编码格式)的字符串算法。对于MBCS多字节编码和Esc序列编码,Windows视作单字节编码处理。

中文用户日常能用到的编码和代码页如下:

Unicode等价编码:
54936(GB18030)以GBK为基础的Unicode传输形式(实际上就是UTF-GBK)
65000(UTF-7)以+和-分隔ASCII和特殊Base64的Unicode传输形式,纯ASCII
65001(UTF-8)以ASCII为基础的Unicode传输形式

老式ANSI编码:
936(GBK)简体中文Windows的默认编码(Win98默认编码,最常见)
950(Big5)繁体中文Windows的默认编码(Win98默认编码,最常见)
20000(CNS)以EUC编码的繁体中文CNS编码
20002(Eten)以EUC编码的繁体中文倚天码
20936(GB2312-80)以EUC编码的简体中文GB2312编码(老设备或嵌入式设备常见)
50227(ISO-2022-GB)简体中文的Esc序列编码,纯ASCII
50229(ISO-2022-CNS)繁体中文的Esc序列编码,纯ASCII
52936(HZ-GB-2312)以~{和~}分隔的简体中文GB2312编码,纯ASCII
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
acmilan
进士 学者 笔友
文章
461
回复
2934
学术分
4
2009/05/30注册,6年6个月前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的