当前位置:首页 > 工业控制 > 电子设计自动化
[导读]摘要:多国语言的存在,使程序员在编码处理上花费了大量时间和精力;然而各种各样的乱码问题,如 XML 格式错误、文本显示异常、解析器异常等依然层出不穷。特别的,相对于 JAVA 语言,C/C++ 在处理编码问题上有更大的

摘要:多国语言的存在,使程序员在编码处理上花费了大量时间和精力;然而各种各样的乱码问题,如 XML 格式错误、文本显示异常、解析器异常等依然层出不穷。特别的,相对于 JAVA 语言,C/C++ 在处理编码问题上有更大的困难。本文避免纠缠不同编码格式的具体异同,以 Unicode 为核心,以简体中文为例,从工程应用角度分析编码问题存在的原因,不仅提出 C/C++ 标准库编程的解决方案,更结合项目经验,总结出处理多国语言编码问题的一般思路。

问题的提出

多国语言的存在、不同语言操作系统的存在,使得针对多语言的设计颇费周章,在编码上所付出的工作量也是可观的。所谓编码的问题,归结起来,就是二进制的编码以何种编码格式进行解析的问题。特别是在硬盘文件和内存数据的相互转化、即读写过程中,如果采用了错误的编码格式,就会造成乱码。JAVA 语言在字符串、编码等处理方面给了程序员更为直接、方便的接口,习惯使用 JAVA 做编码的程序员,在使用 C/C++ 进行文本编码相关的操作时,常会感到困惑。本文的目的在于以常用的 Unicode(UCS-2)、GB2312、UTF8 三种编码为例,分析不同编码在实用中的关系,特别是 C/C++ 中,怎样处理各种编码的问题。

编码处理常见的问题

1. 将内存中编码 A 的字符串以编码 B 格式处理成字节流写入文件

2. 将原本以 A 编码组成的文件以字节流形式读入内存、并以编码 B 解析为字符串。

第一种情况,可能造成数据的变化、失真。

如果使用 JAVA 语言,发生这种错误的情况稍少一些,因为在 JAVA 中没有 wstring 这种概念,在内存中的 String,使用的编码都是 Unicode,其中的转换对于程序员来讲是透明的。只要使用输入 / 输出方法时注意字节流的字符集选择即可。

例如,编码为中文 GB2312 的“标准”字符串被读入内存后转存为 UTF8 的过程:


图 1. 文件转换编码的 JAVA 处理方式

但 C/C++ 编程,由于通常使用 char、string 类型的时候比较多,特别是进行文件读写,基本都是操作 char* 类型的数据。并且也没有像 JAVA 中 getByte(String charsetname) 这种函数,不能直接根据字符集重新编码得到字符串的 byte 数组。这时候,我们使用的 string 其实就一般不是 Unicode,而是符合某种编码表的。这使得我们往往困惑于 string 的编码问题。假设有 utf8 的字符串“一”(E4 B8 80),而我们错误的认为它是符合 gb2312(编码 A)的,并将其转换为 utf8(编码 B),这种转换结果是破坏性的,错误的输出将永远无法正确识别。

依然以“标准”为例,这是一个正确的转换:


图 2. 文件转换编码的 C/C++ 处理方式

第二种情况,则是更常见到的。例如:浏览器浏览网页时的发生的乱码问题;在写 XML 文件时,指定了 < ?xml version="1.0" encoding="utf-8" ?> 然而文件中却包含 GB2312 的字符串——这样经常会导致 XML 文件 bad formatted,而使得解析器出错。

这种情况下,其实数据都是正确的,只要浏览器选择正确的编码,将 XML 文件中的 GB2312 转换为 UTF8 或者修改 encoding,就可以解决问题。

需要注意的是,ASCII 码的字符,即单字节字符,一般不受编码变动影响,在所有编码表中的值是一样的;需要小心处理的是多字节字符,例如中文语言。

编码转换方法

一般的编码转换,直接做映射的不太可能,需要比较多的工作量,大多情况下还是选择 Unicode 作为转换的中介。

使用库函数

