转自: http://blog.chinaunix.net/uid-26790551-id-3190813.html

我们传统的程序基本都只在Windows或只在Linux下运行,Windows程序使用简体
中文GB18030编码,Linux程序则只使用英文,多年以来这些程序运行起来都没有
问题。

近年来,随着程序的组件化,部分代码特别是公用组件都需要同时支持Windows
及Linux平台,这样就出现了不同程度的编码问题,例如在编译时编译器报错,
或者在运行时出现乱码。这些问题都和程序选用的字符编码不正确有关。

本文简要地分析了C++的一些字符编码问题,并提供了建议的方案。受经验和时
间的限制,有些内容可能不一定全面,仅供大家参考。

1. C++源文件的编码需要特别考虑吗?
1.1. 几个相关概念

首先要区分几个概念:

C++源文件的编码

指的是C++源程序文件(.cpp/.h)本身使用什么字符编码(GB18030/UTF-8等)。

C++程序的内码

编译后,C++中的字符串常量都会变成一串字节存放在可执行文件中。这个内
码指的就是在可执行文件中,字符串以什么编码进行存放。这里的字符串常量
指的是窄字符(char)而非宽字符(wchar_t)。宽字符通常是以Unicode(VC
使用UTF-16BE,gcc使用UTF-32BE)存放。

运行环境编码

指的是执行程序时,操作系统或终端所使用的编码。程序中输出的字符最终要
转换为运行环境编码才能正确显示,否则就会出现乱码。

1.2. 各种环境下通常使用的编码
C++源文件的编码

通常在简体中文Windows环境下,各种编辑器(包括Visual Studio)新建文件的
缺省编码都是GB18030,所以不特别指定的话,Windows环境下C++源文件的编码
通常为GB18030。

而在Linux环境下,最常使用,也是推荐使用的是UTF-8编码。

C++程序的内码

一般来说,我们常用的简体中文版VC所使用的内码是GB18030,而gcc/g++使用的
内码缺省是utf-8,但可以通过-fexec-charset参数进行修改。

Note
可以通过在程序中打印字符串每个字节十六进制形式来判断程序所使用的内码。
运行环境编码

我们常用的简体中文版Windows的环境编码是GB18030,而Linux下最常用的环境
编码是UTF-8。

1.3. 这几个编码之间的关系

源程序需要由编译器编译为目标文件,目标文件运行后输出信息到终端,因此这
几个编码之间存在一些的关联:

+--------+ | 源程序 |----------源文件编码 +---+----+ | 编译器编译 +---+----+ |目标文件|----------程序内码 +---+----+ | 运行后输出信息 +---+----+ | 输出 |----------运行环境编码 +--------+
  • 编译器需要正确识别源文件的编码,把源文件编译为目标文件,并把源文件中
    的以源文件编码的字符串转换为以程序内码编制的字符串保存在目标文件中。

    Note
    当源文件的字符编码与程序内码都是UTF-8时(gcc的缺省情况),gcc似乎并不会对源文件中的字符编码进行转换,而是直接把字符串原样存放到目标文
    件中,在这种情况下,源程序中的GB18030编码的字符串在输出时仍然为GB18030
    编码。但如果在其它源文件字符编码的实际值与编译选项不同时,会在编译时报无法从XXX转换到UTF-8的错,因此还不清楚为什么两个编码都是UTF-8时,GB18030 编码的源文件能通过编译。
  • C++标准库需要正确识别终端的运行环境编码,并把程序的输出转换为运行环
    境所使用的编码,以便正确显示。

在这过程中,如果有一个环节出现问题,就会导致程序的输出发生异常,产生乱
码或其它更严重的后果。

2. 源文件应该采用什么编码?
2.1. 编译器对不同源文件编码的支持一样吗?

根据 http://stackoverflow.com/questions/688760/how-to-create-a-utf-8-string-literal-in-visual-c-2008
一文中提供的资料,gcc/vc各版本对C++源文件编码有不同的处理:

