C++字符串之区分UTF-16 、UTF-8、std::string 、std::wstring

3,125 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 14天,点击查看活动详情

一、UTF-8 和 UTF-16

UTF-8和UTF-16是Unicode编码的两种不同实现方式。

  • UTF-8:是一种变长字符编码方式,以8位为编码单元,用1到4个字节编码所有的Unicode字符,对于英文字符,只需要一个字节,而对于汉字等其他字符,则需要2-4个字节。由于其变长性质,UTF-8能够很好地支持国际化和多语言文字处理。
  • UTF-16:是一种定长字符编码方式,以16位为编码单元,用2个字节编码大部分Unicode字符,而用4个字节编码一些较偏僻的字符。由于其定长性质,UTF-16 适合处理基于字符的算法,如搜索、替换等操作。

二、Unicode编码方式

Unicode 是一种字符编码方案,它用于把文本字符映射到数字编码。

2.1 浅析映射

以一个英文字符串 "Hello, World!"为例,我们要将这个字符串转换成 Unicode 编码。我们可以使用 Unicode 的一个子集,也就是 ASCII 编码。ASCII 编码只包含 128 个字符,其中包含了大部分常用的字符,比如字母、数字、标点符号等等。在 ASCII 编码中,每个字符都有一个数字表示,这个数字在 0 到 127 之间。

那么对于字符串 "Hello, World!" 来说,它就可以用下面的 ASCII 编码来表示:

72 101 108 108 111 44 32 87 111 114 108 100 33

这里的数字就是每个字符在 ASCII 编码中对应的数字。比如字符 H 在 ASCII 编码中的数字就是 72,字符 e 在 ASCII 编码中的数字就是 101,以此类推。

当我们使用 Unicode 编码时,我们需要为每个字符都分配一个唯一的数字。Unicode 编码用的是十六进制数,表示为 4 位或 6 位的数字。比如字符 L 的 ASCII 编码 108,对应的十六进制的 Unicode 编码就是 006C。

image.png

因此,我们可以将字符串 "Hello, World!" 转换成 Unicode 编码,得到下面的结果:

0048 0065 006C 006C 006F 002C 0020 0057 006F 0072 006C 0064 0021

附:ASCII编码表

十进制字符十进制字符十进制字符十进制字符
0NUL32(64@96`
1SOH33)65A97a
2STX34*66B98b
3ETX35+67C99c
4EOT36,68D100d
5ENQ37-69E101e
6ACK38.70F102f
7BEL39/71G103g
8BS40072H104h
9HT41173I105i
10LF42274J106j
11VT43375K107k
12FF44476L108l
13CR45577M109m
14SO46678N110n
15SI47779O111o
16DLE48880P112p
17DC149981Q113q
18DC250:82R114r
19DC351;83S115s
20DC452<84T116t
21NAK53=85U117u

Unicode定义了几种编码方式,常用的有以下几种:

2.2 常见编码方式

1. UTF-8

UTF-8 是一种变长编码方式,它使用 1-4 个字节表示一个字符。ASCII 码使用 1 个字节,欧洲的其他字符使用 2-3 个字节,东亚字符使用 3 个字节。UTF-8 是目前最流行的 Unicode 编码方式之一,它适用于多种计算机操作系统和应用程序。

2. UTF-16

UTF-16 是一种定长编码方式,它使用 2 个字节或 4 个字节表示一个字符。UTF-16 主要用于 Windows 平台和 Java 程序中。UTF-16 可以表示 BMP(基本多文种平面)和非 BMP 字符。

3. UTF-32

UTF-32 是一种定长编码方式,它使用 4 个字节表示一个字符。UTF-32 可以表示 Unicode 中的任何字符,但由于它的字符编码比较大,因此它的存储空间相对较大。

4. UCS-2

UCS-2 是一种定长编码方式,它使用 2 个字节表示一个字符。它只能表示 BMP 中的字符,不能表示非 BMP 字符。

5. UCS-4

UCS-4 是一种定长编码方式,它使用 4 个字节表示一个字符。它可以表示 Unicode 中的任何字符,但由于它的字符编码比较大,因此它的存储空间相对较大。

总之,Unicode 编码方式主要区别在于它们的编码方式、字符集范围和存储空间。选择使用哪种编码方式应该根据实际需求来确定。

三、std::string 和 std::wstring

std::string 和 std::wstring 则是 C++ 标准库提供的两种不同的字符串类型:

  • std::string:是由 char 类型构成的字符串类型,使用单字节字符集(如ASCII、GBK等)表示字符。在使用std::string 时需要注意字符集的兼容性,如在使用中文时可能需要转换字符集。
  • std::wstring:是由wchar_t类型构成的字符串类型,使用双字节字符集(如UTF-16)表示字符。在处理多语言和国际化方面具有很好的兼容性,但是也会带来内存占用和处理效率方面的一些问题。对于C++11及以上的版本,建议使用 Unicode 字符串类型 std::wstring 和 std::wstringstream 等进行中文字符串的处理。其中 std::wstring 具体是何种 Unicode 类型,需要看你使用的操作系统和编译器,如果你使用的是 Windows 操作系统,且编译器采用的是 Visual C++ 编译器,那么 std::wstring 字符串类型中的字符编码就是 UTF-16。如果你使用的是 Linux 或者 macOS 等操作系统,那么 std::wstring 字符串类型中的字符编码就是 UTF-32。

四、数据类型转换分析

举个例子,在Windows 操作系统,Visual C++ 编译器下,需要 将 SUStringRef 转 std::wstring 我们需要做什么呢?

  • 首先明确 SketchUp API 中,SUStringRef 是指向 UTF-8 字符串的指针
  • 在Windows 操作系统,Visual C++ 编译器下,std::wstring字符串类型中的字符编码就是 UTF-16
  • 所以需要先将 SUStringRef 转换为 UTF-16 字符串
  • 再将 UTF-16 字符串转换为 std::wstring
std::wstring SUStringRefToStdWstring(SUStringRef str) {
  size_t len = 0;
  SUStringGetUTF16Length(str, &len);
  std::vector<wchar_t> buffer(len);
  SUStringGetUTF16(str, len, &buffer[0], &len);
  return std::wstring(buffer.begin(), buffer.end());
}

这里调用 SUStringGetUTF16Length 函数获取 SUStrignRef 类型字符串的长度,这里将长度存储在变量 len 中。创建一个长度为 lenwchar_t 类型的vector buffer用于存储转换后的 UTF-16 字符串。使用了 SUStringGetUTF16 函数将 SUStringRef 转换为 UTF-16 字符串,并使用了 std::vector 作为缓冲区来存储 UTF-16 字符串的内容。最后,使用 std::wstring 的构造函数将 UTF-16 字符串转换为 std::wstring

4.1 补充:wchar_t 和 char

上面提到的 wchar_t 和 char 都是 C++ 中的字符类型,但是它们有一些区别。

char 类型是一个字节,常用于表示 ASCII 字符集中的字符,范围是 0 到 127 。

wchar_t 类型则通常占两个字节四个字节,可以表示更多的字符集,例如Unicode字符集。在Windows平台上,wchar_t类型被广泛应用于API函数和字符串函数。

需要注意的是,使用 wchar_t 类型时,需要使用 wchar_t 类型的字符串字面量,例如 L"Hello, World!"。而不能使用普通的字符串字面量,例如 "Hello, World!",这会导致编译错误。

在实际开发中,如果需要处理 Unicode 字符集,应该使用 wchar_t 类型和 wchar_t 类型的字符串。如果只需要处理 ASCII 字符集,可以使用 char 类型和普通字符串。