如前文所说,JAVA 的 String 对象是以 Unicode 编码存在的,所以 JAVA 程序员主要关心的是读入时判断字节流的编码,从而确保可以正确的转化为 Unicode 编码;相比之下,C/C++ 将外部文件读出的数据存为字符数组、或者是 string 类型;而 wstring 才是符合 Unicode 编码的双字节数组。一般常用的方法是 C 标准库的 wcstombs、mbstowcs 函数,和 windows API 的 MultiByteToWideChar 与 WideCharToMultiByte 函数来完成向 Unicode 的转入和转出。

这里以 MBs2WCs 函数的实现说明 GB2312 向 Unicode 的转换的主要过程:

清单 1. 多字节字符串向宽字节字符串转换

wchar_t * MBs2WCs(const char* pszSrc){

wchar_t* pwcs = NULL;

intsize = 0;

#ifdefined(_linux_)

setlocale(LC_ALL, "zh_CN.GB2312");

size = mbstowcs(NULL,pszSrc,0);

pwcs = new wchar_t[size+1];

size = mbstowcs(pwcs, pszSrc, size+1);

pwcs[size] = 0;

#else

size = MultiByteToWideChar(20936, 0, pszSrc, -1, 0, 0);

if(size <= 0)

returnNULL;

pwcs = new wchar_t[size];

MultiByteToWideChar(20936, 0, pszSrc, -1, pwcs, size);

#endif

returnpwcs;

}

相应的,WCs2MBs 可以将宽字符串转化为字节流。

清单 2. 宽字节字符串向多字节字符串转换

char* WCs2MBs(const wchar_t * wcharStr){

char* str = NULL;

intsize = 0;

#ifdefined(_linux_)

setlocale(LC_ALL, "zh_CN.UTF8");

size = wcstombs( NULL, wcharStr, 0);

str = new char[size + 1];

wcstombs( str, wcharStr, size);

str[size] = '';

#else

size = WideCharToMultiByte( CP_UTF8, 0, wcharStr, -1, NULL, NULL, NULL, NULL );

str = new char[size];

WideCharToMultiByte( CP_UTF8, 0, wcharStr, -1, str, size, NULL, NULL );

#endif

returnstr;

}

Linux 的 setlocale 的具体使用可以参阅有 C/C++ 文档,它关系到文字、货币单位、时间等很多格式问题。Windows 相关的代码中 20936 和宏定义 CP_UTF8 是 GB2312 编码对应的的 Code Page[ 类似的 Code Page 参数可以从 MSDN的 Encoding Class 有关信息中获得 ]。

这里需要特别指出的是 setlocale 的第二个参数,Linux 和 Windows 是不同的:

1. 笔者在 Eclipse CDT + MinGW 下使用 [country].[charset](如 zh_CN.gb2312 或 zh_CN.UTF8)的格式并不能通过编码转换测试,但可以使用 Code Page,即可以写成 setlocale(LC_ALL, ".20936") 这样的代码。这说明,这个参数与编译器无关,而与系统定义有关,而不同操作系统对于已安装字符集的定义是不同的。

2. Linux 系统下可以参见 /usr/lib/locale/ 路径,系统所支持的 locale 都在这里。转换成 UTF8 时,并不需要 [country] 部分一定是 zh_CN,en_US.UTF8 也可以正常转换。

另外,标准 C 和 Win32 API 函数返回值是不同的,标准 C 返回的 wchar_t 数组或者是 char 数组都没有字符串结束符,需要手动赋值,所以 Linux 部分的代码要有区别对待。

最后,还要注意应当在调用这两个函数后释放分配的空间。如果将 MBs2WCs 和 WCs2MBs 的返回值分别转化为 wstring 和 string,就可以在它们函数体内做 delete,这里为了代码简明,故而省略,但请读者别忘记。

第三方库

目前的第三方工具已经比较完善,这里介绍两个,本文侧重点不在此,不对其做太多探讨。

Linux 上存在第三方的 iconv 项目,使用也较为简单,其实质也是以 Unicode 作为转换的中介。

ICU 是一个很完善的国际化工具。其中的 Code Page Conversion 功能也可以支持文本数据从任何字符集向 Unicode 的双向转换。

实验测试

在代码中调用“编码转换方法”一节里提到的函数,将 gb2312 编码的字符串转换为 UTF8 编码,分析其编码转换的行为:

在英文 Linux 环境下,执行下列命令:

export LC_ALL=zh_CN.gb2312

然后编译并执行以下程序(其中汉字都是在 gb2312 环境中写入源文件)

L1:wstring ws = L"一";

L2:string s_gb2312 = "一";

L3:wchar_t * wcs = MBs2WChar(s_gb2312.c_str());

L4:char* cs = WChar2MBs(wcs);

查看输出:

L1 - 1 wide char: 0x04bb

L2 - 2 bytes:0xd2,0xbb,即 gb2312 编码 0xD2BB

L3 - 返回的 wchar_t 数组内容为 0x4E00,也就是 Unicode 编码

L4 - 将 Unicode 再度转换为 UTF8 编码,输出的字符长度为 3,即 0xE4,oxB8,0x80

在 L1 行,执行结果显示编码为一个 0x04bb,其实这是一个转换错误,如果使用其他汉字,如“哈”,编译都将无法通过。也就是说 Linux 环境下,直接声明中文宽字符串是不正确的,编译器不能够正确转换。

而在中文 windows 下使用相同测试代码,则会在 L1 处出现区别,ws 中的 wchar_t 元素十六进制值是 0x4e00,这是汉字“一”的 Unicode 编码。

处理编码问题的经验总结

首先,这里先简单说明一下 Unicode 和 UTF8 的关系:Unicode 的实现方式和它的编码方式并不相同,UTF8 就是其实现之一。比方使用 UltraEdit 打开 UTF8 编码的中文文件,使用 16 进制查看,可以发现看到的中文对应部分应当是 Unicode 编码,每个中文字长度 2 字节—— UltraEdit 在这里已经做了转化;如果直接查看其二进制文件,可以发现是 3 字节。但两者的差别仅在于 Unicode 向 UTF8 做了数学上的转化。

其次,关于第三方库的选择,应当综合考虑项目的需求。一般的文本字符转换,系统的库函数已经可以满足需求,实现也很简单;如果需要针对不同地区的语言、文字、习惯进行编程,需要更为丰富的功能,当然选择成熟的第三方工具可以事半功倍。

最后,从逻辑上保持字符串的编码正确,需要注意几条一般规律:

编码选择:多国语言环境的编程,以使用 UTF 编码为原则,减少字符集转换。

string 并不包含编码信息,但是编码确定了 string 的二进制内容。

读写一致:读入时使用的字符集要与写出时使用的一致。如果不需要改变字符串内容,仅仅是将字符串读入、再写出,建议不要调整任何字符集——即使程序使用的系统默认字符集 A 与文件的实际编码 B 不符合,写出的字符串依然会是正确的 B 编码。

读入已知:对于必须处理、解析或显示的字符串,从文件读入时必须知道它的编码,避免处理字符串的代码简单使用系统默认字符集;即便对于程序从系统中收集到的内存字符串,也应知道其符合的编码格式——一般为系统默认字符集。

避免直接使用 Unicode:这里是说将非 ASCII 编码的 16 进制或者 10 进制数值用 &# 与 ; 包含起来的使用方式,例如将中文“一”写成“&#4e00;”。这种方法的实质是 Unicode 编码直接写入文件。这不仅会降低代码的通用性、输出文件的可读性,处理起来也很困难。比如法文字符在其他字符集中是大于 80H 的单字节字符,程序同时要支持中文的时候,很有可能会将多字节的中文字符错误割裂。

避免陷入直接的字符集编程:国际化、本地化的工具已经比较成熟,非纯粹做编码转换的程序员没有必要自己去处理不同编码表的映射转换问题。

Unicode/UTF8 并不能解决一切乱码问题:Unicode 可以说是将世界语言统一起来的一套编码。但是这并不意味着在一个系统中可以正常显示的按照 UTF8 编码的文件,在另一个系统中也可以正常显示。例如,在中文的 UTF8 编码或者 Unicode 编码在没有东亚语言包支持的法文系统中,依然是不可识别的乱码——尽管 UTF8、Unicode 它们都支持。



来源:神秘0次

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