字符集、字符编码和字符编码转换

字符集和字符编码

字符集:一个字符系统的集合,如表示英语字符的ASCII字符集,表示汉字的gb2312、GBK字符集。

字符编码:字符集合中的每个字符在计算机系统中用二进制表示和存储。因为计算机存储单位一般为字节,因此各种字符编码系统以字节为基础进行表示。

根据每个字符集中的一个字符用几个字节表示,字符集可以分为以下三大类:

(1)单字节字符集,字符集中的每个字符只用一个字节表示。

ASCII:用一个字节的低7bit表示一个字符,范围从0x00 - 0x7F 共可以表示128个字符,包括英文大小写字母、数字。

EASCII:使用一个字节的全部8bit表示字符,在原来基础上增加表示一些控制字符、特殊字符等。扩展后范围从0x00-0xFF共可以表示256个字符。

(2)多字节字符集(MBCS)

ANSI字符集:ASCII只能表示127个字符,即使扩展后也只能表示256个字符,对于表示欧美国家的字符够用了,但是对于其他国家文字,比如中国的汉字,就显然不够用了。因此其他国家为表示自己的字符集制订新的编码方案,主要方法是用多个字节表示一个字符。但是这些编码方案必须满足美国的ANSI标准,即要兼容ASCII字符集,任何新的字符集编码都必须保留0x00 - 0x7F的127个编码表示ASCII字符,其他字符的表示使用大于128(0x7F)的字节作为一个Leading Byte,紧跟在Leading Byte后的第二(甚至第三)个字符与 Leading Byte一起作为实际的编码。所有符合ANSI标准的编码都称为ANSI编码。例如,

gb2312,用一个小于0x7F的字节表示ASCII字符,用均大于0x7F的两个字节表示一个汉字,两个字节的编码范围为0xA1A1 - 0xFEFE,这个范围可以表示23901个汉字。

GBK,表示方法和gb2312一样,只不过对两字节的编码范围进行了扩充,用0x8140 - 0xFEFE表示其他汉字,最多可以表示3万多个汉字。

GB18030,在GBK表示的字符集基础上进一步扩充,增加了少数民族的字符,是目前最全的中文字符集,它并没有确定所有的字形,只是规定了编码范围,留待以后扩充。GB18030最多可以用四个字节表示一个字符,用两个字节表示时与GBK兼容,扩充部分用四个字节表示,每个字节规定不同的编码范围,其编码范围是首字节0x81-0xfe、二字节0x30-0x39、三字节0x81-0xfe、四字节0x30-0x39。

由以上例子可知,符合ANSI标准的编码规则,在表示字符集中的字符时字节数量不固定,例如gb2312用一个或者两个字节表示、GB18030用一个、两个或者四个字节表示。

从ANSI标准派生的字符集同城为ANSI字符集,每种ANSI字符集编码时使用的字节数不是固定的,因此它们都称为MBCS(Multi-Byte Chactacter System,即多字节字符系统)

(3)Unicode字符集

每个国家都制定一个符合ANSI标准的字符集,有可能同一个编码对应不同字符集中的不同字符,会造成乱码。因此,将全世界的所有字符整理成统一的字符集,统一编码,所有国家都使用这个字符集就不会出现乱码问题,这就是unicode字符集。

Unicode除规定了一个字符集,同时也给字符集中的每个字符分配了一个编号,称为代码点(code point)。代码点用两字节表示,格式为U+XXXX,取值范围为U+0000~U+FFFF,用两个字节表示unicode字符集的编号称为UCS-2(Universal Character Set coded in 2 octets)。

为了表示更多的字符,又提出了UCS-4,即用四个字节表示一个字符的编号, 它的范围为 U+00000000~U+7FFFFFFF。不过目前并未全部使用,只使用了U+00010000一直到U+0010FFFF。其中 U+00000000~U+0000FFFF和UCS-2是一样的。

扩展到UCS-4后,Unicode将所有字符进行分区定义,每个区中有65536(2^16)个字符,称为一个平面,最前面的65536个字符位,称为基本平面(缩写BMP),也就是原来UCS-2表示的字符,代码点为U+0000到U+FFFF。所有最常见的字符都放在这个平面。剩下的字符都放在辅助平面(缩写SMP),码点范围从U+010000一直到U+10FFFF。

Unicode只是一个字符集,只规定字符的二进制代码编号,没规定字符是如何进行存储,所以这也造成了一些问题。比如,汉字的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。也就是说,这个符号至少需要2个字节来表示其它更大的符号。

