结论

宽字符类型wchar_t

locale

为什么需要宽字符类型

多字节字符串和宽字符串相互转换

最近使用到了wchar_t类型,所以准备详细探究下,没想到水还挺深,网上的资料大多都是复制粘贴,只有个结论,也没个验证过程。本文记录探究的过程及结论,如有不对请指正。

Unicode、UCS

UCS(Universal Character Set)本质上就是一个字符集。

Unicode的开发结合了国际标准化组织所制定的 ISO/IEC 10646,即通用字符集(

Universal Character Set, UCS)。Unicode 与 ISO/IEC 10646 在编码的运作原理相同,但 The Unicode Standard 包含了更详尽的实现信息、涵盖了更细节的主题,诸如比特编码(bitwise encoding)、校对以及呈现等。摘自(Unicode)

所以也可以简单的理解为,Unicode和UCS等价,都是字符集。

UCS编码的长度是31位,可用4个字节表示,可以表示2的31次方个字符。如果两个字符的高位相同,只有低16位不同,则它们属于同一平面,所以一个平面由2的16次方个字符组成。目前大部分字符都位于第一个平面称为BMP。BMP的编码通常以U+xxxx这种形式表示,其中x是16进制数。

比如中文“你”对应的UCS编码为U+4f60,“好”对应的UCS编码为U+597d。更多中文编码可以在Unicode编码表中查询。

有了UCS编码,任何一个字符在计算机中都最多可以用四个字节来表示,称为码点。

UTF8

现在有了UCS字符集,那么一个字符在计算机中真的要按四个字节(UTF-32)来存储吗?

答案是否定的,一方面每个字符都按四字节来存储非常浪费空间,因为大部分字符都在BMP,只有后16位有效,前16位都是0。另一方面这与c语言不兼容,在c语言中0字节表示字符串的结尾,库函数strlen等函数依赖这一点,如果按UTF-32存储,其中有很多0字节并不表示字符串结尾。

Ken Thompson发明了UTF-8编码,可以很好的解决以上问题。Unicode 和 UTF-8 之间的转换关系表如下:

码点起 值码点 终值字节序列 Byte1 Byte2 Byte3 Byte4 Byte5 Byte6

U+0000 U+007F 1 0xxxxxxx

U+0080 U+07FF 2 110xxxxx 10xxxxxx

U+0800 U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx

U+10000 U+1FFFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

U+200000 U+3FFFFFF 5 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

U+4000000 U+7FFFFFFF 6 111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx


第一个字节要么最高位是0(ASCII码),要么最高位都是1,最高位之后的1的个数决定了后面的有多少个字节也属于当前字符编码,例如111110xx,最高位之后还有4个1,表示后面的4个字节属于当前编码。后面的每个字节的最高位都是10,可以和第一个字节区分开来。后面字节的x表示的就是UCS编码。所以UTF-8就像一列火车,第一个字节是车头,包含了后面的哪几个字节也属于当前这列火车的信息,后面的字节是车厢,其中承载着UCS编码。

以中文字符“你”为例,对应的Unicode为"U+4f60",二进制表示为0100 1111 0110 0000。按照表中的规则编码成UTF-8就是11100100 10111101 10100000(0xe4 0xbd 0xa0)。

结论

Unicode本质是字符集,在这个集合中的任意一个字符都可以用一个四字节来表示。

UTF-8是编码规则,可以通过这个规则将Unicode字符集中任一字符对应的字节转换为另一个字节序列。UTF-8只是编码规则中的一种,其它的编码规则还有UTF-16,UTF-32等。

宽字符类型wchar_t

在介绍宽字符前先了解下locale。因为多字节字符串和宽字符串的转换和locale相关。

locale

什么是locale

区域设置(locale),也称作“本地化策略集”、“本地环境”,是表达程序用户地区方面的软件设定。在linux执行locale可以查看当前locale设置:

ubuntu@VM-0-16-ubuntu:~$ localeLANG=zh_CN.UTF-8LANGUAGE=LC_CTYPE="zh_CN.UTF-8"LC_NUMERIC="zh_CN.UTF-8"LC_TIME="zh_CN.UTF-8"LC_COLLATE="zh_CN.UTF-8"LC_MONETARY="zh_CN.UTF-8"LC_MESSAGES="zh_CN.UTF-8"LC_PAPER="zh_CN.UTF-8"LC_NAME="zh_CN.UTF-8"LC_ADDRESS="zh_CN.UTF-8"LC_TELEPHONE="zh_CN.UTF-8"LC_MEASUREMENT="zh_CN.UTF-8"LC_IDENTIFICATION="zh_CN.UTF-8"LC_ALL=

