字符编码探究小记

Posted By Zyp on 2019-09-24
随笔

在写C++项目的过程中,发现自己对string、wstring、CString等字符相关的东西很模糊,所以抽空认真查了些材料,这里做一个小记。

字符编码基础

首先说说最基础的字符编码,为了统一在计算机上字符的表示,ASCII字符集最先出现,它使用7位来表示一个字符,而我们一般用字节(8位)作为基本单位,所以第一位总是0,后来IBM进行了扩展,用8位来表示一个字符,当第一位是0时仍表示之前的常用字符,是1时表示其他补充的字符。

一个字节用于表示英文及标点足够,但是对其他一些文字就不够了。并且出现了各种字符集,它们之间进行数据交换也会出现问题,于是Unicode就出现了。

Unicode第一个版本用2个字节表示所有字符,这样说容易产生歧义,其实Unicode涉及到两个步骤,首先是定义一个规范,给所有的字符指定一个唯一对应的数字。第二步才是怎么把字符对应的数字保存在计算机中,这才涉及到实际在计算机中占多少字节空间。Unicode是用0至65535之间的数字来表示所有字符,其中0至127这128个数字表示的字符仍然跟ASCII完全一样,这是第一步,第二步就是怎么把0至65535这些数字转化成01串保存到计算机中,这肯定就有不同的保存方式了,于是出现了UTF(unicode transformation format),有UTF-8、UTF-16。

UTF-16比较好理解,就是任何字符对应的数字都用两个字节来保存,但是很显然如果都是英文字母这做有点浪费。UTF-16不需要用啥字符来做标志,所以两字节也就是2的16次能表示65536个字符。

于是又有个UTF-8,这里的8非常容易误导人理解为一个字节表示一个字符。当用UTF-8时表示一个字符是可变的,有可能是用一个字节表示一个字符,也可能是两个、三个。当然最多不能超过3个字节了,反正是根据字符对应的数字大小来确定。

用UTF-8有点复杂,因为程序是把一个字节一个字节的来读取,然后再根据字节中开头的bit标志来识别是该把1个还是两个或三个字节做为一个单元来处理:

  • 0xxxxxxx,表示把一个字节做为一个单元,就跟ASCII完全一样。

  • 110xxxxx 10xxxxxx,如果是这样的格式,则把两个字节当一个单元。

  • 1110xxxx 10xxxxxx 10xxxxxx,如果是这种格式则是三个字节当一个单元。

UTF-8由于里面有额外的标志信息,所有一个字节只能表示2的7次方128个字符,两个字节只能表示2的11次方2048个字符,而三个字节能表示2的16次方,65536个字符。

1
2
用UTF-8表示"汉",由于"汉"的编码27721大于2048了所有两个字节还不够,只能用三个字节来表示。
1110xxxx 10xxxxxx 10xxxxxx这种格式,把27721对应的二进制填充XXX符号,填充方式可以不一样,Big-Endian就是从左到右填充,Little-Endian是从右到左。

可能会问当用程序打开一个文件时我们怎么知道那是用的UTF-8还是UTF-16?自然会有点啥标志,在文件的开头几个字节就是标志:

1
2
EF BB BF 表示UTF-8
FE FF 表示UTF-16

从1996年开始又来了Unicode版本2,用四个字节表示所有字符,这样就出现了UTF-8、UTF16、UTF-32。原理和之前是完全一样的,UTF-32就是把所有的字符都用32bit也就是4个字节来表示,UTF-8可以选择1至8个字节中的任一个来表示,UTF-16只能是选两字节或四字节。

1
2
3
4
5
EF BB BF    UTF-8
FE FF     UTF-16/UCS-2, little endian
FF FE     UTF-16/UCS-2, big endian
FF FE 00 00  UTF-32/UCS-4, little endian.
00 00 FE FF  UTF-32/UCS-4, big-endian.

一些开发库说明

STL(standard template library标准模板库):c++程序库的一部分,其中包含了一些工具方便使用,如容器和迭代器。

MFC(Microsoft Foundation Classes):微软的应用程序框架,利用c++的语法对windows系统api进行的面向对象的封装,主要用于开发软件程序界面。

ATL(ActiveX Template Library):也是一个c++模板库,主要用于开发COM,ActiveX组件。

宽窄字符

wstring是宽char,Unicode编码,所以一般情况下一个字符占两个字节大小

string是窄char,AscII编码,所以一个字符占一个字节大小

而CString是对string(字符串)和wstring(宽字符串)的一个封装,常用在mfc中,用来解决编码问题。

要把std::string或者std::wstring类型的数据存放到CString中,直接调用string::c_str()或者wstring::c_str()就行了。

CString::GetBuffer(0)就能转换到string或者wstring。如果编译时候定义了UNICODE,则转换到wstring,这时使用CT2A函数可以转为stirng;如果未定义,则转换到string。

一些小例子

窄字符编码中文
宽字符编码中文

在VS工程属性里无论你选择Multi-Byte Character Set 或 Unicode Character Set字符集,char str[] = “中”;这个表达式里,”中”都是ANSI编码,编码值都是[0xD6,0xD0]。即默认情况下,如果不加_T或L,默认情况下所有的字符都是ANSI编码。