因为需要至少2个字节来表示更大的符号,这就导致了两个问题,第一个是如何区别该编码是Unicode还是ASCII,计算机怎么知道该字符是2个字节还是3个字节甚至更多。第二个问题是,众所周知,英文字母只需要一个字节来进行编码,但是如果用2个字节3个字节甚至更多字节来表示这就会造成相应倍数的存储空间的增加,造成了存储空间上的极大浪费。

所以最后也出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式来表示Unicode,随着互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式,同时也存在UTF-16以及UTF-32,以下我们会对其分别展开讲述。

UFT-8:一种变长的编码方案,使用 1~6 个字节来存储;
UFT-32:一种固定长度的编码方案,不管字符编号大小,始终使用 4 个字节来存储;
UTF-16:介于 UTF-8 和 UTF-32 之间,使用 2 个或者 4 个字节来存储,长度既固定又可变。


UTF 是 Unicode Transformation Format 的缩写,意思是“Unicode转换格式”,后面的数字表明至少使用多少个比特位(Bit)来存储字符。

1) UTF-8
UTF-8 的编码规则很简单:如果只有一个字节,那么最高的比特位为 0;如果有多个字节,那么第一个字节从最高位开始,连续有几个比特位的值为 1,就使用几个字节编码,剩下的字节均以 10 开头。

具体的表现形式为:

0xxxxxxx:单字节编码形式,这和 ASCII 编码完全一样,因此 UTF-8 是兼容 ASCII 的;
110xxxxx 10xxxxxx:双字节编码形式;
1110xxxx 10xxxxxx 10xxxxxx:三字节编码形式;
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字节编码形式。
xxx 就用来存储 Unicode 中的字符编号。

下面是一些字符的编码实例(绿色部分表示本来的 Unicode 编号):

字符    N    æ    ⻬
Unicode 编号(二进制)    01001110    11100110    00101110 11101100
Unicode 编号(十六进制)    4E    E6    2E EC
UTF-8 编码(二进制)    01001110    11000011 10100110    11100010 10111011 10101100
UTF-8 编码(十六进制)    4E    C3 A6    E2 BB AC
对于常用的字符,它的 Unicode 编号范围是 0 ~ FFFF,用 1~3 个字节足以存储,只有及其罕见,或者只有少数地区使用的字符才需要 4~6个字节存储。

2) UTF-32
UTF-32 是固定长度的编码,始终占用 4 个字节,足以容纳所有的 Unicode 字符,所以直接存储 Unicode 编号即可,不需要任何编码转换。浪费了空间,提高了效率。

3) UTF-16
UFT-16 比较奇葩,它使用 2 个或者 4 个字节来存储。

对于 Unicode 编号范围在 0 ~ FFFF 之间的字符,UTF-16 使用两个字节存储,并且直接存储 Unicode 编号,不用进行编码转换,这跟 UTF-32 非常类似。

对于 Unicode 编号范围在 10000~10FFFF 之间的字符,UTF-16 使用四个字节存储,具体来说就是:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于 D800~DBFF 之间的双字节存储,较低的一些比特位(剩下的比特位)用一个值介于 DC00~DFFF 之间的双字节存储。

如果你不理解什么意思,请看下面的表格:

Unicode 编号范围
(十六进制)    具体的 Unicode 编号
(二进制)    UTF-16 编码    编码后的
字节数
0000 0000 ~ 0000 FFFF    xxxxxxxx xxxxxxxx    xxxxxxxx xxxxxxxx    2
0001 0000---0010 FFFF    yyyy yyyy yyxx xxxx xxxx    110110yy yyyyyyyy 110111xx xxxxxxxx    4

位于 D800~0xDFFF 之间的 Unicode 编码是特别为四字节的 UTF-16 编码预留的,所以不应该在这个范围内指定任何字符。如果你真的去查看 Unicode 字符集,会发现这个区间内确实没有收录任何字符。

UTF-16 要求在制定 Unicode 字符集时必须考虑到编码问题,所以真正的 Unicode 字符集也不是随意编排字符的。

总结
只有 UTF-8 兼容 ASCII,UTF-32 和 UTF-16 都不兼容 ASCII,因为它们没有单字节编码。

另外,通常所说的Unicode编码为UTF16编码。

参考:

https://blog.csdn.net/jeffasd/article/details/84100607

https://blog.csdn.net/mjLlm/article/details/101559522

窄字符和宽字符

窄字符:在C/C++中,char是一种数据类型,规定sizeof(char)=1,即一个char占用一个字节,1Byte=8bit。并没有规定一个char就要与ASCII对应,不过,通常情况下char值与ASCII编码对应。