可以将locale理解为一系列环境变量。locale环境变量值的格式为language_area.charset。languag表示语言,例如英语或中文;area表示使用该语言的地区,例如美国或者中国大陆;charset表示字符集编码,例如UTF-8或者GBK。

这些环境变量会对日期格式,数字格式,货币格式,字符处理等多个方面产生影响。

参考资料:

locale wiki

Environment Variables

如何设置系统默认的locale

修改配置文件/etc/default/locale,比如要将locale设为zh_CN.UTF-8,添加如下语句LANG=zh_CN.UTF-8

locale环境变量有何作用

以LC_TIME为例,该变量会影响strftime()等函数。size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr)

strftime根据format中定义的格式化规则,格式化结构timeptr表示的时间,并把它存储在str中。

#include<locale.h>#include<stdio.h>#include<time.h>intmain(){time_tcurrtime;structtm*timer;charbuffer[80]; time( &currtime ); timer = localtime( &currtime );printf("Locale is: %sn", setlocale(LC_TIME,"en_US.iso88591")); strftime(buffer,80,"%c", timer );printf("Date is: %sn", buffer);printf("Locale is: %sn", setlocale(LC_TIME,"zh_CN.UTF-8")); strftime(buffer,80,"%c", timer );printf("Date is: %sn", buffer);printf("Locale is: %sn", setlocale(LC_TIME,"")); strftime(buffer,80,"%c", timer );printf("Date is: %sn", buffer);return(0);}

编译后运行结果如下:

Localeis: en_US.iso88591Dateis: Sun07Jul201904:08:39PM CSTLocaleis: zh_CN.UTF-8Dateis:2019年07月07日 星期日16时08分39秒Localeis: zh_CN.UTF-8Dateis:2019年07月07日 星期日16时08分39秒

可以看到对LC_TIME设置不同的值后,调用strftime()会产生不同的结果。

char* setlocale (int category, const char* locale);可以用来对当前程序进行地域设置。

category:用于指定设置影响的范围,LC_CTYPE影响字符分类和字符转换,LC_TIME影响日期和时间的格式,LC_ALL影响所有内容。

locale:用于指定变量的值,上例中分别使用了"en_US.iso88591","zh_CN.UTF-8"和空字符串"",""表示使用当前操作系统默认的区域设置。

参考资料:

setlocale()

为什么需要宽字符类型

“你好”对应的Unicode分别为"U+4f60"和"U+597d”,对应的UTF-8编码分别为“0xe4 0xbd 0xa0”和“0xe5 0xa5 0xbd”

多字节字符串在编译后的可执行文件以UTF-8编码保存

#include<stdio.h>#include<string.h>intmain(void){chars[] ="你好";size_tlen =strlen(s);printf("len = %dn", (int)len);printf("%sn", s);return0;}

编译后执行,输出如下:

len = 6

你好

od编译后的可执行文件,可以发现"你好"以UFT-8编码保存,也就是“0xe4 0xbd 0xa0”和“0xe5 0xa5 0xbd”6个字节。

strlen()函数只管结尾的0字节而不管字符串里存的是什么,所以len是6,也就是“你好”的UFT-8编码的字节数。

printf("%sn", s);相当于将“0xe4 0xbd 0xa0”和“0xe5 0xa5 0xbd”6个字节write到当前终端的设备文件,如果当前终端的驱动程序能识别UTF-8编码就能打印汉字,如果当前字符终端的驱动程序不能识别UTF-8就打印不出汉字。

宽字符串在编译后可执行文件中以Unicode保存

#include<wchar.h>#include<stdio.h>#include<locale.h>intmain(void){ setlocale(LC_ALL,"zh_CN.UTF-8");//设置localewchar_ts[] =L"你好";size_tlen = wcslen(s);printf("len = %dn", (int)len);printf("%lsn", s);return0;}

编译后执行,输出如下:

len = 2

你好

对编译后的可执行文件执行od命令,可以找到如下这些字节:

