Visual C++编码转换和读写文本文件
acmilan2015/09/01软件综合 IP:四川
在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

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接收转换的字符串,以防止该字符串过早地被释放:
<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>

运行截图:

捕获.png

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

来自:计算机科学 / 软件综合
1
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
acmilan 作者
9年4个月前 修改于 9年4个月前 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注册,5年10个月前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}