c++ unicode转换中文_彻底弄懂UTF-8、Unicode、宽字符、locale
結(jié)論
寬字符類型wchar_t
locale
為什么需要寬字符類型
多字節(jié)字符串和寬字符串相互轉(zhuǎn)換
最近使用到了wchar_t類型,所以準(zhǔn)備詳細(xì)探究下,沒想到水還挺深,網(wǎng)上的資料大多都是復(fù)制粘貼,只有個(gè)結(jié)論,也沒個(gè)驗(yàn)證過程。本文記錄探究的過程及結(jié)論,如有不對(duì)請(qǐng)指正。
Unicode、UCS
UCS(Universal Character Set)本質(zhì)上就是一個(gè)字符集。
Unicode的開發(fā)結(jié)合了國際標(biāo)準(zhǔn)化組織所制定的 ISO/IEC 10646,即通用字符集(
Universal Character Set, UCS)。Unicode 與 ISO/IEC 10646 在編碼的運(yùn)作原理相同,但 The Unicode Standard 包含了更詳盡的實(shí)現(xiàn)信息、涵蓋了更細(xì)節(jié)的主題,諸如比特編碼(bitwise encoding)、校對(duì)以及呈現(xiàn)等。摘自(Unicode)
所以也可以簡單的理解為,Unicode和UCS等價(jià),都是字符集。
UCS編碼的長度是31位,可用4個(gè)字節(jié)表示,可以表示2的31次方個(gè)字符。如果兩個(gè)字符的高位相同,只有低16位不同,則它們屬于同一平面,所以一個(gè)平面由2的16次方個(gè)字符組成。目前大部分字符都位于第一個(gè)平面稱為BMP。BMP的編碼通常以U+xxxx這種形式表示,其中x是16進(jìn)制數(shù)。
比如中文“你”對(duì)應(yīng)的UCS編碼為U+4f60,“好”對(duì)應(yīng)的UCS編碼為U+597d。更多中文編碼可以在Unicode編碼表中查詢。
有了UCS編碼,任何一個(gè)字符在計(jì)算機(jī)中都最多可以用四個(gè)字節(jié)來表示,稱為碼點(diǎn)。
UTF8
現(xiàn)在有了UCS字符集,那么一個(gè)字符在計(jì)算機(jī)中真的要按四個(gè)字節(jié)(UTF-32)來存儲(chǔ)嗎?
答案是否定的,一方面每個(gè)字符都按四字節(jié)來存儲(chǔ)非常浪費(fèi)空間,因?yàn)榇蟛糠肿址荚贐MP,只有后16位有效,前16位都是0。另一方面這與c語言不兼容,在c語言中0字節(jié)表示字符串的結(jié)尾,庫函數(shù)strlen等函數(shù)依賴這一點(diǎn),如果按UTF-32存儲(chǔ),其中有很多0字節(jié)并不表示字符串結(jié)尾。
Ken Thompson發(fā)明了UTF-8編碼,可以很好的解決以上問題。Unicode 和 UTF-8 之間的轉(zhuǎn)換關(guān)系表如下:
碼點(diǎn)起 值碼點(diǎn) 終值字節(jié)序列 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
第一個(gè)字節(jié)要么最高位是0(ASCII碼),要么最高位都是1,最高位之后的1的個(gè)數(shù)決定了后面的有多少個(gè)字節(jié)也屬于當(dāng)前字符編碼,例如111110xx,最高位之后還有4個(gè)1,表示后面的4個(gè)字節(jié)屬于當(dāng)前編碼。后面的每個(gè)字節(jié)的最高位都是10,可以和第一個(gè)字節(jié)區(qū)分開來。后面字節(jié)的x表示的就是UCS編碼。所以UTF-8就像一列火車,第一個(gè)字節(jié)是車頭,包含了后面的哪幾個(gè)字節(jié)也屬于當(dāng)前這列火車的信息,后面的字節(jié)是車廂,其中承載著UCS編碼。
以中文字符“你”為例,對(duì)應(yīng)的Unicode為"U+4f60",二進(jìn)制表示為0100 1111 0110 0000。按照表中的規(guī)則編碼成UTF-8就是11100100 10111101 10100000(0xe4 0xbd 0xa0)。
結(jié)論
Unicode本質(zhì)是字符集,在這個(gè)集合中的任意一個(gè)字符都可以用一個(gè)四字節(jié)來表示。
UTF-8是編碼規(guī)則,可以通過這個(gè)規(guī)則將Unicode字符集中任一字符對(duì)應(yīng)的字節(jié)轉(zhuǎn)換為另一個(gè)字節(jié)序列。UTF-8只是編碼規(guī)則中的一種,其它的編碼規(guī)則還有UTF-16,UTF-32等。
寬字符類型wchar_t
在介紹寬字符前先了解下locale。因?yàn)槎嘧止?jié)字符串和寬字符串的轉(zhuǎn)換和locale相關(guān)。
locale
什么是locale
區(qū)域設(shè)置(locale),也稱作“本地化策略集”、“本地環(huán)境”,是表達(dá)程序用戶地區(qū)方面的軟件設(shè)定。在linux執(zhí)行l(wèi)ocale可以查看當(dāng)前l(fā)ocale設(shè)置:
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理解為一系列環(huán)境變量。locale環(huán)境變量值的格式為language_area.charset。languag表示語言,例如英語或中文;area表示使用該語言的地區(qū),例如美國或者中國大陸;charset表示字符集編碼,例如UTF-8或者GBK。
這些環(huán)境變量會(huì)對(duì)日期格式,數(shù)字格式,貨幣格式,字符處理等多個(gè)方面產(chǎn)生影響。
參考資料:
locale wiki
Environment Variables
如何設(shè)置系統(tǒng)默認(rèn)的locale
修改配置文件/etc/default/locale,比如要將locale設(shè)為zh_CN.UTF-8,添加如下語句LANG=zh_CN.UTF-8
locale環(huán)境變量有何作用
以LC_TIME為例,該變量會(huì)影響strftime()等函數(shù)。size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr)
strftime根據(jù)format中定義的格式化規(guī)則,格式化結(jié)構(gòu)timeptr表示的時(shí)間,并把它存儲(chǔ)在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);}
編譯后運(yùn)行結(jié)果如下:
Localeis: en_US.iso88591Dateis: Sun07Jul201904:08:39PM CSTLocaleis: zh_CN.UTF-8Dateis:2019年07月07日 星期日16時(shí)08分39秒Localeis: zh_CN.UTF-8Dateis:2019年07月07日 星期日16時(shí)08分39秒
可以看到對(duì)LC_TIME設(shè)置不同的值后,調(diào)用strftime()會(huì)產(chǎn)生不同的結(jié)果。
char* setlocale (int category, const char* locale);可以用來對(duì)當(dāng)前程序進(jìn)行地域設(shè)置。
category:用于指定設(shè)置影響的范圍,LC_CTYPE影響字符分類和字符轉(zhuǎn)換,LC_TIME影響日期和時(shí)間的格式,LC_ALL影響所有內(nèi)容。
locale:用于指定變量的值,上例中分別使用了"en_US.iso88591","zh_CN.UTF-8"和空字符串"",""表示使用當(dāng)前操作系統(tǒng)默認(rèn)的區(qū)域設(shè)置。
參考資料:
setlocale()
為什么需要寬字符類型
“你好”對(duì)應(yīng)的Unicode分別為"U+4f60"和"U+597d”,對(duì)應(yīng)的UTF-8編碼分別為“0xe4 0xbd 0xa0”和“0xe5 0xa5 0xbd”
多字節(jié)字符串在編譯后的可執(zhí)行文件以UTF-8編碼保存
#include<stdio.h>#include<string.h>intmain(void){chars[] ="你好";size_tlen =strlen(s);printf("len = %dn", (int)len);printf("%sn", s);return0;}
編譯后執(zhí)行,輸出如下:
len = 6
你好
od編譯后的可執(zhí)行文件,可以發(fā)現(xiàn)"你好"以UFT-8編碼保存,也就是“0xe4 0xbd 0xa0”和“0xe5 0xa5 0xbd”6個(gè)字節(jié)。
strlen()函數(shù)只管結(jié)尾的0字節(jié)而不管字符串里存的是什么,所以len是6,也就是“你好”的UFT-8編碼的字節(jié)數(shù)。
printf("%sn", s);相當(dāng)于將“0xe4 0xbd 0xa0”和“0xe5 0xa5 0xbd”6個(gè)字節(jié)write到當(dāng)前終端的設(shè)備文件,如果當(dāng)前終端的驅(qū)動(dòng)程序能識(shí)別UTF-8編碼就能打印漢字,如果當(dāng)前字符終端的驅(qū)動(dòng)程序不能識(shí)別UTF-8就打印不出漢字。
寬字符串在編譯后可執(zhí)行文件中以Unicode保存
#include<wchar.h>#include<stdio.h>#include<locale.h>intmain(void){ setlocale(LC_ALL,"zh_CN.UTF-8");//設(shè)置localewchar_ts[] =L"你好";size_tlen = wcslen(s);printf("len = %dn", (int)len);printf("%lsn", s);return0;}
編譯后執(zhí)行,輸出如下:
len = 2
你好
對(duì)編譯后的可執(zhí)行文件執(zhí)行od命令,可以找到如下這些字節(jié):
193 000302000100020` O 00} Y 00n 0001940002000100004f600000597d0000000a
00004f60正是“你”對(duì)應(yīng)的Unicode,0000597d是“好”對(duì)應(yīng)的Unicode。所以對(duì)于寬字符串是按Unicode保存在可執(zhí)行文件中的。
wchar_t是寬字符類型。在字符常量或者字符串前加L就表示寬字符常量或者寬字符串。所以len是2。
wcslen()和strlen()不同,不是見到0字節(jié)就結(jié)束而是要遇到UCS編碼為0的字符才結(jié)束。
目前寬字符在內(nèi)存中以Unicode進(jìn)行保存,但是要write到終端仍然需要以多字節(jié)編碼輸出,這樣終端驅(qū)動(dòng)程序才能識(shí)別,所以printf在內(nèi)部把寬字符串轉(zhuǎn)換成多字節(jié)字符串,然后write出去。這個(gè)轉(zhuǎn)換過程受locale影響,setlocale(LC_ALL, "zh_CN.UTF-8");設(shè)置當(dāng)前進(jìn)程的LC_ALL為zh_CN.UTF-8,所以printf將Unicode轉(zhuǎn)成多字節(jié)的UTF-8編碼,然后write到終端設(shè)備。如果將setlocale(LC_ALL, "zh_CN.UTF-8");改為setlocale(LC_ALL, en_US.iso88591):打印結(jié)果中將不會(huì)輸出"你好"。
一般來說程序在內(nèi)存計(jì)算時(shí)通常以寬字符編碼,存盤或者網(wǎng)絡(luò)發(fā)送則用多字節(jié)編碼。
多字節(jié)字符串和寬字符串相互轉(zhuǎn)換
c語言中提供了多字節(jié)字符串和寬字符串相互轉(zhuǎn)換的函數(shù)。
#include<stdlib.h>size_tmbstowcs(wchar_t*dest,constchar*src,size_tn);size_twcstombs(char*dest,constwchar_t*src,size_tn);
mbstowcs()將多字節(jié)字符串轉(zhuǎn)換為寬字符串。
wcstombs()將寬字符串轉(zhuǎn)換為多字節(jié)字符串。
考慮下面的例子:
#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] ="你好";//內(nèi)存中對(duì)應(yīng)0xe4 0xbd 0xa0 0xe5 0xa5 0xbd 0x00 wchar_tws[10] =L"你好";//內(nèi)存中對(duì)應(yīng)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);}
編譯后,執(zhí)行結(jié)果如下:
Locale is: zh_CN.UTF-80xe40xbd0xa00xe50xa50xbd0x000x600x4f0x000x000x7d0x590x000x000x000x000x000x000x600x4f0x000x000x7d0x590x000x000x000x000x000x00
第二行輸出也印證了我們之前說的多字節(jié)字符串在內(nèi)存中以UTF-8存儲(chǔ),"0xe4 0xbd 0xa0 0xe5 0xa5 0xbd"正是"你好"的UTF-8編碼。
第三行輸出印證了之前說的寬字符串在內(nèi)存中以Unicode存儲(chǔ),"0x60 0x4f 0x00 0x00 0x7d 0x59 0x00 0x00"正好是寬字符串L"你好"對(duì)應(yīng)的Unicode。
setlocale(LC_ALL, "zh_CN.UTF-8")設(shè)置locale,程序?qū)⒁訳TF-8解碼寬字符串。調(diào)用mbstowcs()后,可以看到“你好”的UTF-8編碼 "0xe4 0xbd 0xa0 0xe5 0xa5 0xbd 0x00"確實(shí)被轉(zhuǎn)換成了“你好”對(duì)應(yīng)的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 ");那么最后一行的輸出也就會(huì)不一樣。
需要更多點(diǎn)擊這里
總結(jié)
以上是生活随笔為你收集整理的c++ unicode转换中文_彻底弄懂UTF-8、Unicode、宽字符、locale的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 特斯拉宣布开启调价 Model 3/Y后
- 下一篇: c++ list box 字体设置_上海