您的位置:首页 > 其它

字符编码、字符存储、字符转换及工程中字符的使用

2016-05-16 15:09 281 查看
字符编码、字符存储、字符转换及工程中字符的使用

版本控制

版本
时间(北京时间)
作者
备注
V1.0
2016-05-13
施小丰
创建本文、第七章工程总结尚未完成
一、 前言

1. 目的

本文主要用于整理字符相关知识,包括字符编码、字符存储、行业标准、文件读写、工程注意事项等涉及字符相关的内容,

从而在实际工程中更好地设计和使用字符、更快地解决字符问题。

2. 适用范围

本文标题是“Windows C++字符编码、存储、转换大全”,

但“第三段.行业标准“属于概念总结,不涉及具体的编程语言和平台,是通用的知识,读者无需编程基础,但需要一定的计算机基础。

第四段到第七段仅适用于Windows C++ VC环境,需要读者具备一定的C++开发基础。

3. 开发环境及辅助工具

操作系统: Windows7 64bit中文版

IDE: VS2008+Sp1

WinHex, 下载地址http://www.winhex.com/winhex/index-m.html

Notepad++:下载地址https://notepad-plus-plus.org/

4. 声明

本文由施小丰发表于http://www.smallgui.com/,任何人以任何形式转载时,请确保本文完整,包括本节权利声明。

二、 常见问题

在使用Windows、C++、VC编程时,经常遇到以下个问题

1. 函数调用时参数字符编码不匹配

比如提示cannot convertparameter 3 from 'LPCWSTR' to 'const char *'

2. 存储或解析字符后是乱码

比如程序存储数据至文本文件后,使用Notepad打开发现是乱码。

3. 网络请求时由于字符问题导致乱码

比如发送http请求时,由于编码格式不对,对方服务器无法正确解析数据导致没有服务器正确执行预期操作

三、 字符理论

面对字符编码,我们不禁要问,怎么会产生字符编码问题,为什么不能统一字符编码?那就得先看下字符编码的历史了。

1. 标准ASCII码

计算机内的信息本质上都是二进制信息,即只有0和1两种状态,则此时字符本身,比如'A'需要用多个二进制位表示。

所以为统一各种字符的二进制值,美国国家标准委员会(American National Standard Institute,ANSI)于上个世纪60年代制定了叫做ASCII码(AmericanStandard Code for Information Interchange,美国标准信息交换码。)的字符编码。

标准ASCII码表使用8个二进制,最高位统一为0,所以实际使用7个低二进制位,从而规定了128个字符(2的7次方)的编码,见本文附件1.ASCII码表。

(这里其实来到了计算机行业一个恒久不变的坑:“够了”,比如这里的ASCII码表,比如当时的IP地址,比如比尔盖茨先生的“640K ought to be enough for anyone”,比如千年虫问题。。。)

2. 非标准ASCII码

纯英文使用ASCII码或许够了,但到非英语国家时,显然128个字符不够用,于是很多国家都充分利用起最高位来引入新的符号,

但每个国家对于扩展位并没有统一,于是出现”130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。”。

到目前为止ASCII已经不够用了,必须设计新的编码方式,但新编码方式至少需要解决六个问题

(1)0-127各国都一样,都是标准ASCII码,新编码方式需要兼容

(2)如何统一现有的所有字符

(3)如何在兼容原有字符的基础上,能在未来扩展新字符

(4)新编码是否能够不显著增加小子集字符国家的复杂度,比如对纯英语国家,128种字符够用,完全用全新的多个字节的编码方式,太浪费了。

(5)如何知道一个文件的编码格式和存储方式(大端小端),从而使用正确解析文件内容

(6)如何确保在网络传输过程中双方的编码和解码一致

3. Unicode、UCS2、UCS4

为了解决上述六个问题,有两个国际组织试图设计Unicode,分别是ISO(国际标准化组织)和Unicode.org(软件制造商协会),ISO开发ISO 10646项目,Unicode开发Unicode项目。

从1999年的Unicode3.0开始,Unicode项目和ISO10646项目的字库和字符已经一样,所以我们可以认为他们是一样的。

