C++ Builder 的字符串类型、字符类型、字符编码
C++ Builder 参考手册 ➙ C++ Builder 的字符串类型、字符类型、字符编码
- 字符变量
- 字符常数
- 字符串常数
- 标准 C / C++ 字符串变量类型
- Windows API 字符串变量类型
- C++ Builder 字符串变量类型
- UTF-8 / UTF-16 / UTF-32 / ANSI / GBK / BIG5 等编码转换
一. 字符变量
变量类型 | 说明 |
---|---|
char | 一个字节的字符变量类型,有符号或无符号 8 位整数【注1】, UTF-8 或 ANSI / ASCII 编码 【注2】 |
wchar_t | 宽字符变量类型,2 或 4 个字节,UTF-16 或 UTF-32 编码, 操作系统 API 函数的宽字符类型【注3】 |
char16_t | 2 个字节的字符变量类型,UTF-16 编码 |
char32_t | 4 个字节的字符变量类型,UTF-32 编码 |
_TCHAR | C 语言头文件 <tchar.h> 里面的变量类型, 项目设置里面的 _TCHAR maps to 选项 可以设此类型为 wchar_t 或 char |
TCHAR | Windows API 里面的字符变量类型,与 _TCHAR 类型相同 |
【注1】char 类型在不同的平台里面,可能是有符号整数 (x86 / x64),也可能是无符号整数 (ARM / PowerPC)。大多数编译器里面的 char 都是 8 位的整数,虽然 C / C++ 标准里面没有规定 char 的位数,但是说明了 char 必须支持 UTF-8 编码 (C++ 14),那么 char 就应该是 8 位整数。
【注2】char 类型的字符编码,可能是 UTF-8 类型的 (Linux 默认编码 / Windows 10 1903 之后的版本在控制面板里面设定 UTF-8 编码,如下面的截图所示),也可能是 ANSI 编码 (Windows 到目前为止的所有的版本的默认的编码)。
【注3】wchar_t 类型在不同的平台里面,可能是 2 个字节的 UTF-16 编码的字符类型 (Windows),也可能是 4 个字节的 UTF-32 编码的字符类型 (Linux)。
Windows 10 1903 之后版本的控制面板里面的 UTF-8 编码选项,打勾之后,"字符串"、char、std::string 和 AnsiString 都变成了 UTF-8 编码:
Windows 10 的字符编码改为 UTF-8 的选项二. 字符常数
字符常数 | 说明 |
---|---|
'c' | 单引号里面只能有一个字符【注4】,是一个字符的常数, 这个常数的值是一个整数,int 或 unsigned int 类型【注5】, 等于这个字符的编码值,UTF-8 或 ANSI【注6】 |
L'c' | 前缀为大写英文字母 L 的单引号里面只能有一个字符, 是一个字符的常数,wchar_t 类型的, 数值等于这个字符的编码值,UTF-16 或 UTF-32【注7】 |
u'c' | 前缀为小写英文字母 u 的单引号里面只能有一个字符, 是一个字符的常数,char16_t 类型的, 数值等于这个字符的编码值,UTF-16编码【注8】 |
U'c' | 前缀为大写英文字母 U 的单引号里面只能有一个字符, 是一个字符的常数,char32_t 类型的, 数值等于这个字符的编码值,UTF-32 编码 |
_T('c') | C 语言头文件 <tchar.h> 里面的变量类型, 项目设置里面的 _TCHAR maps to 选项 设为 wchar_t 或 char 相当于 L'c' 或 'c' |
_TEXT('c') | 与 _T('c') 相同 |
TEXT('c') | Windows API 里面的字符常数,与 _T('c') 相同 |
【注4】'c' 单引号里面只能有一个字符,不限于编码为 1 个字节的字符 (英文字母与数字等),也可以有超过一个字节的编码的字符,比如汉字等,例如 '汉' 和 '字' 都可以,单引号的字符并不是 char 类型的,而是 int 或 unsigned 类型的,如果给 char 赋值,高位字节丢失,只剩下最低位的一个字节的值,这种情况,编译器可能会给出警告。
【注5】'c' 或 '汉' 这样的字符常数,是 int 或 unsigned int 类型的,对于 C++ Builder,如果使用 clang 编译器,是 int 类型的,如果使用 Borland 编译器,是 unsigned int 类型的,其他 C/C++ 开发工具没有测试。clang 编译器超过 1 个字节的编码的字符常数会有警告,因为通常这样的字符要给 char 赋值,会丢失高位字节。
【注6】'c' 或 '汉' 这样的字符常数的字符编码,可能是 UTF-8 类型的 (Linux 默认编码 / Windows 10 1903 之后的版本在控制面板里面设定 UTF-8 编码,如本文前面 char 类型的备注的截图所示的参数位置),也可能是 ANSI 编码 (Windows 到目前为止的所有的版本的默认的编码)。
【注7】L'c' 或 L'汉' 这样的字符常数的字符编码和 wchar_t 类型相同,可能是 UTF-32 编码 (Linux),也可能是 UTF-16 编码 (Windows)。如果是 UTF-16 编码,存在 2 个 char16_t 字符的编码 (4 个字节的编码),如果使用的是 Borland 编译器,丢失第二个 char16_t,只剩下第一个 char16_t。例如 U+1F642 的 Emoji 字符 L'🙂' 的 UTF-16 编码为 0xD83D, 0xDE42 两个 char16_t 字符,Borland 编译器这个字符的编码值只剩下了 0xD83D。如果使用 clang 编译器,2 个 char16_t 的编码的字符无法编译通过,即 L'汉' 可以得到正确的编码值,L'🙂' 就无法编译通过了,这样的字符需要用字符串处理。
【注8】u'c' 或 u'汉' 这样的字符常数为 UTF-16 编码的,如果这个字符是 2 个 char16_t 编码的,例如 U+1F642 的 Emoji 字符 L'🙂' 的 UTF-16 编码为 0xD83D, 0xDE42 两个 char16_t 字符,就无法编译通过了,这样的字符需要用字符串处理。
通过以上注释,字符常数的总结:
- UTF-8 或 ANSI 超过 1 个字节的编码要用字符串处理,单个字符的字符常数的值超过了 1 个字节对于不同的编译器的表现不同,可能无法正确处理;
- UTF-16 编码的字符如果是由 2 个 char16_t 组成的,不同的编译器的表现不同,并且都无法正确处理,所以这样的字符需要用字符串处理;
- UTF-32 编码的字符永远都是正确的,他们的编码值就等于 UNICODE 编码值。
三. 字符串常数
字符串常数 | 说明 |
---|---|
"字符串" | UTF-8 或 ANSI 编码的字符串【注9】 |
L"字符串" | 前缀为大写英文字母 L 的字符串, UTF-16 或 UTF-32 编码【注10】 |
u"字符串" | 前缀为小写英文字母 u 的字符串,UTF-16编码 |
U"字符串" | 前缀为大写英文字母 U 的字符串,UTF-32 编码 |
_T("字符串") | C 语言头文件 <tchar.h> 里面的变量类型, 项目设置里面的 _TCHAR maps to 选项 设为 wchar_t 或 char 相当于 L"字符串" 或 "字符串" |
_TEXT("字符串") | 与 _T("字符串") 相同 |
TEXT("字符串") | Windows API 里面的字符常数,与 _T("字符串") 相同 |
【注9】"字符串" 这样的字符串常数的字符编码,可能是 UTF-8 类型的 (Linux 默认编码 / Windows 10 1903 之后的版本在控制面板里面设定 UTF-8 编码,如本文前面 char 类型的备注的截图所示的参数位置),也可能是 ANSI 编码 (Windows 到目前为止的所有的版本的默认的编码)。
【注10】L"字符串" 这样的字符串常数的字符编码,可能是 2 个字节的 UTF-16 编码的字符类型 (Windows),也可能是 4 个字节的 UTF-32 编码的字符类型 (Linux)。
四. 标准 C / C++ 字符串变量类型
变量类型 | 说明 |
---|---|
char * | 字符指针,可以用做字符串变量,UTF-8 或 ANSI 编码【注11】 |
wchar_t * | 宽字符指针,UTF-16 或 UTF-32 编码【注12】 |
char16_t * | UTF-16 字符指针 |
char32_t * | UTF-32 字符指针 |
_TCHAR * | _TCHAR 字符指针,请参考 _TCHAR 字符变量类型, 在项目设置里面的 _TCHAR maps to 选项 可以设 _TCHAR 类型为 wchar_t 或 char |
TCHAR * | Windows API 里面的类型,同 _TCHAR * |
char[] | 字符数组,可以用做字符串变量,UTF-8 或 ANSI 编码【注11】 |
wchar_t[] | 宽字符数组,UTF-16 或 UTF-32 编码【注12】 |
char16_t[] | UTF-16 字符数组 |
char32_t[] | UTF-32 字符数组 |
_TCHAR[] | _TCHAR 字符数组,请参考 _TCHAR 字符变量类型, 在项目设置里面的 _TCHAR maps to 选项 可以设 _TCHAR 类型为 wchar_t 或 char |
TCHAR[] | Windows API 里面的类型,同 _TCHAR[] |
std::string | STL 里面的字符串,UTF-8 或 ANSI 编码【注11】 |
std::wstring | STL 里面的字符串,UTF16 或 UTF32 编码【注12】 |
std::u16string | STL 里面的字符串,UTF-16 编码 |
std::u32string | STL 里面的字符串,UTF-32 编码 |
【注11】char * / char [] / std::string 这些的字符串的字符编码,可能是 UTF-8 类型的 (Linux 默认编码 / Windows 10 1903 之后的版本在控制面板里面设定 UTF-8 编码,如本文前面 char 类型的备注的截图所示的参数位置),也可能是 ANSI 编码 (Windows 到目前为止的所有的版本的默认的编码)。
【注12】wchar_t * / wchar_t [] / std::wstring 这些字符串的字符编码,可能是 UTF-16 编码的字符串 (Windows),也可能是 UTF-32 编码的字符串 (Linux)。
五. Windows API 字符串变量类型
API 类型 | C 语言类型 |
---|---|
CHAR | char |
PCHAR | char * |
PSTR | char * |
LPSTR | char * |
PCSTR | const char * |
LPCSTR | const char * |
WCHAR | wchar_t |
PWCHAR | wchar_t * |
PWSTR | wchar_t * |
LPWSTR | wchar_t * |
PCWSTR | const wchar_t * |
LPCWSTR | const wchar_t * |
PTSTR | _TCHAR * |
LPTSTR | _TCHAR * |
PCTSTR | const _TCHAR * |
LPCTSTR | const _TCHAR * |
BSTR | 虽然看上去是 wchar_t *,但不是 C / C++ 的字符串类型, 而是微软的 COM 的字符串类型, 前 4 个字节是长度,接下来是字符串内容,然后是结束符, 指针指向第一个字符,而不是内存首地址, 所以从指针指向的内容来看像是 C 语言的字符串。 |
六. C++ Builder 字符串变量类型
变量类型 | 说明 |
---|---|
UnicodeString | UTF-16 编码的字符串, C++ Builder 最常用的字符串类型 |
UTF8String | UTF-8 编码的字符串 |
AnsiString | ANSI 编码的字符串,代码页为 0 的字符串【注11】, typedef AnsiStringT<0> AnsiString; |
AnsiStringT<CP> | 代码页为 CP 的字符串,例如: AnsiStringT<936> 为 GBK 编码的字符串, AnsiStringT<950> 为 BIG5 编码的字符串,AnsiStringT<65001> 为 UTF-8 编码的字符串 |
String | UNICODE 版本为 UnicodeString; ANSI 版本为 AnsiString |
RawByteString | 相当于 char * 类型的封装, 不处理字符编码,不进行编码转换 |
ShortString | 只能和 AnsiString 之间互相赋值,字符串长度在 0 到 255 之间,固定占用 256 个字节 |
SmallString<sz> | 只能和 AnsiString 之间互相赋值,字符串长度在 0 到 sz 之间,固定占用 sz + 1 个字节 |
UCS4String | UTF-32 / UCS4 编码, 只用作编码转换,不能参与字符串运算 |
WideString | BSTR 类型的封装,微软的 COM 字符串类型 |
七. UTF-8 / UTF-16 / UTF-32 / ANSI / GBK / BIG5 等编码转换
1. UTF-8 / UTF-16 / ANSI / GBK / BIG5 等编码转换
UnicodeString、UTF8String、AnsiString、AnsiStringT<CP> 这些字符串之间互相赋值可以自动转码。
void __fastcall TForm1::Button1Click(TObject *Sender)
{
UnicodeString u16s = L"你好,玄坴!";
UTF8String u8s = u16s;
AnsiStringT<936> gbk = u8s;
AnsiStringT<950> big5 = u16s;
AnsiString as = big5;
Memo1->Lines->Add(u16s);
Memo1->Lines->Add(u8s );
Memo1->Lines->Add(gbk );
Memo1->Lines->Add(big5);
Memo1->Lines->Add(as );
wchar_t *lpU16 = u16s.c_str(); // UTF-16
char *lpUTF8 = u8s.c_str(); // UTF-8
char *lpGBK = gbk.c_str(); // GBK
char *lpBIG5 = big5.c_str(); // BIG5
char *lpANSI = as.c_str(); // ANSI
Memo1->Lines->Add(L"---");
Memo1->Lines->Add(lpU16 );
Memo1->Lines->Add(lpUTF8);
Memo1->Lines->Add(lpGBK );
Memo1->Lines->Add(lpBIG5);
Memo1->Lines->Add(lpANSI);
UnicodeString sU16 = lpU16 ; // UTF-16
UTF8String sU8 = lpUTF8; // UTF-8
AnsiStringT<936> s936 = lpGBK ; // GBK
AnsiStringT<950> s950 = lpBIG5; // BIG5
AnsiString sANSI = lpANSI; // ANSI
Memo1->Lines->Add(L"---");
Memo1->Lines->Add(sU16 );
Memo1->Lines->Add(sU8 );
Memo1->Lines->Add(s936 );
Memo1->Lines->Add(s950 );
Memo1->Lines->Add(sANSI );
}
在控制面板里面选择了 UTF-8 编码,编译运行:
- 由于 UnicodeString、UTF8String、AnsiString、AnsiStringT<CP> 这些字符串会自动转码,所以这样的字符串显示出来都不乱码;
- char * 字符串只有和控制面板的编码相同时不会乱码,编码不同会乱码;
- 把 char * 放回对应编码的字符串类型里面,就不乱码了,因为他们会自动转码。
在控制面板里面选择了中文(简体,中国),编译运行:
- 由于 UnicodeString、UTF8String、AnsiString、AnsiStringT<CP> 这些字符串会自动转码,所以这样的字符串显示出来都不乱码;
- char * 字符串只有和控制面板的编码相同时不会乱码,编码不同会乱码;
- 把 char * 放回对应编码的字符串类型里面,就不乱码了,因为他们会自动转码。
2. UTF-32 与其他编码之间转换
由于 Windows 核心都是 UTF-16 编码的,没有处理 UTF-32 编码的能力,如果有 UTF-32 编码的数据需要转成 UTF-16 处理。
由于 UTF-32 编码和 UCS4 编码相同,可以用这两个函数来进行编码转换:
UCS4String __fastcall UnicodeStringToUCS4String(const UnicodeString S);
UnicodeString __fastcall UCS4StringToUnicodeString(const UCS4String S);
UCS4String 字符串也没有处理字符串的能力,只是 UTF-32 字符的动态数组,只用来编码转换,这个字符串类型是这样定义的:
typedef DynamicArray<UCS4Char> UCS4String;
相关:
C++ Builder 参考手册 ➙ C++ Builder 的字符串类型、字符类型、字符编码