最早的计算机只支持ASCII码,具体来说就是用1个字节(最高位为0,没有用)表示0到,总共个字符,这样就可以完全满足英语应用的要求了。
后来扩展到欧洲语系,理论上一个字节可以表示个字符,欧洲人就把剩余的个字符(最高位为1)按照自己语言(法语,德语...)的要求扩充应用了起来,好像也能满足需要。
然后又到了亚洲国家,比如中国,中文汉字有十多万,这剩余的个字符根本不够我用啊,怎么办?于是就有了两个字节的编码,如中文的GBK,GB,BIG5等,当然日语,韩语等其他亚洲国家也有自己编码方式。
这就是所谓的多字节编码(MBCS)方式,Win95/98时代只支持这种方式,那时候处理字符串非常痛苦,因为它里面有些字符是一个字节表示的,也有一些是多个字节表示的,比如字符串"你好abc",里面明明是5个字符,strlen返回长度却是7,你要正确识别字符个数,可以使用类似_mbslen的API,但是实际上该API内部会绑定当前的字符集,不然神仙也识别不了。
要统一解决上面的问题,需要有一个世界通用的统一编码格式,那就是UNICODE。
UNICODE个人感觉分广义和狭义,广义的UNICODE包括UTF8,UCS2,UCS4,而狭义的UNICODE(主要是Windows平台)就是指UCS2。
先说UCS2,Windows平台上常说的UNICODE实际上就是指UCS2,简单来说就是统一用2个字节的编码,表示实际上所有语言的常用字符。
再说UTF8,有了上面的UCS2,为甚么还有要UTF8?UCS2把任何字符全都编码成2个字节(包括我们常用的英文字符),这样极大地增加了网络传输和数据存储的开销,于是就有了UTF8。UTF8对英文字符还是1个字节存储,只对其他语言字符用多个字节存储(2-6个字节)。UTF8和UNICODE可以完全对应的相互转换,具体可以参考这里
为什么还要有UCS4?UCS2用2个字节,最多也只能表示0xFFFF+1=个字符,但是我们仅汉字就有十多万,所以UCS2/UTF8收录的也只是我们一些常用的汉字,所以我们需要UCS4,用4个字节表示一个字符,可以表示0xFFFFFFFF+1=个字符,它才是我们以后的终极解决方案。
在Windows上不同编码方式的相互转换可以通过WideCharToMultiByte和MutiByteToWideChar进行,它里面WideChar就是指UCS2,可以看到它这里把UTF8也命名成MultiByte,是不是有点误导...
下面再谈小大小端(little-endian,big-endian).
计算机是以字节为寻址单位的,这就涉及到字(2个字节),双字(4个字节)及其他多字节单位在计算机内如何排布的问题,这里无非就是2种:低字节在低地址的little-endian和高字节在低地址的big-endian.
如何区分当前系统是哪种类型的大小端?曾经看到有经验的程序员也以当前的操作系统类型来判断,实际上系统的大小端和你的CPU架构体系相关联,比如说X86是小端,PowPC是大端,ARM则是可控制(默认也是小端)。
要判断当前环境是大小端实际上很简单:boolIsLittleEndian(){inti=1;return(*(char*)i==1);}
曾经看到公司跨平台的代码没有通过大小端转换,直接通过memcpy某个表示长度的int在客户端之间传送,却没有发生问题,感觉很奇怪,最后发现原来公司的所有native客户端(Win,Mac,ios,Android,BlackBerry,Linux)全都是小端。感觉现在大端的应用主要是网络字节序,Java内部全都是大端。
上面的UCS2和UCS4因为都是用多字节表示一个字符,所以实际上都有大小端的问题,比如分别对应UCS2-LE和UCS2-BE,Windows上的UNICODE实际上是UCS2-LE,UTF8因为是字节流,所以没有大小端的问题。
下面再说一下BOM(ByteOrderMark),上面说了各种编码方式以及大小端的问题,那么我们怎么知道某个文本或者数据流是何种编码方式?
一般来说有3种方法:一种是分本显示指定,比如web里html头一般会有这么一段"content="text/html;charset=utf-8";要不就是大家默认约定,比如自定义的网络数据流内的字符串一般都会用UTF8编码;还有一种就是用BOM,通过在文件头里填入BOM规定的字节,从而区分文件是何种编码类型:
UTF-80xEF0xBB0xBFUCS2BE0xFE0xFFUCS2LE0xFF0xFEUCS4BE0xxxFE0xFFUCS4LE0xFF0xFE0xx00有兴趣的同学可以用notepad保存,试下各种效果,然后用UltraEdit的16进制方式查看验证。
上面关于各种字符编码方式的集合构成了所谓的字符集/代码页相关的概念,下面是是一些常用的代码页及其字符集:
代码页(CodePage)字符集(CharSet)Description
GB
简体中文(GB)
BIG5
繁体中文(BIG5)
UTF-16
Unicode(UCS2-LE)unicodeFFFE
Unicode(UCS2-BE)
65
UTF-7
Unicode(UTF-7)
UTF-8
Unicode(UTF-8)
UTF-32
Unicode(UCS4-LE)
UTF-32(BE)
Unicode(UCS4-BE)
最后讨论下C++编程中常见的关于字符编码方式相关的问题。
在C++编程中,我们常打交道的无非是编辑器和编译器,对编辑器起来说,我们常遇到就是乱码问题,比如中文注释显示或是保存不了等,解决办法就是把你的文件保存成Unicode(UTF8)。
对于编译器来说,编码方式取决于它对C++标准的支持程度,比如C++11以前,字符串我们只能指定成2种:一种是MBCS,如char*p="abc哈哈";还有一种是UCS2,比如wchar_t*p=L"abc哈哈",这样编译器就知道你要表示的字符串类型。C++11之后,标准增加了UTF8和UCS4的支持,比如char*p=u8"abc哈哈"表示UTF8,wchar_t*p=u"abc哈哈"表示UCS2(实际上和L"xxxx"一样),char32_t*p=U"abc哈哈"表示UCS4。这里要区分编译期和运行期,尽管C++11之前在编译期我们没法告诉编译器我们这个常量串是UTF8格式的,但是程序运行期我们还是可以使用所有的编码式(MBCS/UTF8/UCS2/UCS4),因为这些最终在内存里都是二进制流。
另外C++11还增加了UTF8,UCS2,UCS4相互转码的支持:
std::codecvt_utf8封装了UTF8相关的编码转换std::codecvt_utf16封装了UCS2相关的编码转换std::codecvt_utf8_utf16封装了UTF8与UCS2的编码转换对于C++跨平台开发,我们经常遇到的就是默认用那种编码方式的问题,我们会发现Windows的UCS2解决方案对其他平台来说是个异类,一般来说有2种解决方法:
一种是统一用UTF8,但是这样对Windows来说有点麻烦,因为Windows的API都是UCS2的,所以这种方式意味着任何字符串在传给WindowsAPI之前都要从UTF8转成UCS2;还有一种就是用#define宏了,Windows上将字符串相关宏全都定义成UCS2,其他平台则全都定义成UTF8,该方式要求就你在写代码时头脑要比较清醒,因为同样的代码在不同平台上的编码格式是不一样的。
小腿白癜风泡洗药方北京哪治白癜风最好