Unicode的目的是给符号进行编码(注意不是存储,也不是传输),目前常用的实现是UCS2,使用两个字节(16位)来表示一个字符,如汉字“施”的Unicode编码为\u65bd(可用在线工具http://tool.chinaz.com/tools/unicode.aspx转换)

,其中\u表示这是一个unicode编码(实际可忽略这个\u,仅仅为了区分而标记),其编码为十六进制的65bd。

了解了其形式,我们看看针对上面提出的六个问题,Unicode是如何解决的:

(1)标准ASCII码在Unicode编码中没变,比如字母A在ASCII中为0x41,在Unicode中的UCS2(实际上我们一般直接称UCS2为Unicode)实现中为\u0041,只是高位被用0填充了,所以第一个问题解决了

(2)Unicode为每个字符定义了一个唯一的编号,且字符对应的编码确定好以后,不再更改(至少低位不更改,高位可能由于后续扩展会统一填充),第二个问题也解决了

(3)ASCII码因为只有8个二进制位,所以不够用,而Unicode标准本身并没有规定具体多少个字节,

但真正实现Unicode则必须考虑这个问题,现在实际用的最多的是用两个字节(即16位,共有65536钟表示)表示一个符号,这种实现方式称做UCS2。UCS2实际上是Unicode的一个子集,

也有用四个字节表示一个字符的称作UCS4,所以未来如果忽然增加了很多新字符,则可以设计UCS8、UCS16等实现,且低位兼容UCS2和UCS4即可,第三个问题也解决了

(4)(5)(6)三个问题涉及到编码的具体存储和协商方式,详见第四小节4.UTF8、UTF16, 第五小节5.大端小端, 第六小节6.codepage和charset

4. UTF8、UTF16

UTF8或者UTF16的出现,本质上是解决上面6个问题中的第4个,即如果所有的字符都用相同字节的编码来表示,那么对于小字符集国家而言,这个存储太浪费空间了(可能当年的存储成本很高导致)。

这里以UTF-8为例,UTF-8是对Unicode编码格式(一般平时所说的Unicode等同于UCS2)的一种存储实现。UTF-8和UCS2的转换关系如下所示

UCS2编码(16进制) UTF8字节流(二进制) 备注

0000-007F 0xxxxxxx 其实就是标准ASCII码

0080-07FF 110xxxxx 10xxxxxx

0800-FFFF 1110xxxx 10xxxxxx 10xxxxxx

以汉字“施”为例,其unicode编码为65bd,也即Unicode二进制代码为

110010110111101

则转换成UTF8编码时,使用上述二进制代码从右往左依次填充1110xxxx 10xxxxxx 10xxxxxx中的x,左边不足部分使用0填充,得到其UTF8编码二进制值为

11100110 10010110 10111101

转换成16进制,即为E6 96 BD,我们在记事本中写入施,然后另存为UTF8,格式,再使用WinHex观察,前三个字节EF BB BF属于BOM,后面三个字节就是汉字“施”的UTF8编码,好了,新的问题又出现了,BOM是什么?



5. 大端、小端、BOM、零宽度非换行空格

汉字“施”的Unicode编码(再次提醒,其实一般所说的Unicode编码就是指UCS2编码)是65bd,占用两个字节,

于是就存在两种存储方式,

大端:65在高位的存储方式就是大端

小端:65在低位的存储方式叫做小端

为了区分当前存储或传输方式到底是大端还是小端,Unicode

BOM:字节顺序标记,FF FE

零宽度非换行空格:我们可以理解成BOM的一个组成部分

通过在windows中的Notepad++中写入字符保存为不同的格式,然后用WinHex打开可以得到不同编码的字节顺序标记(本表仅在Windows平台下测试过)

编码
BOM(十六进制)
备注
ASCII



相当于txt另存为ASCII

UTF-8

EF BB BF

相当于txt另存为utf8

UTF-8无BOM



相当于UTF-8去掉文件头BOM

小端

FF FE

相当于txt另存为Unicode

大端

FE FF

相当于txt另存为Unicode big endian

上节中提到的我们存储的汉字“施”就是使用UTF8编码,其中前面三个字节就是BOM。

但是这个BOM其实是Windows的“特产”,所以存储UTF8时,最好不要带BOM。网上这篇文章/article/4774579.html对于UTF8应不应该带BOM讲的比较清楚。结论是UTF-8尽量不要带BOM。

那么新问题又来了,假如我们在文件中只是存储字符的UTF8码,而没有BOM信息的话,我们在解析文件时,如何确定编码方式,尤其在网络上传输信息,比如html的时候,浏览器如何确定编码方式?

6. 文件编码和charset

(1)文件编码

第一种情况是纯文本文件,且没有BOM信息,这种情况下从纯软件的角度来讲其实只能猜测尝试编码方式;可以按照http://blog.csdn.net/turingo/article/details/8136644这篇博文实现。

第二种情况是纯文本文件,有BOM信息,这种情况下从通过BOM信息判断文件的编码格式。

第三种情况是文件本身已经描述了其编码方式,比如标准xml在声明中需要表明其本身的编码方式,一般形式如下:<?xml version="1.0" encoding="utf-8"?>,其中encoding表明文件本身是用utf-8编码的。

(2)网络传输

第一种情况是自定义的socket的话,需要双方自己协商确定编码格式。

另外一种是标准协议或语言,比如html。Html标记语言有一个属性charset,这个属性在html中一般是head节点的第一个子节点,这样浏览器解析html时,会先用默认的编码格式读取一部分html数据(比如GB2312),如果读取到当前html的charset后,与当前默认的编码格式不同,则浏览器使用charset中指定的编码重新读取html并显示。下面我们来验证一下。

使用Chrome浏览器打开百度首页后,右击查看网页源代码后获取的百度首页的html内容,



我们右击另存到本地计算机上后,使用Notepad++打开该文件,然后再使用Chrome打开,字符显示正常(样式会由于css样式表确实导致走样)



然后将其中的utf-8修改为GB2312



再刷新刚打开的页面,发现字符显示已经是乱码



所以对于网页或者传输而言,你要知道你传输的字符或者文件的编码格式,然后由你自己告诉对方你的编码格式,如果你告诉别人的编码方式和实际文件的编码格式不一致,那么结果很可能是两个字:乱码。

四、 常用字符编码标准

ID

标准名称

作用

备注

1

标准ASCII

特指0-127共128个字符

只适用于纯英语环境

2

Unicode

为世界上每个字符分配一个编码

只规定了编号,未规定存储和传输方式

3

UCS2

Unicode编码的一种实现

使用两个字节表示一个字符

实际是Unicode的一个子集

通常所说的Unicode就指UCS2

4

UTF8

Unicode编码存储的一种实现

字节可变

5

GB2312

又称GB2312-80

6

GBK

五、 字符定义总结

在C++体系中,字符类型本质上只有char和wchar_t两种,其他的要么只是封装,比如string封装了char数组,wstring封装了wchar_t数组,要么是为了兼容不同的语言或组件,比如CString是MFC框架下的字符串,_bstr_t是COM组件中的字符串。下表列出了常用的字符类型。

分类

ID

名称

备注

标准C++

char

单字节

wchar_t

宽字节,在string.h中定义

实际是unsigned short

需使用wcs前缀的函数处理wchar_t

wchar_t* myStr=L"测试";

string

wstring

VC

CHAR

等同于char

WCHAR

等同于wchar_t

TCHAR

根据_UNICODE宏,确定当前表示的是ANSI还是Unicode

所以应配合_T使用初始化字符串

且应使用以_tcs为前缀的函数

_T TEXT _TEXT

三个作用一样,

根据环境决定是ASCII还是Unicode

L

将字符串变成Unicode

MFC

CString

MFC字符串

COM相关

OLECHAR

不同环境下自动为

WCHAR或CHAR

BSTR

是一个有长度有前缀和null结束符的OLECHAR数组

是COM中默认的字符串格式

_bstr_t

是对BSTR的封装的类

实际是一个智能指针

为实现和LPCSTR和BSTR才有的

CComBSTR

ATL中的类

是对BSTR类型的分装

VARIANT

可变类型

_variant_t

是对VARIAN的封装,类似于_bstr_t对于BSTR的封装

COleVariant

是对VARIANT的封装

其它类型见WinNT.h头文件:

typedef WCHAR *PWCHAR,*LPWCH, *PWCH;

typedef CONST WCHAR *LPCWCH, *PCWCH;

typedef _Null_terminated_WCHAR *NWPSTR, *LPWSTR, *PWSTR;

typedef _Null_terminated_ PWSTR *PZPWSTR;

typedef _Null_terminated_ CONST PWSTR *PCZPWSTR;

typedef _Null_terminated_ WCHAR UNALIGNED *LPUWSTR, *PUWSTR;

typedef _Null_terminated_ CONST WCHAR *LPCWSTR, *PCWSTR;

typedef _Null_terminated_ PCWSTR *PZPCWSTR;

typedef _Null_terminated_ CONST PCWSTR *PCZPCWSTR;

typedef _Null_terminated_ CONST WCHAR UNALIGNED *LPCUWSTR, *PCUWSTR;

typedef _NullNull_terminated_WCHAR *PZZWSTR;

typedef _NullNull_terminated_ CONST WCHAR *PCZZWSTR;

typedef _NullNull_terminated_ WCHAR UNALIGNED *PUZZWSTR;

typedef _NullNull_terminated_ CONST WCHAR UNALIGNED *PCUZZWSTR;

typedef WCHAR *PNZWCH;

typedef CONST WCHAR *PCNZWCH;

typedef WCHAR UNALIGNED *PUNZWCH;

typedef CONST WCHAR UNALIGNED *PCUNZWCH;

typedef CONST WCHAR*LPCWCHAR, *PCWCHAR;

typedef CONST WCHAR UNALIGNED *LPCUWCHAR, *PCUWCHAR;

//

// UCS (Universal Character Set) types

//

typedef unsigned longUCSCHAR;

#ifndef _TCHAR_DEFINED

typedef WCHAR TCHAR, *PTCHAR;

typedef WCHAR TBYTE , *PTBYTE ;

#define _TCHAR_DEFINED

#endif /* !_TCHAR_DEFINED */

typedef LPWCH LPTCH, PTCH;

typedef LPCWCH LPCTCH, PCTCH;

typedef LPWSTR PTSTR, LPTSTR;

typedef LPCWSTR PCTSTR, LPCTSTR;

typedef LPUWSTR PUTSTR, LPUTSTR;

typedef LPCUWSTR PCUTSTR, LPCUTSTR;

typedef LPWSTR LP;

typedef PZZWSTR PZZTSTR;

typedef PCZZWSTR PCZZTSTR;

typedef PUZZWSTR PUZZTSTR;

typedef PCUZZWSTR PCUZZTSTR;

typedef PZPWSTR PZPTSTR;

typedef PNZWCH PNZTCH;

typedef PCNZWCH PCNZTCH;

typedef PUNZWCH PUNZTCH;

typedef PCUNZWCH PCUNZTCH;

#define __TEXT(quote) L##quote // r_winnt

看了这么多字符类型,只能说C\C++语言体系(不仅仅包括语言本身,还包括配套类库)的统一性还停留在上个实际的水平,对于这种奇奇怪怪的类型,遇到的时候去go to definition就够了。时间终将会逐渐淘汰这些历史过度的产物

六、 字符函数总结

分类

名称

备注

常用字符串操作

str开头的函数

如strcat,连接字符串

strcpy,复制字符串

strcmp,比较字符串,区分大小写

stricmp,比较字符串,不区分大小写

对char类型字符串的处理

wcs开头的函数

如wcscmp功能和strcmp功能一样,只是wcscmp用于wchar_t

对wchar_t类型字符串的处理

单字节和宽字节转换

wcstombs_s

mbstowcs_s

MultiByteToWideChar

从char转换成wchat_t

WideCharToMultiByte

从wchat_t转换成char

ConvertBSTRToString

从BSTR转换成string

ConvertStringToBSTR

从string转换成BSTR

数字和字符串之间转换

atoi

atol

atof

strtod

strtol

strtoul

sprintf

stream

stringstream

ostringstream

七、 工程总结

1.VS工程编码

VS2008

2.UTF8编码存储文件

3.xml

4. 文件读写

5.多语言

6.http

7.哈夫曼编码

八、 参考文献及博客

1. 百度百科:ASCII

http://baike.baidu.com/link?url=fpRp_IBr7z9azzwOUgwJM2cszeFGJxrS-KB_GB42i8ETkAwjkoR-JHEAHIRzdE7BZBCwqJd7QP7v9v25Xb7gJa

2. 维基百科:ISO/IEC646

https://wuu.wikipedia.org/wiki/ISO/IEC_646

3. 字符编码

http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

4. Unicode组织

http://www.unicode.org/

5. The Absolute Minimum Every Software Developer Absolutely, PositivelyMust Know About Unicode and Character Sets (No Excuses!)

http://www.joelonsoftware.com/articles/Unicode.html

6. codePage和charset

/article/4661419.html

7. 程序员趣味读物:谈谈Unicode编码

/article/9296337.html

8. 字符编码(ucs2_+unicode_+utf8_+gb2312)

http://blog.sina.com.cn/s/blog_958a07d0010136y1.html

9. UTF-8编码规则

/article/4921541.html

10. Unicode转UTF8

/article/9932275.html

11. UTF8最好不要带BOM

/article/4774579.html

12. Unicode VS Multibyte

http://blog.codingnow.com/2006/03/unicode_vs_multibyte.html

13.
Unicode(UTF-8,UTF-16)令人混淆的概念


/article/6061801.html

14.
简单几句话总结Unicode,UTF-8和UTF-16


/article/7164511.html

15. How to: Convert BetweenVarious String Types

https://msdn.microsoft.com/en-US/library/ms235631

16. C++中的wchar_t

/article/6491263.html

17. C++官网

http://www.cplusplus.com/

18. BSTR类型跟_bstr_t区别

https://www.douban.com/note/188555222/

19.
搞清楚VC++中的char,wchar_t,TCHAR


/article/8786766.html

20.
关于char,wchar_t, TCHAR, _T(),L,宏 _T、TEXT,_TEXT、L


/article/6395232.html

21. CString与LPCWSTR、LPSTR、char*、LPWSTR等类型的转换

/article/3627260.html

22.
多字符集(ANSI)和UNICODE及字符串处理方式准则


/article/9005242.html

23.
BIG5编码, GB编码(GB2312, GBK,...), Unicode编码,
UTF8,WideChar, MultiByte, Char 说明与区别


/article/1563271.html

24.
GB2312, BIG5,UTF8, Unicode之间的互换


/article/1563444.html

25. ANSI、UNICODE、UTF-8、GB2312、GBK、DBCS、UCS的区别和由来

http://blog.chinaunix.net/uid-20743151-id-326455.html

九、 附录

1.ASCII码表

Bin
Dec
Hex
缩写/字符
解释
0000 0000
0
00
NUL(null)
空字符
0000 0001
1
01
SOH(start of headline)
标题开始
0000 0010
2
02
STX (start of text)
正文开始
0000 0011
3
03
ETX (end of text)
正文结束
0000 0100
4
04
EOT (end of transmission)
传输结束
0000 0101
5
05
ENQ (enquiry)
请求
0000 0110
6
06
ACK (acknowledge)
收到通知
0000 0111
7
07
BEL (bell)
响铃
0000 1000
8
08
BS (backspace)
退格
0000 1001
9
09
HT (horizontal tab)
水平制表符
0000 1010
10
0A
LF (NL line feed, new line)
换行键
0000 1011
11
0B
VT (vertical tab)
垂直制表符
0000 1100
12
0C
FF (NP form feed, new page)
换页键
0000 1101
13
0D
CR (carriage return)
回车键
0000 1110
14
0E
SO (shift out)
不用切换
0000 1111
15
0F
SI (shift in)
启用切换
0001 0000
16
10
DLE (data link escape)
数据链路转义
0001 0001
17
11
DC1 (device control 1)
设备控制1
0001 0010
18
12
DC2 (device control 2)
设备控制2
0001 0011
19
13
DC3 (device control 3)
设备控制3
0001 0100
20
14
DC4 (device control 4)
设备控制4
0001 0101
21
15
NAK (negative acknowledge)
拒绝接收
0001 0110
22
16
SYN (synchronous idle)
同步空闲
0001 0111
23
17
ETB (end of trans. block)
传输块结束
0001 1000
24
18
CAN (cancel)
取消
0001 1001
25
19
EM (end of medium)
介质中断
0001 1010
26
1A
SUB (substitute)
替补
0001 1011
27
1B
ESC (escape)
换码(溢出)
0001 1100
28
1C
FS (file separator)
文件分割符
0001 1101
29
1D
GS (group separator)
分组符
0001 1110
30
1E
RS (record separator)
记录分离符
0001 1111
31
1F
US (unit separator)
单元分隔符
0010 0000
32
20
(space)
空格
0010 0001
33
21
!
 
0010 0010
34
22
"
 
0010 0011
35
23
#
 
0010 0100
36
24
$
 
0010 0101
37
25
%
 
0010 0110
38
26
&
 
0010 0111
39
27
'
 
0010 1000
40
28
(
 
0010 1001
41
29
)
 
0010 1010
42
2A
*
 
0010 1011
43
2B
+
 
0010 1100
44
2C
,
 
0010 1101
45
2D
-
 
0010 1110
46
2E
.
 
00101111
47
2F
/
 
00110000
48
30
0
 
00110001
49
31
1
 
00110010
50
32
2
 
00110011
51
33
3
 
00110100
52
34
4
 
00110101
53
35
5
 
00110110
54
36
6
 
00110111
55
37
7
 
00111000
56
38
8
 
00111001
57
39
9
 
00111010
58
3A
:
 
00111011
59
3B
;
 
00111100
60
3C
<
 
00111101
61
3D
=
 
00111110
62
3E
>
 
00111111
63
3F
?
 
01000000
64
40
@
 
01000001
65
41
A
 
01000010
66
42
B
 
01000011
67
43
C
 
01000100
68
44
D
 
01000101
69
45
E
 
01000110
70
46
F
 
01000111
71
47
G
 
01001000
72
48
H
 
01001001
73
49
I
 
01001010
74
4A
J
 
01001011
75
4B
K
 
01001100
76
4C
L
 
01001101
77
4D
M
 
01001110
78
4E
N
 
01001111
79
4F
O
 
01010000
80
50
P
 
01010001
81
51
Q
 
01010010
82
52
R
 
01010011
83
53
S
 
01010100
84
54
T
 
01010101
85
55
U
 
01010110
86
56
V
 
01010111
87
57
W
 
01011000
88
58
X
 
01011001
89
59
Y
 
01011010
90
5A
Z
 
01011011
91
5B
[
 
01011100
92
5C
\
 
01011101
93
5D
]
 
01011110
94
5E
^
 
01011111
95
5F
_
 
01100000
96
60
`
 
01100001
97
61
a
 
01100010
98
62
b
 
01100011
99
63
c
 
01100100
100
64
d
 
01100101
101
65
e
 
01100110
102
66
f
 
01100111
103
67
g
 
01101000
104
68
h
 
01101001
105
69
i
 
01101010
106
6A
j
 
01101011
107
6B
k
 
01101100
108
6C
l
 
01101101
109
6D
m
 
01101110
110
6E
n
 
01101111
111
6F
o
 
01110000
112
70
p
 
01110001
113
71
q
 
01110010
114
72
r
 
01110011
115
73
s
 
01110100
116
74
t
 
01110101
117
75
u
 
01110110
118
76
v
 
01110111
119
77
w
 
01111000
120
78
x
 
01111001
121
79
y
 
01111010
122
7A
z
 
01111011
123
7B
{
 
01111100
124
7C
|
 
01111101
125
7D
}
 
01111110
126
7E
~
 
01111111
127
7F
DEL (delete)
删除
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: