类型 Type |
字节数 Bytes |
代码页 Code Page |
编码 Encoding |
说明 Description |
char |
1 |
Windows: CP_ACP
Linux: CP_UTF8 |
Windows: ANSI
Linux: UTF-8 |
代码页表格里面的编码,
如果与本地 ANSI 编码不同,显示出来会乱码。 |
wchar_t |
Windows: 2
Linux: 4 |
UTF-16: 1200
UTF-32: 65005 |
Windows: UTF-16
Linux: UTF-32 |
wchar_t 在不同的系统里面的编码与字节数都不同,所以跨平台比较困难,跨平台的编码一般都使用 UTF-8 |
char16_t |
2 |
UTF-16: 1200 |
UTF-16 |
UNICODE 的一种编码方式,支持多国语言,不会乱码 |
char32_t |
4 |
UTF-32: 65005 |
UTF-32 |
UNICODE 的一种编码方式,支持多国语言,不会乱码 |
_TCHAR |
根据版本 |
UTF-16: 1200
CP_ACP |
根据项目的版本,
UTF-16 或 ANSI |
这个类型是在 C 语言头文件 <tchar.h> 里面声明的,根据项目的 UNICODE/ANSI 版本,相当于 wchar_t 或 char。C++ Builder 操作指南 → 项目的常用的重要配置 → 选择 UNICODE / ANSI |
TCHAR |
根据版本 |
UTF-16: 1200
CP_ACP |
根据项目的版本,
UTF-16 或 ANSI |
这个类型是在 WinAPI 函数里面声明的,根据项目的 UNICODE/ANSI 版本,相当于 wchar_t 或 char。C++ Builder 操作指南 → 项目的常用的重要配置 → 选择 UNICODE / ANSI |
String |
根据版本 |
UTF-16: 1200
CP_ACP |
根据项目的版本,
UTF-16 或 ANSI |
根据项目的 UNICODE/ANSI 版本,相当于 UnicodeString 或 AnsiString。C++ Builder 操作指南 → 项目的常用的重要配置 → 选择 UNICODE / ANSI |
AnsiString |
字符串长度 + 1
(1个结束符 = 1字节) |
CP_ACP |
ANSI 本地编码 |
和 char 一样,是代码页表格里面的编码,
如果与本地 ANSI 编码不同,显示出来会乱码。 |
AnsiStringT<CP> |
字符串长度 + 1
(1个结束符 = 1字节) |
<CP> |
<CP> (Code Page)
代码页指定的编码 |
如果 CP 在 0~3 范围,与本地编码不同会乱码,
不支持 1200/1201/65005/65006,
前面两条之外的其他 CP 都能够正常处理,不并且会乱码。 |
UTF8String |
字符串长度 + 1
(1个结束符 = 1字节) |
CP_UTF8 |
UTF-8 |
UNICODE 的一种编码方式,支持多国语言,不会乱码 |
RawByteString |
字符串长度 + 1
(1个结束符 = 1字节) |
不处理代码页 |
不处理编码 |
相当于 char 数组的封装,不会在代码页之间自动编码转换 |
UnicodeString |
字符串长度*2 + 2
(1个结束符 = 2字节) |
UTF-16: 1200 |
UTF-16 |
在 Windows 里面,相当于 wchar_t 的封装 |
ShortString |
256 个字节
(1个结束符 = 1字节) |
CP_ACP |
ANSI |
只能和 AnsiString 之间互相赋值,字符串长度在 0 到 255 之间,固定占用 256 个字节 |
SmallString<sz> |
sz + 1 个字节
(1个结束符 = 1字节) |
CP_ACP |
ANSI |
只能和 AnsiString 之间互相赋值,字符串长度在 0 到 sz 之间,固定占用 sz + 1 个字节 |
UCS4String |
字符串长度*4 + 4
(1个结束符 = 4字节) |
UTF-32: 65005 |
UTF-32 |
只用作编码转换,很难参与运算。
UNICODE 的一种编码方式,支持多国语言,不会乱码。 |
BSTR |
字符串长度*2 + 6
(1个前缀 = 4字节,
1个结束符
= 2字节) |
UTF-16: 1200 |
UTF-16 |
微软的 COM 接口使用的数据类型,不是标准的 C/C++ 字符串类型。有 4 个字节前缀,表示字符串长度,BSTR 指针是 wchar_t * 类型的,指向第一个字符,最后一个字符后面有一个结束符 L'\0' 占用 2 个字节,所以总字节数 = 字符串长度*2 + 4 + 2 |
WideString |
字符串长度*2 + 6
(1个前缀 = 4字节,
1个结束符
= 2字节) |
UTF-16: 1200 |
UTF-16 |
WideString 封装的 BSTR 类型,不是标准的 C/C++ 字符串类型。如果认为这是标准的 C/C++ 类型,那么使用时稍不留神就能让程序崩溃。 |
std::string |
字符串长度 + 1
(1个结束符 = 1字节) |
Windows: CP_ACP
Linux: CP_UTF8 |
Windows: ANSI
Linux: UTF-8 |
std::string 封装的 char *,编码与 char 相同 |
std::wstring |
字符串长度*2 + 2
(1个结束符 = 2字节)
字符串长度*4 + 4
(1个结束符 = 4字节) |
UTF-16: 1200
UTF-32: 65005 |
Windows: UTF-16
Linux: UTF-32 |
std::wstring 封装的 wchar_t *,编码与 wchar_t 相同 |
std::u16string |
字符串长度*2 + 2
(1个结束符 = 2字节) |
UTF-16: 1200 |
UTF-16 |
std::u16string 封装的 char16_t *,编码与 char16_t 相同 |
std::u32string |
字符串长度*4 + 4
(1个结束符 = 4字节) |
UTF-32: 65005 |
UTF-32 |
std::u32string 封装的 char32_t *,编码与 char32_t 相同 |
书写方法 |
类型 |
编码 |
备注 |
'c'
'字' |
char |
Windows: ANSI
Linux: UTF-8 |
单个字符。单引号只能表示一个字符,是一个整数值,等于这个字符的编码值。
• 在 Windows 里面,是 ANSI 本地编码的,代码页 CP_ACP (0),
• 在 Linux 里面,是 UTF-8 编码的,代码页 CP_UTF8 (65001)。
因为采用以上编码,一个字符可能是 1 到 4 个字节,由于 char 只有一个字节,如果把这样的字符赋值给 char 只会得到编码值的最低位字节,其他数据丢失,所以单个字符,通常用在单字节字符上,双字节或多字节一般都是用字符串处理。 |
L'c'
L'字' |
wchar_t |
Windows: UTF-16
Linux: UTF-32 |
单个字符。大写英文字母 L 开头的单引号,只能表示一个 wchar_t 字符,是一个整数值,等于这个字符的编码值。
• 在 Windows 里面,是 UTF-16 编码的,
• 在 Linux 里面,是 UTF-32 编码的。
在 Linux 里面没有任何问题,任何字符都能够得到正确的编码值。
在 Windows 里面,单个 char16_t 的字符,能够得到正确的编码值,2 个 char16_t 组成的字符,只能得到第一个 char16_t 的编码值,第二个 char16_t 丢失,所以多个 char16_t 的字符需要用字符串处理。 |
u'c'
u'字' |
char16_t |
UTF-16 |
单个字符。小写英文字母 u 开头的单引号,只能表示一个 char16_t 字符,2 个 char16_t 组成的字符无法编译通过,这种情况需要用字符串处理。 |
U'c'
U'字' |
char32_t |
UTF-32 |
单个字符。大写英文字母 U 开头的单引号,只能表示一个 char32_t 字符,由于 UTF-32 编码每个 char32_t 字符都是 UNICODE 编码值,所以所有的字符都能得到正确的编码值。 |
_T('c')
_TEXT('c') |
char 或
wchar_t |
ANSI 或 UTF-16 |
根据项目的 UNICODE/ANSI 版本,相当于 wchar_t 或 char。
C++ Builder 操作指南 → 项目的常用的重要配置 → 选择 UNICODE / ANSI |
"string 字符串" |
char [] |
Windows: ANSI
Linux: UTF-8 |
双引号字符串,长度仅受内存大小的限制,是这些字符的编码值构成的 char 型数组,在最后一个字符的后面,会自动添加一个作为结束符的字符,编码值为 0。
• 在 Windows 里面,是 ANSI 本地编码的,代码页 CP_ACP (0),
• 在 Linux 里面,是 UTF-8 编码的,代码页 CP_UTF8 (65001)。 |
L"string 字符串" |
wchar_t [] |
Windows: UTF-16
Linux: UTF-32 |
大写英文字母 L 开头的字符串,长度仅受内存大小限制,是这些字符的编码值构成的 wchar_t 数组,在最后一个字符的后面,会自动添加一个作为结束符的字符,编码值为 0。 |
u"string 字符串" |
char16_t [] |
UTF-16 |
小写英文字母 u 开头的字符串,长度仅受内存大小限制,是这些字符的编码值构成的 char16_t 数组,在最后一个字符的后面,会自动添加一个作为结束符的字符,编码值为 0。 |
U"string 字符串" |
char32_t [] |
UTF-32 |
大写英文字母 U 开头的字符串,长度仅受内存大小限制,是这些字符的编码值构成的 char32_t 数组,在最后一个字符的后面,会自动添加一个作为结束符的字符,编码值为 0。 |
_T("字符串")
_TEXT("字符串") |
char 或
wchar_t |
ANSI 或 UTF-16 |
根据项目的 UNICODE/ANSI 版本,相当于 wchar_t 或 char。
C++ Builder 操作指南 → 项目的常用的重要配置 → 选择 UNICODE / ANSI |
上面的例子,s 和 t 得到同样的字符串常数,反斜线 \ 在行末,作为续行符,相当于下一行的内容连接在这个位置,下一行的内容 def\ 必须从行首开始写,如果有空格,也会把空格连在字符串里面。
赋值语句 |
结果 |
说明 |
在不同地区运行是否会乱码 |
char c = 'z'; |
'z' |
GB2312/GBK 编码:'z' 是单字节的,c 得到的是字符 'z' 本身 |
不会,英文字母在所有地区的编码都是一样的 |
int i = 'z'; |
0x7A (122) |
GB2312/GBK 编码:'z' 是单字节的,在 int 的取值范围之内,i 得到的是字符 'z' 的编码,与 'z' 的 ASCII 值相等 |
不会,英文字母在所有地区的编码都是一样的 |
char c = '我'; |
0xD2 (210) |
GB2312/GBK 编码:'我' 是双字节的,编码值为 0xCED2,c 得到的是低位字节 0xD2,高位丢失,很显然这并不是期望的值。0xD2 在 GB2312/GBK 里面是不完整的编码,即所谓的 “半个汉字”。 |
会乱码,ANSI 编码到代码页不同的地区会乱码 |
unsigned short c = '我'; |
0xCED2 (52946) |
GB2312/GBK 编码,'我' 是双字节的,正好在 unsigned short 的取值范围之内,得到编码值为 0xCED2。 |
会乱码,ANSI 编码到代码页不同的地区会乱码 |
char s[] = { 'a', 'b', 'c', 0 }; |
"abc" |
GB2312/GBK 编码,'a', 'b', 'c' 都是单字节编码,他们给 char 数组对应的每个元素赋值的时候,都能得到正确的值。 |
不会,英文字母在所有地区的编码都是一样的 |
char s[] = { '你', '好', 0 }; |
"忝" |
GB2312/GBK 编码,'你' 的编码是 0xC4E3,'好' 的编码是
0xBAC3,他们分别赋值给 char 之后,都取了低位字节,即 s[0] = 0xE3 和 s[1] = 0xC3,这两个字符组成一个编码为 0xE3C3 的双字节字符,正好是汉字 '忝' 的 GB2312/GBK 编码。 |
会乱码,ANSI 编码到代码页不同的地区会乱码 |
char c = 'wxyz'; |
'z' |
由于单引号里面是多个字符,他们会组成 4 个字节的字符编码,编译器会提出警告,丢失放在编码高位的字节,c 得到最低位字节 'z'; |
不会,英文字母在所有地区的编码都是一样的 |
unsigned long i = 'wxyz'; |
0x7778797A (2004384122) |
由于单引号里面是多个字符,他们会组成 4 个字节的字符编码,'w'、'x'、'y'、'z' 的编码值分别为 0x77, 0x78, 0x79, 0x7A。 |
不会,英文字母在所有地区的编码都是一样的 |
char c = 'hello!'; |
编译错误 |
因为字符编码最多 4 个字节,超过 4 个字节无法编译通过。 |
|
| | | |
wchar_t c = L'我'; |
L'我' |
UTF-16 单个 char16_t 编码,直接得到字符。 |
不会,UNICODE 不会乱码 |
wchar_t c = '我'; |
L'我' |
ANSI 编码的 '我' 认为是 GB2312/GBK 编码的,程序运行时进行编码转换,转为 UTF-16 编码的对应的字符,得到 L'我'。由于产生了不必要的编码转换,少写了一个 L 会引起到不同代码页的地区出现乱码,很显然这不是我们预期的结果。 |
会乱码,ANSI 编码到代码页不同的地区会乱码 |
wchar_t c = L'𠀾'; |
0xD840 (55360) |
UTF-16 编码的 L'𠀾' 是 2 个 char16_t 的编码:0xD840, 0xDC3E,而在 Windows 系统里面的 L'𠀾' 只能得到第一个编码值 0xD840 |
不会,UNICODE 不会乱码 |
unsigned long i = L'𠀾'; |
0xD840 (55360) |
尽管 i 是 4 个字节的,能够放得下 2 个 char16_t 编码,但是 L'𠀾' 这个写法只能够得到第一个编码,所以还是 0xD840 |
不会,UNICODE 不会乱码 |
wchar_t s[] = L"𠀾"; |
L"𠀾" |
字符串长度仅受内存大小的限制,所以无论几个 char16_t 的 UTF-16 编码的字符,都可以正常放在字符串里面,得到正确的值。 |
不会,UNICODE 不会乱码 |
| | | |
char16_t c = u'我'; |
u'我' |
UTF-16 单个 char16_t 编码,直接得到字符。 |
不会,UNICODE 不会乱码 |
char16_t c = u'𠀾'; |
编译错误 |
小写英文字母 u 开头的单引号,只能表示一个 UTF-16 单个 char16_t 编码的字符,u'𠀾' 是 2 个 char16_t 编码的字符,所以无法编译通过 |
|
char16_t s[] = u"𠀾"; |
u"𠀾" |
字符串长度仅受内存大小的限制,所以无论几个 char16_t 的 UTF-16 编码的字符,都可以正常放在字符串里面,得到正确的值。 |
不会,UNICODE 不会乱码 |
| | | |
char32_t c = U'我'; |
U'我' |
大写引文字母 U 开头的是 UTF-32 编码,字符编码就是 UNICODE 码本身,所以始终是正确的 |
不会,UNICODE 不会乱码 |
char32_t c = U'𠀾'; |
U'𠀾' |
大写引文字母 U 开头的是 UTF-32 编码,字符编码就是 UNICODE 码本身,所以始终是正确的 |
不会,UNICODE 不会乱码 |
char32_t s[] = U"𠀾"; |
U'𠀾' |
大写引文字母 U 开头的是 UTF-32 编码,字符编码就是 UNICODE 码本身,所以始终是正确的 |
不会,UNICODE 不会乱码 |
| | | |
String s = _T("字符串") |
L"字符串" 或
"字符串" |
String: 根据项目的 UNICODE/ANSI 版本,相当于 UnicodeString 或 AnsiString。_T("字符串") 相当于 wchar_t [] 或 char []。C++ Builder 操作指南 → 项目的常用的重要配置 → 选择 UNICODE / ANSI |
UNICODE 版本不会乱码
ANSI 版本会乱码 |
AnsiString s = "字符串"; |
"字符串" |
GB2312/GBK 编码字符串 |
会乱码,ANSI 编码到代码页不同的地区会乱码 |
UnicodeString s = L"字符串"; |
L"字符串" |
UTF-16 编码字符串 |
不会,UNICODE 不会乱码 |
AnsiString s = L"字符串"; |
"字符串" |
运行时把 UTF-16 编码的字符串转为 ANSI 编码,即 GB2312/GBK。产生不必要的转码,并且不会从根本上解决乱码问题。例如到英国的电脑里面运行,只剩下英文和数字了,到日本的电脑里面会显示出来一部分日本 Shift-JIS 编码里面的汉字,而不是全部汉字。 |
虽然不会乱码,但是这个地区的代码页所使用的编码里面没有的字符会丢失 |
UnicodeString s = "字符串"; |
L"字符串" |
虽然变量是 UnicodeString 类型的,但是 "字符串" 是 ANSI 编码的,运行时会从 ANSI 编码转为 UTF-16 编码,这样在代码页不同的地区运行,就会乱码。 |
会乱码,ANSI 编码到代码页不同的地区会乱码 |
| | | |
WideString s = L"字符串"; |
L"字符串" |
定义变量时赋初始值,调用的是 WideString 的构造函数,构造出来一个新的 BSTR 并且赋值为 L"字符串" |
不会,UNICODE 不会乱码 |
WideString s;
s = L"字符串"; |
运行时程序崩溃 |
WideString 的 = 操作符,需要一个新创建的 BSTR 类型,而 L"字符串" 是标准 C/C++ 的字符串,当 WideString 把这个字符串当作 BSTR 销毁的时候,程序就会崩溃。 |
|
WideString s;
s = SysAllocString(L"字符串"); |
L"字符串" |
WideString 的 = 操作符,需要一个新创建的 BSTR 类型,用 SysAllocString 函数创建新的 BSTR 字符串 L"字符串"。 |
不会,UNICODE 不会乱码 |
WideString s;
s = WideString(L"字符串"); |
L"字符串" |
用 WideString 构造函数创建一个新的 BSTR 字符串,赋值给 s。 |
不会,UNICODE 不会乱码 |
比较项目 |
UNICODE (获胜) |
ANSI (失败) |
乱码 |
全世界统一的编码,在不同的地区运行不会乱码 |
不同的地区的编码不同,会乱码 |
编码长度 |
1 ~ 4 个字节 |
1, 2 或 4 个字节 |
计算字符个数 |
无论哪个国家的文字,一个字符就是一个字符,一个汉字、一个英文字母、和一个俄语字母……,他们都是一个字符,没有区别,处理文字简单,无论是编辑框还是数据库,对于能够容纳的字符个数都不用给用户复杂的解释 |
一个汉字是两个字符 (GBK/GB2312 是这样,如果 GB18030 是 2 或 4 个字符) 这个说法根深蒂固,一直在困扰着大家,在输入文字,或者创建数据库的时候,对于字符个数的描述都是这里能容纳的汉字个数是英文字母个数的一半,大家都在研究怎么判断一个字符是汉字还是英文,给用户的解释也很复杂 |
解码能力 |
可以从文字中间的任何一个字节开始解析 |
必须从头开始解析,从中间解析可能会乱码 |
随意提取中间一段文字 |
如果提取了中间一段,两端的不完整的字符会忽略掉,不会引起乱码 |
如果随意从中间提取一段文字,不完整字符会导致后面所有的内容都乱码 |
容错能力 |
文字中间坏掉一个字节,只能影响这个字节及后面附近的一个或几个字符,不同的编码类型会不同,但是不会导致后面所有的字符都乱码 |
可能会引起这个字符开始的后面所有的文字都乱码 |
判断中间某个字符是否为汉字 |
直接根据这个字符的编码值的范围,就知道了 |
必须从第一个字符开始解析,一直解析到这个字符,才知道这个字符的编码 |
操作系统对文字的处理 |
从 Windows 2000 开始,操作系统内核就是 UNICODE 了,ANSI 编码需要根据代码页转换到 UNICODE 来执行 |
ANSI 编码是为了兼容 Windows 9x 保留下来的,会按照默认的代码页处理,转换编码到 UNICODE 上执行,默认的代码页可以在控制面板里面修改 |
处理文字的方法和函数 |
UNICODE 版本的函数都很简单,所有国家的文字统一处理 |
• ANSI 版本的函数都很复杂,虽然使用上可能会看起来比较简单,他们内部无论在处理什么,都必须都是从第一个字节开始解析的,这样才能保证不乱码。
• 由于处理 ANSI 的函数效率特别低,系统和开发工具都会提供两套不同的函数:一套是常用的,不持处理双字节和多字节字符,但是效率很高的,另一套是不常用的,专门对付必须处理双字节和多字节字符的函数,但是效率很低。
• 例如 AnsiString.Pos() 和 AnsiString.AnsiPos(),
含有汉字的时候,用 AnsiString.Pos() 可能会找到错误的内容,但是 AnsiString.AnsiPos() 始终是正确的。 |
编程习惯及编程资料 |
• 我是老 Windows 程序猿,UNICODE 是什么?😲
• 我是新 Windows 程序猿,当然是学习前辈的经验,没听说 UNICODE 呀!😕
• UNICODE 再简单大家也都发蒙,因为书和资料都是你抄我、我抄你,老资料总也不更新,新资料也跟不上。😞
• 大家都喜欢听老程序猿乐此不疲的讲 ANSI 难题,没了难题的 UNICODE 真的太不习惯了!😏 |
• ANSI 的习惯根深蒂固,无法动摇。
• 那个年代,有经验的人都认认真真的写书,写资料,没有经验的人都在认认真真的看书,看资料。不但书和资料多,质量也很好,一直到现在都是大家抄袭的对象。
• 老程序猿总是在乐滋滋的分享着如何解决一个 ANSI 编码难题给大家听,什么一个汉字两个字符啦、半个汉字啦、识别汉字和英语字母啦、乱码啦…… |