193 000302000100020` O 00} Y 00n 0001940002000100004f600000597d0000000a

00004f60正是“你”对应的Unicode,0000597d是“好”对应的Unicode。所以对于宽字符串是按Unicode保存在可执行文件中的。

wchar_t是宽字符类型。在字符常量或者字符串前加L就表示宽字符常量或者宽字符串。所以len是2。

wcslen()和strlen()不同,不是见到0字节就结束而是要遇到UCS编码为0的字符才结束。

目前宽字符在内存中以Unicode进行保存,但是要write到终端仍然需要以多字节编码输出,这样终端驱动程序才能识别,所以printf在内部把宽字符串转换成多字节字符串,然后write出去。这个转换过程受locale影响,setlocale(LC_ALL, "zh_CN.UTF-8");设置当前进程的LC_ALL为zh_CN.UTF-8,所以printf将Unicode转成多字节的UTF-8编码,然后write到终端设备。如果将setlocale(LC_ALL, "zh_CN.UTF-8");改为setlocale(LC_ALL, en_US.iso88591):打印结果中将不会输出"你好"。

一般来说程序在内存计算时通常以宽字符编码,存盘或者网络发送则用多字节编码。

多字节字符串和宽字符串相互转换

c语言中提供了多字节字符串和宽字符串相互转换的函数。

#include<stdlib.h>size_tmbstowcs(wchar_t*dest,constchar*src,size_tn);size_twcstombs(char*dest,constwchar_t*src,size_tn);

mbstowcs()将多字节字符串转换为宽字符串。

wcstombs()将宽字符串转换为多字节字符串。

考虑下面的例子:

#include<locale.h>#include<stdio.h>#include<time.h>#include<stdlib.h>#include<wchar.h>#include<string.h>wchar_t* str2wstr(constcharconst* s) {constsize_tbuffer_size =strlen(s) +1;wchar_t* dst_wstr = (wchar_t*)malloc(buffer_size *sizeof(wchar_t)); wmemset(dst_wstr,0, buffer_size); mbstowcs(dst_wstr, s, buffer_size);returndst_wstr;}voidprintBytes(constunsignedcharconst* s,intlen){for(inti =0; i < len; i++) {printf("0x%02x ", *(s + i)); }printf("n");}intmain(){chars[10] ="你好";//内存中对应0xe4 0xbd 0xa0 0xe5 0xa5 0xbd 0x00 wchar_tws[10] =L"你好";//内存中对应0x60 0x4f 0x00 0x00 0x7d 0x59 0x00 0x00 0x00 0x00 0x00 0x00 printf("Locale is: %sn", setlocale(LC_ALL,"zh_CN.UTF-8"));//Locale is: zh_CN.UTF-8printBytes(s,7);//0xe4 0xbd 0xa0 0xe5 0xa5 0xbd 0x00 printBytes((char*)ws,12);//0x60 0x4f 0x00 0x00 0x7d 0x59 0x00 0x00 0x00 0x00 0x00 0x00 printBytes((char*)str2wstr(s),12);//0x60 0x4f 0x00 0x00 0x7d 0x59 0x00 0x00 0x00 0x00 0x00 0x00 return(0);}

编译后,执行结果如下:

Locale is: zh_CN.UTF-80xe40xbd0xa00xe50xa50xbd0x000x600x4f0x000x000x7d0x590x000x000x000x000x000x000x600x4f0x000x000x7d0x590x000x000x000x000x000x00

第二行输出也印证了我们之前说的多字节字符串在内存中以UTF-8存储,"0xe4 0xbd 0xa0 0xe5 0xa5 0xbd"正是"你好"的UTF-8编码。

第三行输出印证了之前说的宽字符串在内存中以Unicode存储,"0x60 0x4f 0x00 0x00 0x7d 0x59 0x00 0x00"正好是宽字符串L"你好"对应的Unicode。

setlocale(LC_ALL, "zh_CN.UTF-8")设置locale,程序将以UTF-8解码宽字符串。调用mbstowcs()后,可以看到“你好”的UTF-8编码 "0xe4 0xbd 0xa0 0xe5 0xa5 0xbd 0x00"确实被转换成了“你好”对应的Unicode "0x60 0x4f 0x00 0x00 0x7d 0x59 0x00 0x00 0x00 0x00 0x00 0x00"。

如果将setlocale(LC_ALL, "zh_CN.UTF-8")换成setlocale(LC_ALL, "en_US.iso88591 ");那么最后一行的输出也就会不一样。

需要更多点击这里

c++ unicode转换中文_彻底弄懂UTF-8、Unicode、宽字符、locale相关推荐

  1. php将unicode转换成中文乱码,php如何将unicode转换中文

    php如何将unicode转换中文,中文,方法,转化成,文件,中文网 php如何将unicode转换中文 易采站长站,站长之家为您整理了php如何将unicode转换中文的相关内容. php将unic ...

  2. python2 unicode转换中文

    python2 unicode转换中文 unicode.decode('string-escape')json.dumps({"k":"v"},ensure_a ...

  3. 如何改变php的语言变中文,如何使php将unicode转换中文

    如何使php将unicode转换中文 发布时间:2020-07-15 15:34:34 来源:亿速云 阅读:105 作者:Leah 如何使php将unicode转换中文?针对这个问题,这篇文章详细介绍 ...

  4. python,unicode转换中文,中文转换unicode

    Unicode转中文 python2: ``` >>> s='\u54c8\u54c8' >>> print s.encode('unicode_escape') ...

  5. java接口防抖_彻底弄懂节流和防抖

    节流和防抖 这两个东西,你肯定听过,就是两种优化浏览器性能的手段.相关文章你肯定也看过,如果还是不太清楚,没关系,看完这篇短文,相信你能轻松理解其中差别. 防抖(deounce) 我们先说防抖吧,这里 ...

  6. sql unicode转中文_汉字转拼音的Java类库 JPinyin

    JPinyin是一个汉字转拼音的Java开源类库,在PinYin4j的功能基础上做了一些改进. [JPinyin主要特性] 1.准确.完善的字库: Unicode编码从4E00-9FA5范围及3007 ...

  7. url 转换中文_数字快速转换成中文大写,我有妙招

    昨天给大家介绍第二种普通数字转中文大写的方法:NumberString函数法.这种方法和第一种单元格格式法一样存在一个缺陷就是只能对正整数进行转换,所以它不能直接用于平时的财务工作中,不过没关系,既然 ...

  8. 离线缓存占内存吗_彻底弄懂浏览器缓存策略

    浏览器缓存策略对于前端开发同学来说不陌生,大家都有一定的了解,但如果没有系统的归纳总结,可能三言两语很难说明白,甚至说错,尤其在面试过程中感触颇深,很多候选人对这类基础知识竟然都是一知半解,说出几个概 ...

  9. sizebox模型下载_彻底弄懂CSS盒模型BoxModel

    前言 假如你想尝试一下不用表格来排版网页,而是用CSS来排版你的网页,也就是常听的用DIV来编排你的网页结构,又或者说你想学习网页标准设计,再或者说你的上司要你改变传统的表格排版方式,提高企业竞争力, ...

最新文章

  1. c++编写算法判断二叉树是否为完全二叉树_[校招-算法题] 二叉树基础算法1
  2. 页面某一个元素跟随输入框输入内容动态变化
  3. JAVA实验一——数组类编程
  4. [scikit-learn 机器学习] 3. K-近邻算法分类和回归
  5. oracle inside(4)
  6. 114_Power Pivot 销售订单之销售额、成本、利润率相关
  7. 剑指offer31-栈的压入、弹出序列
  8. 利用DataSnap的回调功能在客户端显示服务器方法的执行进度
  9. matlab图像大作业,MATLAB图像大作业
  10. [论文阅读] IL2M: Class Incremental Learning With Dual Memory
  11. 和 Eclipse 并肩十年后,我终于「投敌」IDEA 了
  12. HttpHandler和ashx使用Session 出现未初始化异常
  13. 阿里云云计算 26 SLB的配置
  14. 台湾-李宏毅教授的深度学习视频教程
  15. 压力传感器的误差补偿
  16. 【咸鱼教程】TextureMerger1.6.6 一:Egret MovieClip的制作和使用
  17. 共享单车系统的软件测试报告,共享单车APP实验报告模板.docx
  18. java jar 最大内存大小_Java运行Jar包内存配置的操作
  19. [动态规划] 放置街灯 Uva 10859 - Placing Lampposts
  20. 黑马程序员软件测试课后习题答案

热门文章

  1. vue操作,显示数据
  2. android中的相对路径
  3. 第七课、Qt中的坐标系统------------------狄泰软件学院
  4. thinkphp字符截取函数msubstr()
  5. 关于Java栈与堆的思考
  6. python 时间序列分析之ARIMA(不使用第三方库)
  7. 详解 ASP.NET异步
  8. 百度不收录你网站的原因
  9. C#代码规范 .NET程序员需要提升的修养1
  10. Hibernate-HQL