支持UTF-8编码的源文件,UTF-8编码的源文件可以有BOM,也可以没有。

  • vc2005+:

如果源文件使用UTF-8编码的话,必须有BOM。

Note
gcc提供了-finput-charset参数可以指定源文件的字符编码,但由于标准
头文件都是ascii编码的,因此如果要引用标准头文件的话,源代码的编码必须兼容ascii。而vc未能找到类似的选项。
2.2. 源文件应该采用什么编码?

很多文章都推荐C/C++代码中只使用ascii字符,如果有非ascii字符可以用\xHH
或\uXXXX表示。注释中建议使用utf-8编码。也可以使用gettext 把非ascii字符串放到单独的语言文件中,而在源代码中只保留ascii字符。

在实践中,由于\xHH或\uXXXX等方式很不直观,容易出错且不易发现,而未必所
有程序都需要支持多语言,因此未必想引入gettext或类似的解决方案。在这样
的情况下,大家都习惯在源程序文件中直接写入中文等非ascii字符,这就需要
选择一种至少能被gcc和vc接受的文件编码。

本来,Unicode是解决多语言问题的最好选择,而UTF-8由于与ASCII兼容,也是
最通用的Unicode编码方式,但从上面的资料中可见,如果用UTF-8的话,gcc(
至少是低版本)不允许有BOM,而vc2005 以上要求必须有BOM,因此同一个文件
无法在gcc及vc下通过编译,UTF-8似乎不是一个好的选择。但如果使用gcc比较
高的版本(4.4.0以上?),使用带BOM的UTF-8编码文件应该也是可行的。

考虑到目前现状,我们一般都在简体中文Windows下工作,源文件中使用GB18030
编码似乎是一个比较现实的选择。在vc下可以直接编译,而在gcc下也可以通过
增加编译选项-finput-charset=gb18030予以支持。而且根据维基百科中GB18030
的词条内容,GB18030 is a superset of ASCII and can represent the whole
range of Unicode code points(GB18030向后兼容ASCII,并且能表示所有的
Unicode码点),因此使用GB18030有足够的表达能力,可以表示所有的Unicode
字符。使用GB18030的唯一缺点就是在非简体中文版本的VC下,由于无法指定源
文件的编码,因此有可能无法正确识别此编码的源文件。

3. 应该使用什么程序内码?

正如前面提到的,C++有窄字符(char)和宽字符(wchar_t)的分别,分别有一
套相应的类和函数(string/cout/strlen与wstring/wcout/wcslen等)。前者在
不同的编译器下有不同的缺省编码(简体中文vc是GB18030,gcc是UTF-8),后
者一般都使用Unicode,其中vc下使用UTF-16,gcc缺省使用UTF-32。

C++在输出窄字符时会按程序内码原样输出,不会进行编码转换,因此在使用窄
字符时要求程序内码与运行环境编码一致,这样才不会出现乱码。由于简体中文
版vc的程序内码是GB18030,因此使用窄字符的vc程序只能运行在GB18030环境下
。同样,由于gcc缺省使用UTF-8作为程序内码,因此使用窄字符的gcc程序只能
运行在UTF-8的终端环境下。(这里说的都是在源代码中直接写中文等非ascii字
符的程序。用前面提到的gettext及其它工具,使用窄字符的程序也可以在不同
编码的运行环境中正确输出中文)

C++在输出宽字符时会自动转换为运行环境的编码,因此只要正确设置了运行环
境编码,同一个程序就可以在不同编码的运行环境中正确显示中文。这一点与
Java/.Net很象,Java/.Net的字符串类型都使用Unicode,在输入/输出时都需要
与当前运行环境的编码进行互转。

