博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

setlocale GBK 中文,多字节,宽字符

Posted on 2019-07-29 18:43  bw_0927  阅读(1824)  评论(1编辑  收藏  举报

windows平台  

        char 表示单字符,占用一个字节

        wchar_t 表示宽字符,占用两个字节

Linux平台   

        char 占用一个字节
        wchar_t 占用四个字节

windows平台下对于用字符串保存中文的问题,GBK和UTF8都是用char来表示,只是为了表示一个中文字符需要用到多个char

而对于UNICODE(其实应该说是UFT16),每一个字符都需要一个两个字节,也就是用wchar_t表示。

UNICODE只是一个字符集,规定了不同的字符对应于一个唯一的整数,平时所说的使用UNICODE编码其实说的是UFT16编码(顾名思义就是用16位来表示一个字符)。

UTF8、UTF16和UFT32则是基于UNICODE字符集的三种编码方式

不同之处是:对于一个字符所对应的整数,应该怎样用二进制位表示出来。对于UTF16和UTF32,不管字符对应的数字是多少,都用恒定的多字节表示,所以可以很方便的表示一个字符,但需要注意字节序问题。

比较麻烦的是UFT8,对于不同的字符,可能会用到一个字节,两个字节到最多六个字节。这么做的好处是节省了空间

在实际使用字符串保存时,因为UTF16不管什么字符,都用2个字节表示,所以可能会出现某一个字节全零的情况。例如字符‘A’编码是0x41,用UTF16表示就是0x0041。对于char表示的字符串,以0x00表示结尾,所以没有办法正确的存储此类数据,此时只能用wchar_t来保存。

UTF8编码方式如下:用1~6个字节存储一个字符,当第一个字节的首位为0时,表示这个字符只用一个字节表示(刚好与ASCII码一一对应),当用多字节表示一个字符时,首字节以连续的多个1和一个0开始,表示用多个字节

例如用3个字节是,首字节为1110xxxx,后面各字节均以10开始。

UNICODE原码(16进制) UTF8(2进制)
0000-007F 0xxxxxxx
0080-07FF 110xxxxx 10xxxxxx
0800-FFFF 1110xxxx 10xxxxxx 10xxxxxx
......

如上所示,UTF8中的‘x’就是实际表示字符编码的位。表示的最大值就是全1的情况,最小值就是少一个字节的情况下最大值加1,因为少一个字节已经可以存的下的字符,不会用多一个字节来保存。

由上可知,UTF8可以直接用char类型的字符串来表示,只是用对应的解释方式来解释就可以正确显示了。


另外一种就是GBK等编码方式。这一类编码方式和UNICODE没有任何关系,是另一种字符集和编码方式的规定。使用方法可以类比于UTF8,在编码小于128时,就是ASCII,而中文的编码均大于128,用超过一个字节来表示。

在平时编写的windows程序中,可以理解为如果使用了UNICODE宏,就是在用wchar_t来表示中文,使用UTF16编码;  如果没有UNICODE宏,那么就是在用GBK,以char来表示中文。

windows平台下的TCHAR类型就是通过宏对char和wchar_t的封装。可根据当前平台情况选择对应的类型。_T修饰的字符串常量同理,根据是否定义的UNICODE宏,分别表示""或L""。

Linux平台的不同在于,wchar_t用4个字节表示,也就是UCS-4,而windows用两个字节,UCS-2。

 

 

 


 

https://www.cnblogs.com/my_life/articles/7016406.html

Unicode 是「字符集」【世界上所有字符的一个对照表,每个字符对应一个码位】

UTF-8 是「编码规则」

 

请注意”字符”和”字节”两个术语的不同,“字节”是一个8位的物理存贮单元,而“字符”则是一个文化相关的符号。在unicode中,一个字符就是两个字节。

https://www.cnblogs.com/drfxiaoliuzi/p/7991337.html

字符是人们认识世界,用来标记的符号。但是计算机并不认识这些字符。
所以需要对这些字符进行编号,这样,字符与编码之间就形成了映射关系。当你输入65的时候,计算机才能识别,你其实想表达的是'A'.

常见的字符集:ANSI字符体系,Unicode字符体系

多字节字符

多字节字符: 一般来说,一个char是1个字节,之所以教多字节字符是因为,一个char类型的变量,表示一个字符的时候,可能是一个字节,也可能是多个字节。
比如,你需要保存一个字符'A',已知字符'A'的编码是65,所以一个字节就可以表示。
但是,如果你想表达’赵'这个字符,他的unicode编码对应的是0xD5D4,1个字节就不够用了,因为汉字的编码大于256.

因此大家就是用多字节来表示一个字符来接觉得不够用的问题,但是前128个已经被占用了,具体可以查看ASCII编码。后来256也不够用了。
而且由此也产生一个问题,试想:假如你有一个字符串:

char ch[] = "abc赵钱孙123";

那么你解析的时候,最好能对每个字符做一个标识:
第一个字符a占用1个字节,
第二个字符b占用1个字节
第三个字符c占用1个字节
第四个字符赵,占用2个字节
第五个字符钱,占用2个字节
第六个字符孙,占用2个字节
第七个字符1,占用1个字节
...

这样下来,如果存储一个带有中英文的字符,会比较麻烦,因为,你需要额外的一个数组,来表示上面的字符数组的每个元素,所占用的字节,这样才能正常解析。
如此一来,简直太麻烦了。
--------------------------------------------------------------------------------------------------------------------------------------------

宽字符

后来,宽字符应运而生。已知一个字节是8位,最多能表示256个字符,其实一个字节未必一定是8位的,也可以是16位,只不过大家用久了,默认都是一个字节是8位。
所以,宽字符,其实言外之意,是可以用多个字节表示一个字符的,也就是所有的字符都是多个字节,而不是原来只是超过256的字符。
比如,’赵‘这个字,用2个字节来表示,同样,‘A’也用两个字节来表示这样虽然浪费了一部分内存空间(因为原来一个字节能表示的字符,现在都需要两个字节),但是,解析的时候会方便多了。
也就是,所有的字符,都假设按照两个字节来表示,2^16=65536,6万多个字符,足以表示全世界所有的符号了。

在C/C++中,宽字符类型wchar_t 它和我们熟悉的char类型是一样的,只不过,涉及宽字符的相关库函数,都要用宽字符类型的库函数来处理,一般有w作为前缀。名字和我们熟悉的差别并不大。
比如库函数strlen()对应的是wcslen()。

宽字符并不一定是Unicode,Unicode只是宽字符编码的一种实现。

 

多字节字符是由一个或多个字节的序列构成的字符。 每个字节序列表示扩展字符集中的单个字符。 多字节字符用于字符集(如日文汉字)中。

宽字符是宽度始终为 16 位的多语言字符代码。 字符常量的类型是 char;对于宽字符,该类型是 wchar_t。 

Unicode 规范是宽字符的规范 用于多字节【一个字符可能是1字节,也可能是多字节】和宽字符【一个字节始终是两个字节】之间的转换的运行库例程包括 mbstowcsmbtowcwcstombs和 wctomb

 


 

 

linux 的编码输出,比如从文件输出,从 printf 输出,需要控制台做适当的编码匹配(如果编码不匹配,一般和该程序编译时的编码有若干关系),而控制台的转换输入需要查看当前的系统编码。

比如控制台当前的编码是 UTF-8, 那么 UTF-8 编码的东西能正确显示,GBK 就不能;

同样,当前编码是 GBK, 就能显示 GBK 编码。

 

https://www.cnblogs.com/hnrainll/archive/2011/05/07/2039700.html


https://blog.csdn.net/songjinshi/article/details/8433167

C 语言原本是在英文环境中设计的,主要的字符集是7 位的ASCII 码。从此开始,8 位的byte(字节)变成最常见的字符编码单位,但是国际化软件必须能够表示不同的字符,而这些字符数量庞大,无法使用一个字节编码,于是世界上使用各式 各样多字节的字符编码集合已经有数十年了,比如用来表示“非拉丁字母”以及“非字母”的中、日、韩文字系统。

在1994 年,“Normative Addendum 1”(基准增补一)的采用,让ISO C 可以标准化两种表示大型字符集的方法

宽字符(wide character,该字符集内每个字符使用相同的位长)以及多字节字符(multibyte character,每个字符可以是一到多个字节不等,而某个字节序列的字符值由字符串或流(stream)所在的环境背景决定)。
注 意: 虽然C现在提供抽象机制,可以处理和转换不同种类的编码集合,但语言本身并没有定义或指定任何编码集合,或任何字符集(除前一节提到的基本源代码字符集和 基本运行字符集外)。

换句话说,这部分是由个别的实现版本指定如何编码宽字符,以及要支持什么类型的多字节字符编码机制

自从1994 年的增补之后,C 不只提供char类型,还提供wchar_t类型(宽字符),此类型定义在stddef.h 头文件中wchar_t 类型足以表示某个实现版本扩展字符集的任何元素。
虽然C 标准没有支持Unicode 字符集,许多实现版本使用Unicode 转换格式UTF-16 和UTF-32(参考http://www.unicode.org) 来处理宽字符。Unicode 标准和ISO/IEC 10646标准相当接近,而且是许多既有字符集(包括7 位的ASCII)的超集。如果遵循Unicode标准,wchar_t类型至少是16或32位长,而wchar_t类型的一个值就代表一个Unicode 字符。比方说,下列的定义将变量wc 初始化为希腊字母α。

wchar_t wc = '"x3b1';


此 转义符以“"x”起头,后面接着十六进制的数字,会将这个数字所代表的字符赋值到变量中。在这个例子中,此字符是小写的alpha。多字节字符集中,每 个字符的编码宽度都不等,可以是一个字节,也可以是多个字节。源代码字符集和运行字符集都可能包含多字节字符,如果真的包含多字节字符的话,那么基本字符 集中的每个字符都只会占用一个字节(完全没有多字节的字符),空字符是唯一的例外,空字符可能会占用任意个数的字节(但这些字节内全部的位都必须为0)。 多字节字符可以被用于字符的常量、字符串字面值(string literal)、标识符(identifier)、注释(comment),以及头文件。许多的多字节字符集被设计来支持特定国家的语言,例如JIS 字符集(日本业界标准,Japanese Industrial Standard)。

多字节UTF-8 字符集是由Unicode Consortium(万国码联盟)定义的,可以表示Unicode 字符集的所有字符。
UTF-8 字符所使用的空间大小从一个字节到四个字节都有可能。

多字节字符和宽字符(也就是wchar_t)的主要差异在于宽字符占用的字节数目都一样,而多字节字符的字节数目不等这样的表示方式使得多字节字符串比宽字符串更难处理。

比 方说,即使字符'A'可以用一个字节来表示,但是要在多字节的字符串中找到此字符,就不能使用简单的字节比对,因为即使在某个位置找到相符合的字节,此字 节也不见得是一个字符,它可能是另一个不同字符的一部分。

然而,多字节字符相当适合用来将文字存储成文件(参见第13 章)。

宽字符肯定是多字节(每个字符是固定的两个字节);但多字节不一定是宽字符(多字节可能是一个字节,两个字节,三个字节,或者4个字节,utf8就是一种多字节的编码方式);

宽字符每个字符都是两个字节,可能造成空间的浪费,比如asic码只需要一个字节来表示;

多字节没有空间的浪费,适合网络传输和文件保存,但多字节的字符串处理比较麻烦,所以得先把多字节转换成宽字符,然后进行处理。


C 提供了一些标准函数,可以将多字节字符转换为wchar_t,或将宽字符转换为多字节字符。

比方说,如果C 编译器使用Unicode 标准的UTF-16 和UTF-8,那么下面调用wctomb()函数就可以获得字符α 的多字节表示方式(注:wctomb = wide character to multibyte)。

wchar_t wc = L'"x3B1'; // 小写的希腊字母alpha,α

 

char mbStr[10] = "";

 

int nBytes = 0;

 

nBytes = wctomb( mbStr, wc );


在调用此函数之后,mbStr数组会得到多字节的字符,在这个例子中,也就是""xCE"xB1"符号。

此wctomb()函数的返回值是“所需要 的字节个数”,在这个例子中,被赋值到变量nBytes 的值是2,意思是:希腊小写字母alpha 在多字节字符中需要占用两个字节
---------------------
作者:Vincent_Song
来源:CSDN
原文:https://blog.csdn.net/songjinshi/article/details/8433167
版权声明:本文为博主原创文章,转载请附上博文链接!


https://blog.csdn.net/youxishaonian/article/details/70312438

char与wchar_t

我们知道C++基本数据类型中表示字符的有两种:char、wchar_t。 
char叫多字节字符,一个char占一个字节之所以叫多字节字符是因为它表示一个字时可能是一个字节也可能是多个字节。一个英文字符(如’s’)用一个char(一个字节)表示,一个中文汉字(如’中’)用3个char(三个字节)表示,看下面的例子。

  1. void TestChar()
  2. {
  3. char ch1 = 's'; // 正确
  4. cout << "ch1:" << ch1 << endl;
  5. char ch2 = '中'; // 错误,一个char不能完整存放一个汉字信息
  6. cout << "ch2:" << ch2 << endl;
  7. char str[4] = "中"; //前三个字节存放汉字'中',最后一个字节存放字符串结束符\0
  8. cout << "str:" << str << endl;
  9. //char str2[2] = "国"; // 错误:'str2' : array bounds overflow
  10. //cout << str2 << endl; 
  11. }

 

结点如下:

ch1:s 
ch2: 
str:中

 

wchar_t被称为宽字符,一个wchar_t占2个字节之所以叫宽字符是因为所有的字都要用两个字节(即一个wchar_t)来表示,不管是英文还是中文。看下面的例子:

  1. void TestWchar_t()
  2. {
  3. wcout.imbue(locale("chs")); // 将wcout的本地化语言设置为中文
  4. wchar_t wch1 = L's'; // 正确
  5. wcout << "wch1:" << wch1 << endl
  6. wchar_t wch2 = L'中'; // 正确,一个汉字用一个wchar_t表示
  7. wcout << "wch2:" << wch2 << endl;
  8. wchar_t wstr[2] = L"中"; // 前两个字节(前一个wchar_t)存放汉字'中',最后两个字节(后一个wchar_t)存放字符串结束符\0
  9. wcout << "wstr:" << wstr << endl;
  10. wchar_t wstr2[3] = L"中国";
  11. wcout << "wstr2:" << wstr2 << endl;
  12. }

 

结果如下:

ch1:s 
ch2:中 
str:中 
str2:中国

说明: 
1. 用常量字符给wchar_t变量赋值时,前面要加L。如: wchar_t wch2 = L’中’; 
2. 用常量字符串给wchar_t数组赋值时,前面要加L。如: wchar_t wstr2[3] = L”中国”; 
3. 如果不加L,对于英文可以正常,但对于非英文(如中文)会出错。

 

string与wstring

字符数组可以表示一个字符串,但它是一个定长的字符串,我们在使用之前必须知道这个数组的长度。为方便字符串的操作,STL为我们定义好了字符串的类string和wstring。大家对string肯定不陌生,但wstring可能就用的少了。

string是普通的多字节版本是基于char的,对char数组进行的一种封装。

wstring是Unicode版本,是基于wchar_t的,对wchar_t数组进行的一种封装。

 

string 与 wstring的相关转换:

以下的两个方法是跨平台的,可在Windows下使用,也可在Linux下使用。

#include <cstdlib>
#include <string.h>
#include <string>
 
// wstring => string
std::string WString2String(const std::wstring& ws)   //宽字符转成多字节
{
    std::string strLocale = setlocale(LC_ALL, "");
    const wchar_t* wchSrc = ws.c_str();
    size_t nDestSize = wcstombs(NULL, wchSrc, 0) + 1;
    char *chDest = new char[nDestSize];
    memset(chDest,0,nDestSize);
    wcstombs(chDest,wchSrc,nDestSize);
    std::string strResult = chDest;
    delete []chDest;
    setlocale(LC_ALL, strLocale.c_str());
    return strResult;
}
 
// string => wstring
std::wstring String2WString(const std::string& s)   //多字节转成宽字符
{
    std::string strLocale = setlocale(LC_ALL, ""); 
    const char* chSrc = s.c_str();
    size_t nDestSize = mbstowcs(NULL, chSrc, 0) + 1;
    wchar_t* wchDest = new wchar_t[nDestSize];
    wmemset(wchDest, 0, nDestSize);
    mbstowcs(wchDest,chSrc,nDestSize);
    std::wstring wstrResult = wchDest;
    delete []wchDest;
    setlocale(LC_ALL, strLocale.c_str());
    return wstrResult;
}

  

字符集(Charcater Set)与字符编码(Encoding)

字符集(Charcater Set或Charset):是一个系统支持的所有抽象字符的集合,也就是一系列字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。常见的字符集有:ASCII字符集、GB2312字符集(主要用于处理中文汉字)、GBK字符集(主要用于处理中文汉字)、Unicode字符集等。

字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个字符集(如字母表或音节表),与计算机能识别的二进制数字进行配对。即它能在符号集合与数字系统之间建立对应关系,是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息,而计算机的信息处理系统则是以二进制的数字来存储和处理信息的。字符编码就是将符号转换为计算机能识别的二进制编码

一般一个字符集等同于一个编码方式,ANSI体系(ANSI是一种字符代码,为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符)的字符集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。一般我们说一种编码都是针对某一特定的字符集。 
一个字符集上也可以有多种编码方式,例如UCS字符集(也是Unicode使用的字符集)上有UTF-8、UTF-16、UTF-32等编码方式。

从计算机字符编码的发展历史角度来看,大概经历了三个阶段: 
第一个阶段:ASCII字符集和ASCII编码。 
计算机刚开始只支持英语(即拉丁字符),其它语言不能够在计算机上存储和显示。ASCII用一个字节(Byte)的7位(bit)表示一个字符,第一位置0。后来为了表示更多的欧洲常用字符又对ASCII进行了扩展,又有了EASCII,EASCII用8位表示一个字符,使它能多表示128个字符,支持了部分西欧字符。

第二个阶段:ANSI编码(本地化) 
为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 ‘中’ 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。 
不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。 
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。

第三个阶段:UNICODE(国际化) 
为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。UNICODE 常见的有三种编码方式:UTF-8(1个字节表示)、UTF-16((2个字节表示))、UTF-32(4个字节表示)。

我们可以用一个树状图来表示由ASCII发展而来的各个字符集和编码的分支:

 

 https://blog.csdn.net/luoweifu/article/details/49385121

  • UTF-32又称UCS-4是一种将Unicode字符编码的协定,对每个字符都使用4字节。就空间而言,是非常没有效率的。
  • 尽管有Unicode字符非常多,但是实际上大多数人不会用到超过前65535个以外的字符。因此,就有了另外一种Unicode编码方式,叫做UTF-16(因为16位 = 2字节)。UTF-16将0–65535范围内的字符编码成2个字节,如果真的需要表达那些很少使用的"星芒层(astral plane)"内超过这65535范围的Unicode字符,则需要使用一些诡异的技巧来实现。UTF-16编码最明显的优点是它在空间效率上比UTF-32高两倍,因为每个字符只需要2个字节来存储(除去65535范围以外的),而不是UTF-32中的4个字节。
  • UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码定长码),也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。

UTF-8使用一至四个字节为每个字符编码:

 


https://apporz.com/2015/01/02/cpp-encoding-problem/                

源文件的编码格式和输出的控制台编码格式一致,才能正确输出

测试之前很有必要说明一点:

A program should not mix output operations on wcout with output operations on cout (or with other narrow-oriented output operations on stdout): Once an output operation has been performed on either, the standard output stream acquires an orientation (either narrow or wide) that can only be safely changed by calling freopen on stdout.
—- cplusplus.com

就是说不要混用cout和wcout进行输出,因此下面的例子中都是单独使用cout或者wcout。

cout测试

下面的测试在Visual Studio 2013中进行。

MSVC,默认编码GB2312(可以在 文件–高级保存选项 中查看和修改)

<!-- lang: cpp -->
#include <iostream>

int main() {
    using namespace std;

    const char *code = "abc中文def";

    cout << "abc中文def" << endl;
    cout << code << endl;

    return 0;
}

结果

abc中文def

abc中文def

均正确输出。

MSVC,改变编码为UTF8(+bom)

结果

abc中文def

abc中文def

均正确输出。

MSVC,改变编码为UTF8(-bom)

结果

abc涓枃def

abc涓枃def

出现乱码。

问题分析

可以看到源文件的编码方式会影响最后的输出,原因在于常量文本采用了硬编码的方式,也就是说源代码里面的中文会根据当前文件的编码方式直接翻译成对应字节码放进存储空间。

如“中文”二字,

GB2312(Codepage 936)的编码为:

D6 D0 CE C4

而UTF8是:

E4 B8 AD E6 96 87

而控制台也有一套编码方式,对于Windows的cmd,可以查看其 属性 下面的当前代码页,笔者是ANSI(936)。

当向控制台传送GB2312的字节码时,中文显示正常,当传入无签名的UTF8的字节码时,中文就不能被正确解释,出现了乱码。

Q:为什么带有签名的UTF8却可以正常显示呢?

A:实际上UTF8完全不需要带签名,M$自作聪明YY了一个bom头来识别文件是不是UTF8。因此带有签名的UTF8能被cmd识别为UTF8,中文才能显示正常。

为了进一步证实是不是和控制台的编码有关系,并正确理解上一个例子中乱码的产生缘由,我们可以做一个重定向,将结果输出到文本文件:

test.exe > test.txt

使用任意可以改变编码的文本编辑器(笔者使用的是everedit)查看,可以发现以UTF8解释,显示正常,以ANSI(936)解释,将得到刚才那个乱码。


下面的测试在QtCreator中进行。

MinGW,UTF8

结果

abc涓枃def

abc涓枃def

出现乱码。

MinGW,ANSI-936

结果

abc中文def

abc中文def

显示正确。


下面的测试在Linux的bash中进行。

g++,UTF8

结果

abc中文def

abc中文def

显示正确。

g++,gb2312

结果

abc▒▒▒▒def

abc▒▒▒▒def

出现乱码。

Ubuntu查看/etc/default/locale,可以看到LANG=”en_US.UTF-8”,说明bash能解释UTF8的字节码,而gb2312的变成了乱码。

小结

程序的输出编码必须和”显示程序”的显示编码适配时才能得到正确的结果。简而言之就是解铃还须系铃人。


宽字符使用多个字节来表示一个字符,中文可以用char来表示没问题,用wchar来表示也没有问题。

wcout测试

wcout输出wchar_t型的宽字符,测试代码如下:

<!-- lang: cpp -->
#include <iostream>

int main() {
    using namespace std;

    const wchar_t *code = L"abc中文def";

    wcout << L"abc中文def" << endl;
    wcout << code << endl;

    return 0;
}

MSVC,无论上述何种编码

结果

abc

输出被截断,只有前几个英文字母可以被输出,传入指针输出无效。

问题分析

L”abc中文def” 在内存中表现为:

(gb2312) 61 00 62 00 63 00 2d 4e 87 65 64 00 65 00 66 00

(utf8-bom) 61 00 62 00 63 00 2d 4e 87 65 64 00 65 00 66 00

(utf8+bom)61 00 62 00 63 00 93 6d 5f e1 83 67 64 00 65 00 66 00

wcout 在处理L”abc中文def”时,按宽字节依次遍历,前面的abc没问题(小端序第一个字节是00),遇到中文,识别不了,无输出,间接导致后续<<都没有输出了。

也就是说wcout不能用来处理中文输出。

第二个传入wchar_t指针,发现没有任何输出,为了验证是不是由于上一条输出语句中中文的影响,单独测试如下:

<!-- lang: cpp -->
#include <iostream>

int main() {
    using namespace std;

    const wchar_t *code = L"abc中文def";

    wcout << code << endl;

    return 0;
}

结果

abc

说明传入wchar_t指针是可以正常输出宽字节英文的,一旦遇到非00字节间隔,后续所有输出将无效。

MinGW的结果同样如此,无论编码与否,只要wcout遇到中文立马跪。

有博主称可以在输出前执行下面的函数或者进行全局设置来让wcout认识中文:

<!-- lang: cpp -->
std::wcout.imbue(std::locale("chs"));
std::locale::global(std::locale(""));//全局设置
  • MSVC下,没有问题,可以达到预期结果。

  • MinGW下,第一条语句会抛出一个runtime_error异常崩溃掉,第二条语句无效。

  • Linux g++下,没问题。

可见MinGW的libstdc++对locale的实现不理想,有传闻使用stlport可以避免这个问题。


总结

  • 认清你的代码处在何种编码的环境

  • 认清放在你字符串里面的数据是何种编码

  • 认清你要向具有何种编码的屏幕传送数据

  • 解铃还须系铃人

  • 非特殊情况下不建议使用wchar_t来存放中文字符

很多时候中文并不是硬编码进程序的,例如一段中文来自网络,以gb2312编码,而”屏幕”只认UTF8,这个时候就要进行必要的编码转换。boost库的boost::locale::conv中提供了很多常用的转换模板函数,来实现宽窄字符、ansi和utf之间的相互转换。

 

 


https://www.cnblogs.com/zyl910/archive/2013/01/20/wchar_crtbug_01.html

//https://www.cnblogs.com/zyl910/archive/2013/01/20/wchar_crtbug_01.html
#include <stdio.h>
#include <locale.h>
#include <wchar.h>

#include <string>
#include <iostream>

using namespace std;


const char* psa = "A汉字ABC";
const wchar_t* psw = L"W汉字ABC";

int main(int argc, char* argv[])
{
    // init.
    ios::sync_with_stdio(false);    // Linux gcc.
    locale::global(locale(""));
    //setlocale(LC_CTYPE, "");    // MinGW gcc.
    wcout.imbue(locale(""));

    // C++
    cout << psa;    cout.clear();    cout<<endl;
    wcout << psw;    wcout.clear();    wcout<<endl;

    // C
    printf("\nC:\n");
    printf("\t%s\n", psa);
    printf("\t%ls\n", psw);

    return 0;
}

  

大家猜一猜这段程序的运行结果是什么?


1.2 理论结果

  先根据C++标准,分析一下这段程序的理论结果。

  在main函数中,首先执行了这两行代码对地区环境进行了初始化——

    locale::global(locale(""));
    wcout.imbue(locale(""));

 

  细节解释——
1. locale(""):调用构造函数创建一个local,其中的空字符串具有特殊含义:使用客户环境中缺省的locale(《C++标准程序库—自修教程与参考手册》P697)。例如在简体中文系统上,会返回简体中文的locale。
2. locale::global(locale("")):将“C++标准IO库的全局locale”设为“客户环境中缺省的locale”。注意它还会设置C标准库的locale环境,造成与“setlocale(LC_ALL, "")”类似的效果(《C++标准程序库—自修教程与参考手册》P698)。
3. wcout.imbue(locale("")):使wcout使用“客户环境中缺省的locale”。

  就这样,使C标准库、C++标准IO库(尤其是wcout)均正确的设置了地区环境,与客户环境中缺省环境完全匹配。

  随后,使用C++标准IO库的cout、wcout分别输出窄字符串和宽字符串——

    // C++
    cout << psa;    cout.clear();    cout<<endl;
    wcout << psw;    wcout.clear();    wcout<<endl;

 

  细节解释——
1. 调用cout、wcout的clear成员函数是为了清除错误状态,使后续输出能正常运行。
2. 使用“cout<<endl”或“wcout<<endl”时,不仅会使输出文本换行,而且还会执行flush成员函数,提交缓冲区中的数据。使得cout、wcout的输出文本不会发生冲突。

所以cout 和 wcout是能混用的,只要正确的进行了clear(), endl

  最后,使用C标准库的printf函数输出窄字符串和宽字符串——

    // C
    printf("\nC:\n");
    printf("\t%s\n", psa);
    printf("\t%ls\n", psw);

 

  所以,测试程序的运行结果应当为——

A汉字ABC
W汉字ABC

C:
    A汉字ABC
    W汉字ABC

 

  注意为了更好区分C++标准IO库与C标准库的输出结果,这里给printf加了个TAB字符。

七、总结

  虽然C++标准的设想十分完善,可惜各种编译器的实现程度存在不少差异。甚至某些平台上连“locale("")”都不支持。
  为了保证跨平台,慎用C++标准IO库,最好尽可能的使用兼容性非常好的C标准库

 

参考文献——
《ISO/IEC 9899:1999》(C99). ISO/IEC,1999. www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
《C++ International Standard - ISO IEC 14882 Second edition 2003》(C++03). ISO/IEC,2003-10-15.
《C++标准程序库—自修教程与参考手册》. Nicolai M.Josuttis 著,侯捷、孟岩 译. 华中科技大学出版社,2002-09.
《std::locale breakage on MacOS 10.6 with LANG=en_US.UTF-8》. http://stackoverflow.com/questions/1745045/stdlocale-breakage-on-macos-10-6-with-lang-en-us-utf-8
《[C] 跨平台使用TCHAR——让Linux等平台也支持tchar.h,解决跨平台时的格式控制字符问题,多国语言的同时显示》. http://www.cnblogs.com/zyl910/archive/2013/01/17/tcharall.html

 

 https://www.jianshu.com/p/c23f3ea5443d

 

ANSI、UTF-8、Unicode为字符代码的三种编码格式,一个字符可以被编码成ANSI、UT-F8或Unicode格式,这三种格式只是表现形式不一样,其表示内容是一样的。如下表:

charANSI(GBK)UnicodeUTF-8
0xD6D0 0x4E2D 0xE4B8AD

ANSI编码【英文一个字节,中文两个字节】

ANSI表示英文字符时用一个字节,表示中文用两个字节

为了使计算机支持多种语言,不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。

在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。

对于ANSI编码而言,0x00~0x7F之间的字符,依旧是1个字节代表一个字符(ASCII编码),而这之外的字符通常是使用0x80~0xFF范围内的两个字节来表示一个字符。比如汉字找那个的'中'在简体中文中使用[0xD6, 0xD0]这两个字节存储。

下表中展示了在不同ANSI标准下的编码:

charANSI(GBK)ANSI(Big5)ANSI(JIS)UnicodeUTF-8
0xCEC4 0xA4E5 0x95B6 0x6587 0xE69687

可以看出,不同ANSI编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。需要将不同的ANSI编码都转换成UTF-8编码,进而存储。

Unicode编码【不管中英文,全部两个字节】

Unicode字符集编码全称:Universal Multiple-Octet Coded Character Set,通用多八位编码字符集。Unicode字符集是国际组织制定的可以容纳世界上所有文字和符号的编码方案。

Unicode编码使用两个字节0x0000-0xFFFF)来表示一个字符,世界上任何文字和符号都对应于Unicode字符集中的一个二进制代码,但是:

