从字符编码文件到宽字符编码流的编码在程序中能改变吗

1885人阅读
数据区(107)
参考,以字符流形式操作文件的时候,一定要指定正确的编码方案,否则会出现乱码等问题。
以字符流形式操作文件包括两种情形:以字符流的形式读取文件内容,将字符流写入文件中。
一、以字符流的形式读取文件内容
现在有一个文件a.txt,文件内容为“你好,Java程序”,采用UTF-8编码。接下来做2个实验。
实验代码:
package com.
import java.io.*;
import java.nio.charset.C
public class Encoding {
public static void main(String[] args) throws IOException {
BufferedReader reader =
new BufferedReader(new InputStreamReader(new FileInputStream(&a.txt&), Charset.forName(&utf-8&)));
while ((line = reader.readLine()) != null)
System.out.println(line);
实验结果:
实验代码:
package com.
import java.io.*;
import java.nio.charset.C
public class Encoding {
public static void main(String[] args) throws IOException {
BufferedReader reader =
new BufferedReader(new InputStreamReader(new FileInputStream(&a.txt&), Charset.forName(&gbk&)));
while ((line = reader.readLine()) != null)
System.out.println(line);
实验结果:
现在有一个文件b.txt,文件内容如下,采用UTF-8编码。接下来做2个实验。
你好这是第一行
你好这是第二行
实验代码:
package com.
import java.io.*;
import java.nio.charset.C
import java.util.S
public class Encoding {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(new File(&b.txt&), &utf-8&);
scanner.useDelimiter(&\\r\\n#Footer\\r\\n&);
while (scanner.hasNext())
System.out.println(scanner.next());
实验结果:
实验代码:
package com.
import java.io.*;
import java.nio.charset.C
import java.util.S
public class Encoding {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(new File(&b.txt&), &gbk&);
scanner.useDelimiter(&\\r\\n#Footer\\r\\n&);
while (scanner.hasNext())
System.out.println(scanner.next());
实验结果:
由于乱码,导致scanner.hasNext()返回值为false
二、将字符流写入文件中
将字符流写入文件中,做2个实验。
实验代码:
package com.
import java.io.*;
import java.nio.charset.C
import java.util.S
public class Encoding {
public static void main(String[] args) throws IOException {
BufferedWriter writer =
new BufferedWriter(new OutputStreamWriter(new FileOutputStream(&c.txt&), Charset.forName(&utf-8&)));
writer.write(&你好,Java程序&);
writer.close();
}实验结果:
查看c.txt文件的字节流如下图所示
实验代码:
package com.
import java.io.*;
import java.nio.charset.C
import java.util.S
public class Encoding {
public static void main(String[] args) throws IOException {
BufferedWriter writer =
new BufferedWriter(new OutputStreamWriter(new FileOutputStream(&c.txt&), Charset.forName(&gbk&)));
writer.write(&你好,Java程序&);
writer.close();
}实验结果:
当没有明确指定需要使用的字符编码方案时,Java程序通过“java.nio.charset.Charset.defaultCharset().name()”语句来获取默认的字符编码方案,该语句返回的值跟运行Java程序的操作系统的设置有关,在有些操作系统上,该语句返回值可能是UTF-8;在有些操作系统上,该语句返回值可能是GBK;在有些操作系统上,该语句返回值可能是除了UTF-8和GBK以外的其他字符编码方案。这样子,程序的可移植性大大降低。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:459367次
积分:7218
积分:7218
排名:第3039名
原创:302篇
转载:78篇
评论:23条
(10)(11)(29)(23)(25)(27)(15)(20)(15)(26)(39)(54)(54)(32)2814人阅读
C/C++(15)
Linux(21)
这篇文章的目的是希望你能在看完后对字符的编码和子节相关的东西,以及宽字符类型在不同平台之间的处理能有一个清晰的认识,有出入的地方,感谢指正。
“电脑只有二进制,人脑才有乱码”,凡是我们看到的乱码都是由字符编码引起的。如果对于字符编码没有一个清晰的认识,那么各种各样的编码格式在你的脑海里肯定是混乱的。首先,我们知道一个字节是由八个二进制位组成的,用十六进制表示就是0x00,这八个二进制位可以组合成256种不同的状态。最开始计算机只是在美国用的,为了在计算机的终端显示可见的东西,他们就把这8个二进制位所组成的状态约定成一些指定的字符,0x20以下的称为控制字符,如换行,反白之类的。然后他们又把空格啊,标点符号啊,数字字母之类的也指定成了特定的状态表示。这样一直约定到了127号,就算把英文字符编码完了,它们全都是用的一个字节来表示,这些编码就被称为“ascii”。但是当时发明计算机的人也没想到计算机能发展到除了他们美帝以外其他一些国家居然也可以用了,但是各个国家之间的语言字符多种多样,除了“ascii”编码的那些外,还有很多的字符没能在终端显示出来。所以他们继续把127号以后的进行编码,从128一直编到了255,这些又称为“扩展字符”。
但是,当计算机进入中国后,由于我们的汉子博大精深,那区区的一两百个状态怎么够啊,所以我们当然也开始了自己的编码指定。因为最开始的考虑不周,如没有考虑到少数名族语言之类的。单汉字编码就有了好几种。“GB2312”:127号之前的字符保持不变,直接废掉了127之后的那些扩展字符,约定两个127号之后的子节组合在一起编码成一个汉字,前面一个字节称为高子节,后面一个字节称为低字节,都是127号之后的子节。“GBK”:前127号保持不变,不再要求两个字节都是127号之后的子节了,只要第一个字节,也就是高子节,是127号之后的子节就行,第二个字节(低字节)不管是不是127号之后的子节都可以。“GB18030”:对“GBK”又进行了扩展,增加了更多的字符表示。这一系列的中文编码又总称位“DBCS“。
单中文就出现了这么多的编码格式,在世界范围内来看,其他的国家的情况也差不多。这些各种各样的编码都是你认你的我认我的,根本都没有一个统一的共识,这样的编码信息就无法实现信息的传输和共享了,得,要想认别国的字符,那你得装一套别国的编码系统。在这样的情况下,国际标准化组织(iso)就开始着手解决这个问题了,他们废除了国家地区性的编码,统一制定了一个编码格式,”Universal Multiple-Octet Coded Character Set”,简称UCS,
俗称 “unicode“。
unicode有两种格式,UCS2和UCS4,它们采用定长编码,UCS2指定2个字节编码一个字符,UCS4指定4个字节编码一个字符,在这样的约定下,所有国家的字符都采用这样的约定格式来进行重新编码,原有的ascii保持编码格式不变,只是将它扩展成了2个字节或者是4个字节。当然,你可能也发现了,这样的编码指定也存在一些问题,UCS2根本就不能编码出所有的字符,UCS4却可能是文本的长度成倍的增加,因为一些字符本可以用一个字节或者是两个字节就可以编码的。所以基于这样一些原因,而且随着互联网的出现,就有了后来的
UTF-8,UTF-16,UTF-32,它们都是Unicode编码格式的具体实现方式。UTF-8和UTF-16采用变长的编码方式,utf-8约定可以用1-4个字节来表示一个字符,utf-16可以用2个或者是4个字节来编码一个字符。utf-16可以说是ucs2的扩展,而utf-32和ucs4基本相同。关于utf-8的具体编码方式,我建议你可以看一下后面第二个链接阮一峰老师的文章,讲得很清楚。
上面我们说了字符编码,在计算机中还有字节序这个概念了,你肯定也听过大端序列和小端序列这个说法。在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节序列中最小的地址,因为不同机器之间处理器和系统的不同,字节序有大端序列(big-endian)和小端序列(little-endian)之分。大端序列指的是在对象的起始地址存储高序序列,小端序列指的是在对象的起始地址存储低序字节。如一个int类型的&#34567,用小端序列表示为:67
45 23 01,用大端序列表示为:01 23 45 67. 当然,在上面我们说的字符编码中,utf-8时没有大小端序列之分的。 经测试在Intel处理器上的win7 64位系统,Ubuntu 32位系统,OS X系统均为小端字节序。当然在我们常用的Intel处理器都是采用的小端字节序。可以用下面的代码来测试字节序:
#include &iostream&
c[sizeof(short)];
int main()
endian.s = 0x0102;
if(endian.c[0] == 1 && endian.c[1] == 2)
std::cout && &big endian& && std::
else if (endian.c[0] == 2 && endian.c[1] == 1)
std::cout && &little endian& && std::
std::cout && &unknow& && std::
宽字符类型(wchar_t)的跨平台处理
当我们需要把以前写的Windows程序进行跨平台处理时,如果原来的工程采用的Unicode编码,我想你肯定首先想到了,wchar_t类型在Windows和Linux平台下的大小时不一样的,Windows下采用的是2字节编码一个字符,基于utf-16,Linux下采用的是4个字节编码一个字符,基于utf-32。两个平台下的wchar_t类型sizeof出来的大小不同,那同一份代码进行跨平台处理的时候,会不会出问题呢,这个或许你就有点犯难了。
在这里,我们首先应该明确一点,wchar_t类型在Windows和Linux平台下字节大小的不同,对我们程序本身的跨平台性没有任何影响,你Windows下是怎么处理wchar_t的,那么在Linux下就怎么处理,相应的接口和操作都不用改变。不会对数据的读取产生错误。但是有一点,这些是基于这样一个事实的,就是你没有在两个平台之间对不同平台下产生的文件进行读取。如我目前所做的项目中,在代码实现上,需要把wchar_t类型的一些数据输出到文件保存,然后在后续的代码中进行读写。在不同平台下产生的这个文件,wchar_t字符的编码方式肯定是不一样的,所以不能把Windows下生成的文件,直接拿到Linux下面来进行读写,如果这样做,那么读写错误是肯定会发生的。还有一点,不能贸然的添加gcc
编译项 -fshort-wchar,强制将Linux平台下的wchar_t指定成两个字节,因为这样做,只会改变你在代码中自己实现的部分,而内部库或者是第三方库中用到的接口和函数都是没有变的,仍然采用的是4字节编码。如,std::wstring, QT中的QString等。
对于这点,在项目中我拟定了两个方案,方案一是在代码中读写文件的部分,写入文件的时候,把wchar_t类型的数据转成utf-8的编码格式来保存,读取的时候把utf-8编码的数据读出来后再转成平台对应的wchar_t字符,两个平台下都采用同样的解决办法。在windows下可以采用系统函数WideCharToMultiByte()和MultiByteToWideChar()来进行转换,如下面把宽字符转成UTF-8的列子:
#include & windows.h &
std::string to_utf8(const wchar_t* buffer, int len)
int nChars = ::WideCharToMultiByte(
if (nChars == 0) return &&;
newbuffer.resize(nChars) ;
::WideCharToMultiByte(
const_cast& char* &(newbuffer.c_str()),
std::string to_utf8(const std::wstring& str)
return to_utf8(str.c_str(), (int)str.size());
而在Linux下同样采用系统函数iconv()来转换,具体用法下面会提到,这里先省略。当然,如果你现在项目中的编译器支持C++11那就好办了,而我现在windows上的编辑器还是vs2008,C++11在语言上提供了对utf-8的支持,提供了一些新的类型和函数来处理,如下:
// convert UTF-8 string to wstring
std::wstring utf8_to_wstring (const std::string& str)
std::wstring_convert&std::codecvt_utf8&wchar_t&&
return myconv.from_bytes(str);
// convert wstring to UTF-8 string
std::string wstring_to_utf8 (const std::wstring& str)
std::wstring_convert&std::codecvt_utf8&wchar_t&&
return myconv.to_bytes(str);
因为目前项目需要兼容以前Windows下生成的数据文件,所以最后采用了方案二来做,方案二的办法是保持Windows下wchar_t类型的数据读写方式和编码都不变。只在Linux下面做文章,Linux下的数据读写需要根据Windows下wchar_t的编码来进行,也就是,在Linux下写入wchar_t类型的数据的时候,需要先把Linux下的wchar_t的编码格式转成Windows下的wchar_t编码格式,读的时候同理,需要先把读出来的Windows下的wchar_t数据,转成Linux下的wchar_t数据,这样就能实现两个平台下的数据读写,而且夜兼容了以前的数据文件。在Linux下的转换函数也采用的是iconv(),在头文件&iconv.h&中。iconv()的用法如下:
int encodingConvert(const char *tocode, const char *fromcode,
char *inbuf, size_t inlength, char *outbuf, size_t outlength)
#ifndef _WIN32
char **inbuffer = &
char **outbuffer = &
cd = iconv_open(tocode, fromcode);
if((size_t)cd == -1)
return -1;
ret = iconv(cd, inbuffer, &inlength, outbuffer, &outlength);
if(ret == -1)
return -1;
iconv_close(cd);
注意iconv()函数在处理的时候,传进来的inbuffer应该传一个副本,因为iconv()函数在处理的时候会改变inbuffer,iconv()是对inbuffer中的字符一个一个的进行转换,然后保存到outbuffer中。可以用类似下面的代码来做具体的处理,主要传进去的编码需要指定具体的大端序列还是小端序列,因为Linux下默认的好像是大端的,所以指定详细点总没有错的。
//把wchar_t数据写到文件中, 文件中的数据都是以windows下的格式来存放。
int WriteWstringToBuffer(const std::wstring &wstr, void *buffer)
int inLength = (wstr.size() + 1) * sizeof(wchar_t);
int outLength = inLength / 2;
//从Linux下写入wchar_t的数据到文件的长度
char *inBuffer = new char[inLength]();
memcpy(inBuffer, wstr.c_str(), inLength);
char *outBuffer = new char[outLength]();
int ret = EncodingCovert(&UTF-16LE&, &UTF-32LE&, inBuffer, inLength, outBuffer, outLength);
memcpy(buffer, outBuffer, outLength);
delete[] inB
delete[] outB
return (ret == -1)?ret:0;
//从文件中读取wchar_t数据, 文件中的数据都是以windows下的格式来存放。
int ReadWstringFromBuffer(std::wstring &wstr, void *buffer, int bufLength)
int inLength = bufL
int outLength = inLength * 2;
char *inBuffer = new char[inLength]();
memcpy(inBuffer, buffer, inLength);
char *outBuffer = new char[outLength]();
int ret = EncodingCovert(&UTF-32LE&, &UTF-LE&, inBuffer, inLength, outBuffer, outLength);
wstr = outB
delete[] inB
delete[] outB
return (ret == -1)?ret:0;
在进行这样的转换处理后,Linux和Windows下就能互相交换和读写wchar_t类型的数据了,而不用担心数据的读写错误。
字符编码部分参考文章:
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:184573次
积分:2515
积分:2515
排名:第14554名
原创:45篇
转载:32篇
译文:11篇
评论:44条
(2)(1)(1)(2)(2)(1)(1)(3)(3)(1)(4)(4)(6)(4)(4)(1)(4)(1)(2)(3)(1)(4)(3)(8)(8)(1)(4)(9)宽字符(串)和多字节编码的UTF-8字符(串)如何转换? - 查看主题 & Ubuntu中文论坛
&[ 7 篇帖子 ]&
&文章标题 : 宽字符(串)和多字节编码的UTF-8字符(串)如何转换?发表于 :
21:43帖子: 735
如果在C 源文件中写 : L&你好世界\n& ,gcc在编译时会自动将其保存为 unicode 编码, 这样实现了将程序中明文形式的UTF-8多字节编码转换为宽字符串...但是如何把一个保存在char*(或char数组)里面的多字节utf8编码转换成宽字符串形式并并保存到一个wchar*变量里呢?或是如何直接从文件和终端中接收输入并将内容保存的wchar数组里面呢? 我发现有 wprintf, wfprintf之类的函数, 但没有wscanf, wfgets, wfread, 之类的函数, 要是有这些函数就好了...谢谢!!!
_________________
&文章标题 : Re: 宽字符(串)和多字节编码的UTF-8字符(串)如何转换?发表于 :
21:43帖子: 735
我现在亟需具有类似一下接口的函数:wchar_t* cs2wcs(wchar_t* wcs, char* cs);其中wcs以为wchar_t字符串, cs代表char字符串...
_________________
&文章标题 : Re: 宽字符(串)和多字节编码的UTF-8字符(串)如何转换?发表于 :
17:40帖子: 4979
送出感谢: 0 次
查找libglib的文档,估计里面有的。
_________________代码:pip3 install ykdl
install YouKuDownLoader, have fun in downloading.
&文章标题 : Re: 宽字符(串)和多字节编码的UTF-8字符(串)如何转换?发表于 :
21:43帖子: 735
cnkilior 写道:查找libglib的文档,估计里面有的。请问哪有libglib文档?glib不是gtk+的底层库吗?
_________________
&文章标题 : Re: 宽字符(串)和多字节编码的UTF-8字符(串)如何转换?发表于 :
21:43帖子: 735
感谢大家的关注!这个问题不需要烦劳大家解决了...man pages section 3 里面很多相关函数介绍, 只是在你不知道名字时候,很难找到它们...之前我只知道 wprintf之类的函数, 它们的man pages 的see also部分没能让我发现更多函数...今天我man 了一下 utf8,收益颇丰! utf8 的man page里面提到了 mbsrtowcs, 而以mbsrtowcs的man page为起点, 一直see alse, 我发现了很多好用的函数...===============================说下我的感想:man page以section分类, 尽管有see also, 但是其主题性仍然不强!的在线man页面只提供了按所属section排序和所有页面按字母排序两种排序方法, 如果想查找关于某个主题的函数(比如字符编码), 仍然很抓狂!
_________________
&文章标题 : Re: 宽字符(串)和多字节编码的UTF-8字符(串)如何转换?发表于 :
11:16帖子: 12522地址: 廣州
送出感谢: 0 次
iconv 库?所谓的宽字符也是有编码模式的吧,UTF-16 ?
_________________^_^ ~~~要理解递归,首先要理解递归。地球人都知道,理论上,理论跟实际是没有差别的,但实际上,理论跟实际的差别是相当大滴。
&文章标题 : Re: 宽字符(串)和多字节编码的UTF-8字符(串)如何转换?发表于 :
21:43帖子: 735
BigSnake.NET 写道:iconv 库?所谓的宽字符也是有编码模式的吧,UTF-16 ?问题在于宽字符中保存的每个字符所占用的字节都是定长的(实际上一个wchar_t字符里面保存的就是utf-32编码), 而utf-8多字节编码中的每个字符所占用的字节数是不确定的, 当然有类似 sentinel 的东西来标志那些字节共同来表示一个字符. 而实际上把utf-8中的sentinel去掉, 它仍是一个个兼容unicode的编码...我的表述如有遗漏和错误, 欢迎指正!
_________________
显示帖子 : 全部帖子1天7天2周1个月3个月6个月1年&排序 作者发表时间文章标题 升序降序&
&[ 7 篇帖子 ]&
正在浏览此版面的用户:没有注册用户 和 0 位游客
您 不能 在这个版面发表主题您 不能 在这个版面回复主题您 不能 在这个版面编辑帖子您 不能 在这个版面删除帖子您 不能 在这个版面提交附件
选择一个版面
------------------
公告/注意事项
& &新闻和通知
& &校园社团支持
& && &华东校区
& && &华南校区
& && &华北校区
& && &华中校区
& && &东北校区
& && &西北校区
& && &港澳台校区
& && &国外校区
& &软件推荐
& &非常任务
系统安装区
& &教学和常见问答
& && &课堂教学和培训
& &初学者园地 - 17.04 - Zesty Zapus
& &系统安装和升级
& && &新立得和软件源
& && &Wubi安装讨论
& &启动和引导
& &网卡问题以及网络和拨号
& && &校园网拨号
& &笔记本、UMPC支持
& &手机和平板
& && &Ubuntu移动应用开发
& &常用硬件支持
& &系统架构支持
配置美化区
& &字体美化和中文支持
& && &个人配置文件存放点
& &桌面特效
& &窗口管理器
& &屏幕抓图
& &办公、图像、机械电子设计等
& && &Vim和Emacs
& &因特网相关软件
& &影音多媒体
& &Wine及其分支
& &游戏和游戏模拟器
& &虚拟机和虚拟化
& &其它类软件
& &开源模板库
服务器管理
& &服务器基础应用
& &数据库管理
& &服务器维护和硬件相关
& &Ubuntu VPS
参与Ubuntu开发
& &软件和文档翻译
& &编译或打包
& &Ubuntu错误报告
程序设计区
& &Shell脚本
& &GTK+和QT
& &软件/网站开发
& && &Python/Php/Perl
& && &C/C++/Java
& &内核及嵌入式开发
& &开源小工具
& &Ubuntu 17.10
& &Ubuntu 16.04 LTS
& &Ubuntu 14.04 LTS
& &Ubuntu 12.04 LTS
& &Ubuntu 10.04 LTS
& &老旧版本支持
& && &Ubuntu 15.10
& && &Ubuntu 15.04
& && &Ubuntu 14.10
衍生发行版
& &Ubuntu GNOME
& &Kubuntu
& &Xubuntu & Lubuntu
& &Ubuntu中文衍生版
& && &UbuntuKylin
& &Ubuntu国外衍生版
& && &Mint
& &Ubuntu衍生版制作
& &其它类Unix OS发行版
& && &Arch发行版
& && &Debian发行版
& && &OpenSUSE发行版
& && &Deepin
& &深度PK版
& &Ubuntu故事和感慨
& &Full Circle开源杂志
分享交流区
& &同城交流
& &线下活动专版
& &Ubuntu宣传推广
& &论坛管理
& && && &Ubuntu中文网上商店转载,出处:/database/29/.shtml
 我们传统的程序基本都只在Windows或只在下运行,Windows程序使用GB18030编码,Linux程序则只使用英文,多年以来这些程序运行起来都没有问题。
  近年来,随着程序的,部分代码特别是公用组件都需要同时支持Windows及Linux平台,这样就出现了不同程度的编码问题,例如在编译时编译器报错,或者在运行时出现乱码。这些问题都和程序选用的字符编码不正确有关。
  本文简要地分析了C++的一些字符编码问题,并提供了建议的。受经验和时间的限制,有些内容可能不一定全面,仅供大家参考。
  1. C++的编码需要特别考虑吗?
  1.1. 几个相关概念
  首先要区分几个概念:
  C++源文件的编码
  指的是C++源程序文件(。cpp/.h)本身使用什么字符编码(GB18030/UTF-8等)。
  C++程序的内码
  编译后,C++中的字符串常量都会变成一串字节存放在可执行文件中。这个内码指的就是在可执行文件中,字符串以什么编码进行存放。这里的字符串常量指的是窄字符(char)而非宽字符(wchar_t)。宽字符通常是以Unicode(使用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参数进行修改。&可以通过在程序中打印字符串每个字节十六进制形式来判断程序所使用的内码。
  运行环境编码
  我们常用的简体中文版Windows的环境编码是GB18030,而Linux下最常用的环境编码是UTF-8。
  1.3. 这几个编码之间的关系
  源程序需要由编译器编译为目标文件,目标文件运行后输出信息到终端,因此这几个编码之间存在一些的关联:
  +--------+ | 源程序 |----------源文件编码 +---+----+ | 编译器编译 +---+----+ |目标文件|----------程序内码 +---+----+ | 运行后输出信息 +---+----+ | 输出 |----------运行环境编码 +--------+
  编译器需要正确识别源文件的编码,把源文件编译为目标文件,并把源文件中的以源文件编码的字符串转换为以程序内码编制的字符串保存在目标文件中。
  Note 当源文件的字符编码与程序内码都是UTF-8时(gcc的缺省情况),gcc似乎并不会对源文件中的字符编码进行转换,而是直接把字符串原样存放到目标文件中,在这种情况下,源程序中的GB18030编码的字符串在输出时仍然为GB18030编码。但如果在其它源文件字符编码的实际值与编译选项不同时,会在编译时报无法从XXX转换到UTF-8的错,因此还不清楚为什么两个编码都是UTF-8时,GB18030 编码的源文件能通过编译。C++标准库需要正确识别终端的运行环境编码,并把程序的输出转换为运行环境所使用的编码,以便正确显示。
  在这过程中,如果有一个环节出现问题,就会导致程序的输出发生异常,产生乱码或其它更严重的后果。
  2. 源文件应该采用什么编码?
  2.1. 编译器对不同源文件编码的支持一样吗?
  gcc/vc各版本对C++源文件编码有不同的处理:
  gcc (v4.3.2 ):
  支持UTF-8编码的源文件,UTF-8编码的源文件不能有BOM。
  开始支持带BOM的UTF-8文件。
  vc2003:
  支持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,在输入/输出时都需要与当前运行环境的编码进行互转。
  一般来说,如果需要支持多语言,有两种比较好的做法:
  使用窄字符,但源程序中只使用ascii字符,非ascii字符通过gettext或其它
  工具放到单独的文件中,由gettext等工具处理编码转换的问题。
  在各种编码的运行环境中均能正确输出中文。
  程序中不能直接出现非ascii字符,也不能通过\uXXXX方式指定非ascii字符,后者也会被编译器转换为非ascii字符并存放在目标文件中。
  注释中可以使用ascii兼容的编码,不影响编译器。
  有比较多的现成代码可供重用。
  使用宽字符。
  在各种编码的运行环境中均能正确输出中文。
  程序中可以使用非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的话,有几种做法
  使用窄字符,源程序中只使用ascii字符,非ascii字符,如中文等通过
  gettext等工具放到单独的语言包中。
  这种做法比较多人推荐。
  兼容VC及gcc各版本。
  由于源程序中不出现非ascii字符,因此不需要考虑源程序文件的编码问题。
  兼容各种编码的运行环境。
  使用窄字符,源程序中允许使用非ascii字符。
  要求运行环境的编码与程序内码一致,即只支持GB18030编码的Windows及
  UTF-8编码的Linux。
  根据源程序使用的编码不同,对编译器的兼容性也不同:
  使用窄字符,源程序使用带BOM的UTF-8编码。
  兼容VC各语种的各版本。
  兼容gcc 4.4.0以上版本。
  使用窄字符,源程序使用GB18030编码。
  兼容VC的简体中文各版本。
  兼容gcc各版本,但在编译时需要加上-finput-char=gb18030参数。
  使用宽字符,源程序中允许使用非ascii字符。
  兼容各种编码的运行环境。
  根据源程序使用的编码不同,对编译器的兼容性也不同:
  使用窄字符,源程序使用带BOM的UTF-8编码。
  兼容VC各语种的各版本。
  兼容gcc 4.4.0以上版本。
  使用窄字符,源程序使用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的功能,并在此基础上进行了增强,提供了大小写变换、字符顺序比较、时间的处理 、分词、数字的格式化输入/输出、消息格式化、多语种支持、字符编码转换等功能,值得进一步研究及使用。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1575次
排名:千里之外}

我要回帖

更多关于 字符编码 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信