一般来说,如果需要支持多语言,有两种比较好的做法:

  1. 使用窄字符,但源程序中只使用ascii字符,非ascii字符通过gettext或其它
    工具放到单独的文件中,由gettext等工具处理编码转换的问题。

    • 在各种编码的运行环境中均能正确输出中文。

    • 程序中不能直接出现非ascii字符,也不能通过\uXXXX方式指定非ascii字符,后者也会被编译器转换为非ascii字符并存放在目标文件中。

    • 注释中可以使用ascii兼容的编码,不影响编译器。

    • 有比较多的现成代码可供重用。

  2. 使用宽字符。

    • 在各种编码的运行环境中均能正确输出中文。

    • 程序中可以使用非ascii字符。

    • 需要配合前面的源程序文件编码设置,让编译器能正确识别源程序中的非
      ascii字符。

    • 由于以前使用宽字符的程序比较少,可供重用的代码较少。

Note
如果程序中需要一些固定字符编码的字符串常量,例如固定是GB18030
编码的字符串常量,这些常量应该以\xXX的方式存放字符串常量经GB18030编码后的内容,这样的内容才不会被转换为程序的内码,也不会转换为运行环境编码。
4. 运行环境应该用什么字符编码?

正如上面提到的,使用窄字符和使用宽字符的程序对运行环境的字符编码要求是
不一样的。

  • 使用宽字符,只要在程序中正确设置当前环境的字符编码(一般通过locale::global(locale("")) 进行设置),C++标准库会在输入、输出时正
    确进行字符编码转换,因此可以适应各种编码的运行环境。

  • 使用窄字符,但程序中不出现非ascii字符的话,对运行环境没有特别要求,
    可以适应各种编码的运行环境。

  • 使用窄字符,程序中也直接使用汉字等非ascii字符的话,由于C++标准库会把
    目标文件中保存的字符串(以程序内码保存)直接输出,不会进行字符编码转换,因此要求运行环境的编码与程序内码一致。即简体中文VC编译的程序只能运行在GB18030环境下,gcc编译的程序只能运行在UTF-8环境下(可以在编译时通过-fexec-charset参数进行修改)。

5. C++源文件编码的选择
5.1. 几种可行做法

根据上面的讨论,目前看来,要兼容Windows/Linux,VC/gcc的话,有几种做法

  1. 使用窄字符,源程序中只使用ascii字符,非ascii字符,如中文等通过
    gettext等工具放到单独的语言包中。

    • 这种做法比较多人推荐。

    • 兼容VC及gcc各版本。

    • 由于源程序中不出现非ascii字符,因此不需要考虑源程序文件的编码问题。

    • 兼容各种编码的运行环境。

  2. 使用窄字符,源程序中允许使用非ascii字符。

    • 要求运行环境的编码与程序内码一致,即只支持GB18030编码的Windows及
      UTF-8编码的Linux。

    • 根据源程序使用的编码不同,对编译器的兼容性也不同:

      1. 使用窄字符,源程序使用带BOM的UTF-8编码。

        • 兼容VC各语种的各版本。

        • 兼容gcc 4.4.0以上版本。

      2. 使用窄字符,源程序使用GB18030编码。

        • 兼容VC的简体中文各版本。

        • 兼容gcc各版本,但在编译时需要加上-finput-char=gb18030参数。

  3. 使用宽字符,源程序中允许使用非ascii字符。

    • 兼容各种编码的运行环境。

    • 根据源程序使用的编码不同,对编译器的兼容性也不同:

      1. 使用窄字符,源程序使用带BOM的UTF-8编码。

        • 兼容VC各语种的各版本。

        • 兼容gcc 4.4.0以上版本。

      2. 使用窄字符,源程序使用GB18030编码。

        • 兼容VC的简体中文各版本。

        • 兼容gcc各版本,但在编译时需要加上-finput-char=gb18030参数。

5.2. 推荐做法

根据我们的现状,对于需要支持多语种的程序,建议使用窄字符,源程序中只使
用ascii字符。

对于不需要支持多语种的程序,考虑到重用已有的代码,可以考虑使用窄字符,
采用GB18030编码,但只能运行在GB18030编码的Windows环境及UTF-8编码的
Linux环境下。

6. 其它问题
6.1. 用户输入、输出及持久化

由于用户输入、输出及从文件、网络等设施读写的数据在程序底层看来都是字节
流,因此存在在输入时如何把这些字节流解释成有效的信息,在输出时怎么把程
序中的信息转换为正确的字节流的问题。

  • 如果程序本身不需要处理这些数据,只是把数据从一个来源搬到另一个地方(
    如把用户输入保存到文件,或者从一个流读入,写到另一个流等),而输入的字符编码与输出的字符编码一致的话,程序不需要对数据进行任何编码转换,只需要把读入的数据按原样写到输出即可,数据的字符编码与程序的编码没有关系。

    比如网站应用程序,只需要保证用户页面使用UTF-8编码,数据库、数据文件也都使用UTF-8编码,那么用户输入的数据可以直接写入数据库及数据文件,从数据库或数据文件中读取的数据也可以直接展现给用户,不需要进行编码转换。

  • 如果程序需要在一定程序上对数据进行处理(如需要判断字符个数、对字符进
    行比较、在字符串上附加或去掉内容),就要把数据转换为一种明确的字符编码,一般来说是程序内码,再进行处理,在处理后再转换为所需的字符编码进行输出。

    • 对于宽字符程序,如果只需要处理采用当前运行环境字符编码的数据,可以通过ios::imbue()可以指定io流的字符编码,在输入、输出时C++标准库会自动在所指定的字符编码与程序内码之间进行编码转换。如果不使用流的话,也可以通过标准的wcstombs()或mbstowcs()函数进行当前编码(通过locale::global()或setlocale()指定)与宽字符之间的转换。

    • 对于窄字符程序,如果数据的字符编码与程序内码一致也不需要进行编码转换,直接处理即可。

    • 对于其它情形,需要引入iconv或类似的字符编码转换库,以便实现不同
      字符编码之间的转换。

6.2. gettext、iconv的替代品

由于gettext及iconv都属于GNU Project,考虑到版权因素,并非所有程序,特别是商业程序,都适合使用这些库。在Boost 1.48.0中,Boost.Locale库首次正式发布,该库提供了gettext、iconv的功能,并在此基础上进行了增强,提供了大小写变换、字符顺序比较、时间的处理 、分词、数字的格式化输入/输出、消息格式化、多语种支持、字符编码转换等功能,值得进一步研究及使用。