Unicode只是一个符号集, 它只规定了符号的二进制代码, 却没有规定这个二进制代码应该如何存储。

Unicode编码的优点是覆盖了世界上所有的文字和符号,缺陷则是对于英文字符浪费了一个字节。例如:英文A在unicode中表示为0x0041。

UTF-8编码【可变字节存储,中文是三个字节】

UTF-8是Unicode的实现方式之一。

UTF-8全称:8bit Unicode Transformation Format,8比特Unicode通用转换格式。UTF-8是一种针对Unicode的可变长度字符编码。可以表示Unicode标准中的任何一个字符,且其编码中的第一个字节仍然与ASCII兼容。

UTF-8是一种变长的编码方式,可以使用1~6个字节对Unicode字符集进行编码,编码规则如下:

  1. 对于单字节的符号, 字节的第一位设为0, 后面7位为这个符号的unicode码. 因此对于
    英语字母, UTF-8编码和ASCII码是相同的.

  2. 对于n字节的符号(n>1), 第一个字节的前n位都设为1, 第n+1位设为0, 后面字节的前
    两位一律设为10. 剩下的没有提及的二进制位, 全部为这个符号的unicode码.

nUnicode符号范围UTF-8编码方式
1 0000 0000 - 0000 007F 0xxxxxxx
2 0000 0080 - 0000 07FF 110xxxxx 10xxxxxx
3 0000 0800 - 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
4 0001 0000 - 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5 0020 0000 - 03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6 0400 0000 - 7FFF FFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

注:在UTF-8编码中,英文字符占一个字节,中文字符占用3个字节。

总结

1、中文操作系统默认ansi编码,生成的txt文件默认为ansi编码。

2、国际文档(txt和xml)使用unicode编码是正宗做法;操作系统和浏览器都能够“理解”unicode编码。浏览器“迫于压力”才“理解”utf-8编码。但是,操作系统有时只认unicode编码。

3、Windows记事本有四个编码选项:ANSI、Unicode、Unicode Big Endian和UTF-8。

  • ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。
  • Unicode编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码。这个选项用的little endian格式。
  • Unicode big endian编码与上一个选项相对应。采用big endian格式。
  • UTF-8指带BOM 的UTF-8。

ANSI、UTF-8、Unicode转换


Windows Unicode and Character Sets

Unicode编码字符集是最通用的字符编码标准,Windows应用程序使用Unicode字符集的UTF-16实现版本。同时,Windows也支持传统的字符集:单字节字符集(Single-byte character sets, SBCS)和多字节字符集(Multibyte character sets)。

很多Windows API函数拥有“A”和“W”版本,“A”版本基于Windows Code Page,而“W”版本则基于Unicode字符。应用程序可以通过WideCharToMultiByteMultiByteToWideChar两个函数来转换Unicode字符串和基于Windows Code Page字符串。虽然函数名中含有“MultiByte”,这些函数实际上能处理SBCS、DBCS和multibyte character set Code page。

编码转换

在Windows平台下,ANSI、UTF-8、Unicode三者之间的转换主要依赖于WideCharToMultiByteMultiByteToWideChar两个函数。

  • Unicode转UFT-8:设置WideCharToMultiByte的CodePage参数为CP_UTF8;
  • UTF-8转Unicode:设置MultiByteToWideChar的CodePage参数为CP_UTF8
  • Unicode转ANSI:设置WideCharToMultiByte的CodePage参数为CP_ACP;
  • ANSI转Unicode:设置MultiByteToWideChar的CodePage参数为CP_ACP;
  • UTF-8转ANSI:先将UTF-8转换为Unicode,再将Unicode转换成ANSI;
  • ANSI转UTF-8:先将ANSI转换为Unciode,再将Unicode转换成ANSI。


本文内容应该存在错误或者是以偏概全的问题,后续会继续深入了解。



作者:小熊猜猜我有几颗糖
链接:https://www.jianshu.com/p/c23f3ea5443d
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。