宽字符:标准并没有规定wchar_t占多少位,标准只是要求一个wchar_t可以表示任何系统所能认识的字符,在win32 中,wchar_t为16位;Linux中是32位。wchar_t同样没有规定编码,一个wchar_t可以不同的编码表示,不过鉴于UTF16正好是两个字节,且为通用编码方式,所以通常情况下将wchar_t与uinicode编码对应。在 win32中,wchar_t的编码是UCS-2BE(utf16BE);而Linux中是UTF-32BE(等价于UCS-4BE)。

参考:http://club.topsage.com/forum.php?mod=viewthread&tid=2227977

宽窄转换(编码)转换

宽窄转换是窄字符串和宽字符串之间的转换,其实本质上是两种字符串编码之间的转换。宽字符串的编码比较明确,目前都为UTF16编码。窄字符串因为是一系列字节表示,并不能确定是何种编码,可能是MBCS的任何一种编码例如gb2312...,也可能是utf8编码。因此在进行宽窄转换时,要指定窄字符串是何种编码,这样在转换时就可以将被转换的字符串解码到某个字符,再将该字符转换到对应的编码格式。

调用相应转换方法时都要指定窄字符串的编码格式。

1.使用C++标准库codecvt、locale、use_facet

std::codecvt 封装字符串的转换,包括宽和多字节,从一种编码到另一种。其中,std::codecvt<wchar_t,char,std::mbstate_t>用于在系统原生宽和单字节窄字符集间转换。

std::codecvt取决于std:locale,std::locale中包括了决定程序所使用的当前语言编码、日期格式、数字格式及其它与区域有关的设置,因此也决定了转码过程中窄字符串使用的编码格式。

注:

locale包括(1)系统locale,即当前程序运行的系统环境的locale(2)C locale,C本地相关函数调用时使用的locale(3)C++ locale,C++程序本地相关函数使用的locale

(1)C locale的设置函数为setlocale,例如使用setlocale( “”)初始化,会根据本地系统环境为C locale设置合适的值。当C程序启动时(main函数),如果程序中没有用setlocale函数设置地域等其他参数,那么程序运行时locale 被初始化为默认的 C locale,其采用的字符编码是所有本地 ANSI 字符集编码的公共部分,是用来书写C语言源程序的最小字符集。

(2)对于C++,std::locale(" ")获取本地系统的locale变量;std::locale::classic()用于获取C环境的本地引用;std::locale::global(loc)以 loc 替换全局 C++ 本地环境,这表示将来所有对 std::locale 的默认构造函数的调用将返回 loc 的副本。若 loc 拥有名称,则亦如同用 std::setlocale(LC_ALL, loc.name().c_str()); 替换 C 本地环境。此函数是修改全局 C++ 本地环境的唯一方式,否则全局 C++ 本地环境等价于程序启动时的 std::locale::classic() 。

C locale和C++ locales之间的联系

C locale和C++ locale是完全独立的。然而,C++ locale对象是有个名字的,通过std::locale::global()使locale对象变成全局locale会引起C locale改变,这个改变会通过调用std::setlocale()。当它发生时,在C++程序里,locale敏感的C函数会使用变化了的C locale。在一个C程序里,是没有办法改变C++ locale的。

https://zh.cppreference.com/w/cpp/locale/codecvt

https://zh.cppreference.com/w/cpp/locale/use_facet

https://zh.cppreference.com/w/cpp/locale/locale

https://blog.51cto.com/xqtyler/2058706

2.使用std::wstring_convert和std::codecvt_utf8、std::codecvt_utf16组合,(since C++11)(deprecated in C++17)

实现utf8到宽字符(utf6)的转换,因此用于转换的多(窄)字节字符串必须是utf8编码的。

https://zh.cppreference.com/w/cpp/locale/wstring_convert

https://zh.cppreference.com/w/cpp/locale/codecvt_utf8

3.操作系统API,

windows:MultiByteToWideChar和WideCharToMultiByte

MultiByteToWideChar可将utf-8编码的多字节或是ANSI编码的多字节(即两个字节)等转换为Unicode的宽字符wchar_t。例如,两个byte的窄字符表示的ANSI汉字转换为Unicode的宽字符wchar_t。

WideCharToMultiByte可以将wchar_t转换utf-8或ANSI 等编码的多字节。

MultiByteToWideChar根据接口中指定的encoding方式将source多字符转换为对应的unicode值的宽字符;WideCharToMultiByte则刚好相反,是根据指定的encoding编码方式将unicode字符转换为指定的编码方式的多字符。

函数中会明确指定多(窄)字节字符串使用的编码格式。

linux:使用C语言mbstowcs和wcstombs,在调用函数时必须指定当前多字节字符串使用的编码格式,才能转换成功。一般通过C locale指定,如使用setlocale设定C本地环境,从而通过locale明确多字节字符串的编码格式。

参考:

https://blog.csdn.net/weibo1230123/article/details/79138879

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值