关于C++程序的编码问题的更多相关文章

  1. 【转】关于C++程序的编码问题

    引用自:http://blog.chinaunix.net/uid-26790551-id-3190813.html 我们传统的程序基本都只在Windows或只在Linux下运行,Windows程序使 ...

  2. mvn从下载安装到纯命令行创建第一个mvn程序(编码,编译,测试,安装,打包)全过程细致分解

    1.maven的下载和安装: a.maven的下载注意事项:如果你是windows,请选择①号,如果你是linux,请选择②号,下载地址:http://maven.apache.org/downloa ...

  3. Java程序的编码规范

    所有的程序开发手册都包含了各种规则.一些习惯自由程序人员可能对这些规则很不适应,但是在多个开发人员共同写作的情况下,这些规则是必需的.这不仅仅是为了开发效率来考虑,而且也是为了后期维护考虑. 一.命名 ...

  4. 运行Python2.x程序报编码错误的解决办法-UnicodeDecodeError: 'ascii' codec can't decode byte 0xb7 in position 7: ordina not in range(128)

    Python编码问题的终极解决方案:在python的Lib\site-packages文件夹下新建一个sitecustomize.py文件,输入: import sys sys.setdefaulte ...

  5. 微信小程序base64编码解码以及utf-8解码

    function base64_encode (str) { // 编码,配合encodeURIComponent使用 var c1, c2, c3; var base64EncodeChars = ...

  6. [转]JavaScript程序编码规范

    原文:http://javascript.crockford.com/code.html 作者:Douglas Crockford 译文:http://www.yeeyan.com/articles/ ...

  7. c++程序编码

    c++程序中涉及到中文字符的输入输出以及其他操作经常会出现乱码.乱码主要是由于程序的源文件编码.可执行文件编码以及程序运行环境的编码不匹配导致.比如,c++源程序文件编码为GB18030, 在源程序中 ...

  8. java程序应为CRT登录时启动未设置编码,造成启动乱码

    1.以下提供CRT连接程序设置编码脚本,后缀为“.vbs” # $language = "VBScript"# $interface = "1.0" Sub M ...

  9. 前端学HTTP之实体和编码

    前面的话 每天都有各种媒体对象经由HTTP传送,如图像.文本.影片以及软件程序等.HTTP要确保它的报文被正确传送,识别.提取以及适当处理.为了实现这些目标,HTTP使用了完善的标签来描述承载内容的实 ...

随机推荐

  1. self关键字

    self关键字 self:当前类/对象的指针(指向当前对象/方法调用者) 作用1 当类里有变量名和成员变量名一样的时候,可以使用self区分 例: 我们写一个人的类,有一个年龄属性,在get方法里,我 ...

  2. CentOS7.2安装Weblogic12c出现的问题

    Weblogic12c安装到步骤:Prerequisite  Checks 时,会进行操作系统版本的校验,即checking  operating  system  certification. 此处 ...

  3. 快速了解 Robot Operating System(ROS) 机器人操作系统

     http://www.ros.org/ 关于ROS About ROS http://www.ros.org/about-ros/ 机器人操作系统(ROS)是用于编写机器人软件的灵活框架.目的在简化 ...

  4. 初识Spring Boot框架

    前面的铺垫文章已经连着写了六篇了,主要是介绍了Spring和SpringMVC框架,小伙伴们在学习的过程中大概也发现了这两个框架需要我们手动配置的地方非常多,不过做JavaEE开发的小伙伴们肯定也听说 ...

  5. JAVA面向对象-----包机制

    JAVA面向对象-–包机制 问题: 当定义了多个类的时候,可能会发生类名的重复问题. 在java中采用包机制处理开发者定义的类名冲突问题. 怎么使用java的包机制呢? 1.使用package 关键字 ...

  6. 详解EBS接口开发之库存事务处理-物料批次导入

    库存事务处理-物料批次导入 --系统批次表 SELECT * FROM MTL_LOT_NUMBERS T; --API创建批次 inv_lot_api_pub.create_inv_lot(x_re ...

  7. SQLite 分离数据库(http://www.w3cschool.cc/sqlite/sqlite-detach-database.html)

    SQLite 分离数据库 SQLite的 DETACH DTABASE 语句是用来把命名数据库从一个数据库连接分离和游离出来,连接是之前使用 ATTACH 语句附加的.如果同一个数据库文件已经被附加上 ...

  8. lucene索引库的增删改查操作

    1. 索引库的操作 保持数据库与索引库的同步 说明:在一个系统中,如果索引功能存在,那么数据库和索引库应该是同时存在的.这个时候需要保证索引库的数据和数据库中的数据保持一致性.可以在对数据库进行增.删 ...

  9. Java之equals和==详解

    两者的区别: A:== 基本类型:比较的是值是否相同 引用类型:比较的是地址值是否相同 B:equals() 只能比较引用类型. 默认情况下,比较的是地址值是否相同,因为我们可以看源代码可以看到,在O ...

  10. android viewpager切换到最后一页时,跳转至其他activity

    许许多多的qpp,在启动时会显示一些帮助页或者产品介绍什么的,通常滑动到最后一页时会有一个开始体验的按钮,用来跳转到app的主activity上,但是如果没有? 可以通过如下方法实现: 1.为View ...