主页C++ Builder 资料C++ Builder 编程技巧字符串及文字处理字符类型及字符编码
C++ Builder 串口控件
C++ Builder 编程技巧
字符串及文字处理
 • 字符类型及字符编码
 • 各种 ANSI 编码
 • 各种 UNICODE 编码
 • 字符编码之间转换
 • 字符串处理
 • BCB6 程序升级到新版
多媒体处理
图片处理
文件处理
界面处理
C++ Builder 操作指南
C++ Builder 参考手册
网友留言/技术支持
字符类型及字符编码 - 字符串及文字处理
 • 字符和字符串类型及字符编码
 • 宽字符是什么?我怎样确定我的项目使用的是宽字符
 • 字符常数和字符串常数
   字符串常数的续行
   字符串常数的一些例子,包括一些常见的问题
 • UNICODE 和 ANSI 比较,哪个更好?

字符和字符串类型及字符编码

类型 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 相同

 

宽字符是什么?我怎样确定我的项目使用的是宽字符

 • “宽字符” 是指 wchar_t 类型的,是相对 char 类型说的,在 Windows API 里面,WCHAR 对应着 wchar_t 类型。
 • wchar_t 是操作系统内核采用的字符编码,不同的系统,字节数和编码也不同。
 • “宽字符版本” 是指 UNICODE 版本,用来区别 ANSI 版本的程序或软件。
   因为不同的系统或开发工具,都会提供两相同或相似的函数,分别处理 wchar_t 和 char 类型的文字。
 • 我的程序选择宽字符 UNICODE 版本,还是 ANSI 版本,请看这里:选择项目的 UNICODE / ANSI 版本

 

字符常数和字符串常数

书写方法 类型 编码 备注
'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

 

字符串常数的续行

如果一个字符串很长,可以分几行来写,这就涉及到续行问题。

C/C++ 的两个字符串常数之间如果只有空格和换行,没有其他符号,这两个字符串是连接在一起的,例如:

wchar_t s[] = L"abc" L"def"
              L"ghi"
              L"jklmn";

得到的字符串 s 是 L"abcdefghijklmn",两个字符串常数之间没有除了空格和换行之外的符,会连接在一起,一直到分号结束。
如果要在字符串中间换行,需要加 "\r\n"。
第二个例子:

_TCHAR s[] = _T("1st line\r\n")
             _T("2nd line\r\n")
             _T("3rd line\r\n");
_TCHAR t[] = _T("1st line\r\n2nd line\r\n3rd line\r\n");

上面程序的 s 和 t 是完全一样的效果,两个字符串常数之间没有除了空格和换行之外的符号,会连接在一起,需要换行必须要有 "\r\n"。

第三个例子:使用续行符。
一般在宏定义的时候必须使用续行符,而字符串常数不是必须使用续行符的,下面是一个使用续行符的例子:

  _TCHAR s[] = _T("abc\
def\
ghijk");
  _TCHAR t[] = _T("abcdefghijk");

上面的例子,s 和 t 得到同样的字符串常数,反斜线 \ 在行末,作为续行符,相当于下一行的内容连接在这个位置,下一行的内容 def\ 必须从行首开始写,如果有空格,也会把空格连在字符串里面。

字符串常数的一些例子,包括一些常见的问题:以下代码都是在中国大陆的简体中文 Windows 操作系统里面
 • ANSI 本地编码 (Code Page: 0) 对应的代码页为 936 (Code Page: 936),即 GBK/GB2312 编码。
 • 正确的代码写法为绿色,有问题的代码为黄色,错误的代码为红色

赋值语句 结果 说明 在不同地区运行是否会乱码
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 不会乱码

ANSI 和 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 编码难题给大家听,什么一个汉字两个字符啦、半个汉字啦、识别汉字和英语字母啦、乱码啦……
下一页:各种 ANSI 编码

C++ 爱好者 -- Victor Chen 的个人网站 www.cppfans.com 辽ICP备11016859号