c实现多语言编程,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] = '\0';
#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 作为转换的中介。可以参阅 iconv 相关网站。
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 做了数学上的转化。(更多关于 Unicode 和 UTF8 的概念,可以参见 有关文献)
其次,关于第三方库的选择,应当综合考虑项目的需求。一般的文本字符转换,系统的库函数已经可以满足需求,实现也很简单;如果需要针对不同地区的语言、文字、习惯进行编程,需要更为丰富的功能,当然选择成熟的第三方工具可以事半功倍。
最后,从逻辑上保持字符串的编码正确,需要注意几条一般规律:
编码选择:多国语言环境的编程,以使用 UTF 编码为原则,减少字符集转换。
string 并不包含编码信息,但是编码确定了 string 的二进制内容。
读写一致:读入时使用的字符集要与写出时使用的一致。如果不需要改变字符串内容,仅仅是将字符串读入、再写出,建议不要调整任何字符集——即使程序使用的系统默认字符集 A 与文件的实际编码 B 不符合,写出的字符串依然会是正确的 B 编码。
读入已知:对于必须处理、解析或显示的字符串,从文件读入时必须知道它的编码,避免处理字符串的代码简单使用系统默认字符集;即便对于程序从系统中收集到的内存字符串,也应知道其符合的编码格式——一般为系统默认字符集。
避免直接使用 Unicode:这里是说将非 ASCII 编码的 16 进制或者 10 进制数值用 与 ; 包含起来的使用方式,例如将中文“一”写成“e00;”。这种方法的实质是 Unicode 编码直接写入文件。这不仅会降低代码的通用性、输出文件的可读性,处理起来也很困难。比如法文字符在其他字符集中是大于 80H 的单字节字符,程序同时要支持中文的时候,很有可能会将多字节的中文字符错误割裂。
避免陷入直接的字符集编程:国际化、本地化的工具已经比较成熟,非纯粹做编码转换的程序员没有必要自己去处理不同编码表的映射转换问题。
Unicode/UTF8 并不能解决一切乱码问题:Unicode 可以说是将世界语言统一起来的一套编码。但是这并不意味着在一个系统中可以正常显示的按照 UTF8 编码的文件,在另一个系统中也可以正常显示。例如,在中文的 UTF8 编码或者 Unicode 编码在没有东亚语言包支持的法文系统中,依然是不可识别的乱码——尽管 UTF8、Unicode 它们都支持。
学习从 Nicolar M. Josuttis, C++ Standard Library, The: A Tutorial and Reference 这本书,或者在线链接 c++ reference可以获得 C++ 标准库有关信息
wikipedia 上有
Unicode 相关的概念解说
在 developerWorks 上查阅所有 Linux 技巧 和 Linux 教程。
获得产品和技术下载
IBM 软件试用版,体验强大的 DB2?,Lotus?,Rational?,Tivoli?和
WebSphere?软件。
讨论
现供职于 IBM 上海 CSTL,Tools Center 项目组,主要进行 C/C++,XML,服务器工具方面的程序开发。有 6 年 JAVA 和 C/C++ 开发经验,拥有上海交大计算机硕士学位。
c实现多语言编程,C/C++编程中多国语言处理相关推荐
- c# winform项目多语言实现(支持简/繁/英三种语言),Winform中多国语言窗体设计.doc...
Winform中多国语言窗体设计.doc Winform中多国语言窗体的设计以及.NET中资源文件的使用 最近在做一些多国语言的软件,就学习了一下.NET中的多国语言相应知识,受益不少,下面我就简单介 ...
- C/C++ 编程中多国语言处理
C/C++ 编程中多国语言处理 简介: 多国语言的存在,使程序员在编码处理上花费了大量时间和精力:然而各种各样的乱码问题,如 XML 格式错误.文本显示异常.解析器异常等依然层出不穷.特别的,相对于 ...
- 中职升高职c语言程序设计教程课后答案,中职C语言教学创新与实践论文
中职C语言教学创新与实践论文 摘要:自主学习体现了以学生为中心.以人为本的教学思想,是一种行之有效的教学方法,但中职学生自主学习能力整体比较欠缺,学生怕学.厌学现象严重,给教师的教学带来一定的难度.文 ...
- c语言实现快速排序对文件中字符,C语言中快速排序和插入排序优化的实现
快速排序快速排序思想 1962年,由C.A.R.Hoare创造出来.该算法核心思想就一句话:"排序数组时,将数组分成两个小部分,然后对它们递归排序".然而采取什么样的策略将数组分成 ...
- WD_考研计算机C语言基础002【统考中的C语言】
动手能力 数据结构:每年一题 .8年 线性表 .14,17二叉树 操作系统 --11年大纲"并能利用C语言描述相关算法." --大题 PV操作 组成原理 --14年 并能对高级程序 ...
- php 多语言cms,帝国cms模板实现多国语言切换
语言包解决方案(中英文为例,其他可自行添加) 1 .复制gb语言包,放入目录:edatalanguage中,重命名为:"zh"; 另加入语言包(除gb语言包以外):针对模板的中文进 ...
- Android实现应用内语言切换,android 应用内切换 多国语言
微信中可以动态设置,apk的语言版本,最近尝试实现了这个功能. 我们知道,android本身就支持多国语言版本的. 不过这个语言的切换 依赖于系统的的语言设置,见图 也依赖apk本身values多国语 ...
- 3_1符合python语言变量_以下选项中符合Python语言变量命名规则的是
[单选题]以下选项中,不是Python语言特点的是 [单选题]较小的尺寸应离轮廓线较近,较大的尺寸线离轮廓线较远.( ) [单选题]关于Python语言的变量,以下选项中说法正确的是 [单选题]182 ...
- keil4如何将c语言转换成汇编语言_Keil 中关于C语言编译生成汇编代码函数名规则...
在keil 中 C语言的函数有带参数和不带参数之分. 一般的资料里说fun(void)类型的函数不带参数,所以,keil编译器生成的汇编的调用地址(函数名) 为fun.这没有错.事实上,不管C语言的函 ...
最新文章
- 逼疯懒癌:“机器学习100天大作战”正式开始!
- 自学python的书-推荐6本学习Python的免费电子书
- python json库安装_win 安装python的cjson库
- Android大图片裁剪终极解决方案 原理分析
- linux进程号转换成16进制,Shell脚本中不同进制数据转换的例子(二进制、八进制、十六进制、base64)...
- 架构之美阅读笔记之一
- 汇编语言之常见的汇编指令
- BugkuCTF-MISC题zip伪加密
- Windows7系统资源怎么看?
- arm qt mysql插件_Ubuntu下编译ARM平台Qt的MySQL插件
- 【opencv学习】【读取图像、视频、摄像头】
- Pytest之skip、skipif、xfail
- sql server新增列_SQL Server 2016安装中的新增功能?
- Nginx日志配置及分割
- python imagedraw line_修复PIL.ImageDraw.Draw.宽线条线条线条
- ZooKeeper 概念讲的好
- Python-数据类型-4 第七次全国人口普查数据分析
- 触屏java版象棋下载 游戏下载安装_中国象棋官方版下载
- 微信小程序使用建行支付
- 硬盘重新分区,给C盘重新分配空间
热门文章
- 【已解决】单片机串口通讯中RXD与TXD如何连线?
- Winodws10 VS2019 Clion CMake 编译 OpenCV rc 系统找不到指定的文件。NMAKE : fatal error U1077
- 6a标准 api_【阀门标准】API SPEC 6A CHINESE-2010中文版.pdf
- pycharm安装scrapy失败_Scrapy ——环境搭配与一个简单的例子
- 算法练习day10——190328(根据指定值划分单链表、复制含有rand指针节点的链表、两个单链表相交)
- Qt 实现串口终端控制台,适配RT-Thread的FinSH控制台功能(提供qt源码)
- C/Cpp / 设计模式 / 单例模式(线程安全)
- c++输出小数点后几位_2.1 怎么在屏幕上输出各种类型的数据
- phpstudy一直自动停止启动_发动机自动启停,到底是省油还是毁车?
- vscode怎么写qt项目_使用VSCode 编译调试QT程序