字符和unicode标准

字符和unicode标准

1. 数据类型

在编程语言中,字符的数据类型决定了可以存储哪些字符以及如何存储这些字符。 在 C 和 C++ 中,有两种主要字符类型

  • char
    • 主要用于ASCII
  • wchar_t
    • 主要用于Unicode
    • 在Windows平台上,wchar_t通常是2字节,使用UTF-16编码.当你在代码中写wchar_t hello_w[] = L"你好你好你好";时,L前缀告诉编译器这是一个宽字符字符串字面量.编译器会将这个字符串转换为UTF-16编码的宽字符序列,每个宽字符都是一个wchar_t.这个转换过程是在编译时完成的,所以它不受运行时环境的影响.
    • 需要注意的是,这个行为是特定于Windows平台的.在其他平台上,wchar_t可能是4字节,使用UTF-32编码.在这种情况下,”你”和”好”这两个字符也都会被编码为一个4字节的wchar_t.
    • 总的来说,wchar_t的大小和编码方式取决于具体的编译器和平台,而不是字符本身.在Windows平台上,wchar_t通常是2字节,使用UTF-16编码.当你在代码中写wchar_t hello_w[] = L"你好你好你好";时,L前缀告诉编译器这是一个宽字符字符串字面量.编译器会将这个字符串转换为UTF-16编码的宽字符序列,每个宽字符都是一个wchar_t.这个转换过程是在编译时完成的,所以它不受运行时环境的影响.
    • 需要注意的是,这个行为是特定于Windows平台的.在其他平台上,wchar_t可能是4字节,使用UTF-32编码.在这种情况下,”你”和”好”这两个字符也都会被编码为一个4字节的wchar_t.
    • 总的来说,wchar_t的大小和编码方式取决于具体的编译器和平台,而不是字符本身.
    • 在Windows平台上,当你在代码中使用wchar_tL前缀定义一个宽字符字符串时,编译器会将这个字符串转换为UTF-16编码的宽字符序列.

    这个转换过程是在编译时完成的,所以它不受你的输入编码格式的影响.无论你的源代码文件是使用什么编码格式保存的(例如ASCII/UTF-8/GBK等),编译器都会正确地解析这个文件,并将宽字符字符串转换为UTF-16编码. 需要注意的是,这个行为是特定于Windows平台的.在其他平台上,wchar_t可能使用其他的编码方式,例如UTF-32.因此,如果你的代码需要在多个平台上运行,你应该避免依赖于特定的wchar_t编码方式. 在Windows平台上,当你在代码中使用wchar_tL前缀定义一个宽字符字符串时,编译器会将这个字符串转换为UTF-16编码的宽字符序列. 这个转换过程是在编译时完成的,所以它不受你的输入编码格式的影响.无论你的源代码文件是使用什么编码格式保存的(例如ASCII/UTF-8/GBK等),编译器都会正确地解析这个文件,并将宽字符字符串转换为UTF-16编码. 需要注意的是,这个行为是特定于Windows平台的.在其他平台上,wchar_t可能使用其他的编码方式,例如UTF-32.因此,如果你的代码需要在多个平台上运行,你应该避免依赖于特定的wchar_t编码方式.

  • char16_t
    • c11引入,用于存储utf16
  • char32_t
    • c11引入,用于存储utf32
  • 总结 charwchar_t的主要区别在于它们的长度和编译器对它们的处理方式.

char通常用于存储ASCII字符和其他8位编码的字符,如UTF-8.当你在代码中使用char定义一个字符串时,编译器通常不会改变字符串的编码.也就是说,如果你的源代码文件是UTF-8编码的,那么char字符串也会是UTF-8编码的.

wchar_t则是一个宽字符类型,用于存储需要更多位数的字符,如UTF-16或UTF-32字符.当你在代码中使用wchar_tL前缀定义一个宽字符字符串时,编译器会将这个字符串转换为特定的宽字符编码.在Windows平台上,这个编码通常是UTF-16.

2. unicode标准

简单来说,就是世界上所有主流字符的集合。 Unicode 是一个国际标准,用于为世界上所有的字符、符号和表情符号分配一个唯一的数字。这个数字被称为字符的 Unicode 码点。Unicode 的目标是能够表示所有的写作系统,包括拉丁字母、希腊字母、阿拉伯字母、汉字、象形文字等。

Unicode 标准定义了一系列的字符集,每个字符集包含一组字符和它们对应的码点。例如,基本多语言平面(Basic Multilingual Plane,BMP)是最常用的字符集,它包含了大多数现代语言的字符,以及许多符号和标点符号。

Unicode 标准还定义了一些编码方案,如 UTF-8、UTF-16 和 UTF-32,这些编码方案定义了如何将字符的码点转换为字节序列。这使得 Unicode 字符可以被存储在文件中,或者在网络上进行传输。

总的来说,Unicode 标准是一个全球通用的字符编码系统,它使得我们可以在电脑上使用任何语言进行写作和阅读。

3. 编码方案

3.1 ascii

  • 控制字符:ASCII 的前 32 个字符(0-31)被定义为控制字符,如换行符(ASCII 10,表示为 \n)、回车符(ASCII 13,表示为 \r)等。这些字符主要用于控制设备,而不是表示可视的字符。
  • 可打印字符:ASCII 的后 95 个字符(32-126)被定义为可打印字符,包括英文大小写字母、数字、标点符号和一些特殊字符。
  • ASCII 标准本身只定义了 0 到 127 的字符集,这些字符占用 7 位。然而,许多系统和编码标准会使用 8 位(一个字节)来存储字符,这就留下了 128 到 255 的空间可以用于其他字符。
  • 这个 128 到 255 的范围通常被用于存储特殊字符,例如各种符号、非英语字母等。具体的字符集取决于使用的编码标准。例如,在 ISO 8859-1(也被称为 Latin-1)编码中,这个范围被用于存储西欧语言中的特殊字符,如 á, ñ, ô 等。
  • 然而,需要注意的是,这个 128 到 255 的范围并不是 ASCII 标准的一部分,而是由其他编码标准定义的。在不同的编码标准中,这个范围可能代表不同的字符集。

3.2 utf8

  • 在UTF-8编码中,一个字符可能由1到4个字节组成。对于多字节的字符(即2、3、4字节),除了第一个字节外,其余字节的开头都是10。这是UTF-8编码的特性,用于区分多字节字符中的首字节和后续字节。
  • 如果第一个字节的最高位是 1,那么你可以通过计算这个字节开头的连续 1 的个数来确定这个字符的字节长度。例如,如果第一个字节是 110xxxxx,那么这个字符就是两个字节长;如果第一个字节是 1110xxxx,那么这个字符就是三个字节长;如果第一个字节是 11110xxx,那么这个字符就是四个字节长。
  • 所以UTF-8可以兼容ascii,同时可以支持unicode标准。
  • 对于英文字符为大多数的时候,utf8大部分时候都是一个字节的,省空间;因为浪费了比较多的bit去做状态位,而非数据位。在非英文字符为主要内容的时候,经常都是三字节或者四字节的。

3.3 utf16

  • 可以用两个字节或者四个字节表示一个字符。
  • 具体的编码规则不研究了,应该就是针对utf-8的弱点设计的。即utf8中控制位太多,中文字符等其他语言字符基本都是三四字节。而utf16可以使得中文字符都是两个字节表示,一些不常用的才是四字节。

3.4 utf32

  • 统一四个字节表示每一个字符。
  • 字符处理:由于 UTF-32 对每个字符使用固定的 4 个字节,所以在处理字符时,可以直接通过索引来访问字符串中的任何字符,而不需要像处理 UTF-8 或 UTF-16 字符串那样进行复杂的计算。这使得 UTF-32 在需要频繁访问或修改字符串中的字符时非常有用。
  • 内存空间不是问题:UTF-32 使用的存储空间比 UTF-8 和 UTF-16 多,所以如果内存空间不是问题,而且需要进行大量的字符处理,那么 UTF-32 可能是一个好的选择。
  • 兼容性:有些系统或应用可能需要使用 UTF-32 来确保与某些特定的软件或硬件的兼容性。
  • 简单来说就是不用解析了。

4. std::string和std::wstring

4.1 std::string和std::wstring有什么不同

std::stringstd::wstring 都是 C++ 标准库中的字符串类型,但它们用于处理不同的字符集和编码.

  • std::string 是用于处理常规字符的字符串类,它通常用于处理 ASCII 和扩展 ASCII 字符.在 std::string 中,每个字符通常由一个字节(8位)表示,这取决于 char 类型在特定平台上的大小.

  • std::wstring 是宽字符串类,用于处理宽字符集,如 Unicode.在 std::wstring 中,每个字符通常由一个 wchar_t 类型的值表示,这个值的大小取决于特定平台上 wchar_t 类型的大小.在 Windows 平台上,wchar_t 通常是 16 位的,可以用于表示 Unicode 中的基本多语言平面(BMP)中的字符.在一些其他平台上,wchar_t 可能是 32 位的,可以用于表示 Unicode 中的所有字符.

选择使用 std::string 还是 std::wstring 取决于你需要处理的字符数据的类型和编码.如果你只需要处理 ASCII 或扩展 ASCII 字符,那么 std::string 可能就足够了.如果你需要处理 Unicode 字符,那么你可能需要使用 std::wstring.

99. quiz

1. char是有符号的还是无符号的

在主流的编程平台上,char 的表现可能会有所不同.具体来说,char 是有符号的还是无符号的,取决于编译器和平台.

  • 在大多数平台上,包括 Windows 和大多数 Unix-like 系统(如 Linux,macOS)上,char 默认是有符号的(signed). 这意味着它可以表示从 -128 到 127 的整数.

  • 然而,在某些平台上,如一些嵌入式系统和某些 Unix-like 系统(如 ARM,PowerPC),char 默认是无符号的(unsigned). 这意味着它可以表示从 0 到 255 的整数.

这种差异可能会导致一些微妙的问题.例如,如果你的代码假设 char 是有符号的,然后在一个 char 是无符号的平台上运行,那么可能会出现意外的行为.因此,如果你的代码依赖于 char 的符号性,那么最好明确地使用 signed charunsigned char.

2. 为什么char是在windows平台是有符号的?

char在Windows平台上是有符号的,这主要是由于历史原因和兼容性考虑. char为什么是有符号的,已经无从考究了. 只能现在推测一下.

    1. ascii只要求了0-127,所以满足使用要求了.
    1. signed char可能是C语言历史包袱带下来的,有符号的目的是为了做字符类型的数值运算. 例如,’b’-‘a’表示距离为1,而’a’-‘b’等于-1.也许在某个古老版本依赖这种性质,因此设置为是有符号的.而linux的嵌入式设备受历史包袱影响较小,所以就是unsigned char了

3. 网络传输数据的时候,为什么byte类型都是unsigned char的,signed char可以吗?

在网络传输数据时,通常使用 unsigned char 来表示字节,主要有以下几个原因:

  1. 统一性:unsigned char 在所有平台上都表示 0 到 255 的整数,这样可以保证在不同的系统和平台之间传输数据时,数据的解释是一致的.

  2. 无符号性:网络传输的数据通常是二进制数据,而不是数值,所以没有正负之分.使用 unsigned char 可以避免由于符号位引起的混淆.

  3. 兼容性:许多网络协议和函数库都假定数据是由 unsigned char 类型的字节组成的.如果使用 signed char,可能会导致兼容性问题.

虽然理论上 signed char 也可以用于网络传输,但由于上述原因,实际上几乎所有的网络协议和函数库都使用 unsigned char 来表示字节.

3.1 什么是无符号的兼容性?

这个问题要先关注符号扩展和零扩展.

符号扩展和零扩展是计算机科学中的两种位扩展技术,通常用于将较小的整数类型转换为较大的整数类型.

  1. 符号扩展:当我们将一个有符号整数从较小的类型转换为较大的类型时,我们需要保持这个数的符号(正或负).为了做到这一点,我们将较小类型的最高位(也就是符号位)复制到较大类型的所有额外位中.例如,如果我们将一个8位的有符号整数-1(二进制表示为11111111)转换为16位,那么结果将是-1(二进制表示为1111111111111111).

  2. 零扩展:当我们将一个无符号整数从较小的类型转换为较大的类型时,我们不需要保持符号,因为无符号整数总是正的.因此,我们可以简单地将较大类型的所有额外位设置为0.例如,如果我们将一个8位的无符号整数255(二进制表示为11111111)转换为16位,那么结果将是255(二进制表示为0000000011111111).

这两种扩展方式都是为了保持数值的正确性.在C++中,这些扩展通常会自动发生,例如当你将一个char赋值给一个int时.

即使传输的数据是有符号的,但是无符号也不会修改数据,不影响读.

4. byte类型用unsigned char好,还是u8int_t好?

unsigned charuint8_t 都是无符号的整数类型,但是它们之间存在一些差异:

  1. 大小:unsigned char 的大小在不同的平台和编译器上可能会有所不同,但是它至少能够表示 0 到 255 的整数.而 uint8_t 是一个精确的 8 位无符号整数类型,它总是能够表示 0 到 255 的整数.

  2. 可移植性:unsigned char 是 C++ 标准的一部分,因此它在所有的 C++ 平台和编译器上都是可用的.而 uint8_t 是 C99 和 C++11 标准的一部分,因此它在一些较旧的 C++ 平台和编译器上可能不可用.

  3. 用途:unsigned char 通常用于表示字符或字节.而 uint8_t 通常用于表示精确大小的无符号整数,例如在处理二进制数据或硬件接口时.

总的来说,如果你需要一个精确的 8 位无符号整数,并且你的代码需要在 C++11 或更高版本的平台上运行,那么你应该使用 uint8_t.否则,你应该使用 unsigned char. 简单来说,unsigned char在c/c++标准其实没有严格限制是8bit的,只是要求是大于等于8bit的. 但是uint8_t又不是c98标准的,因此部分考虑到老代码兼容性的可能会用unsigned char,而新的其实只需要用uint8_t就好了

100. 练习

#include <iostream>
using namespace std;

int main() {
  char hello[] = "你好你好你好";
  std::cout << sizeof(hello) << std::endl; 
  // 如果你好是通过utf8输入,输出19。
  // 如果你好是通过gbk输入的,输出13
  std::cout << hello << std::endl;

  wchar_t hello_w[] = L"你好你好你好";
  std::cout << sizeof(hello_w) << std::endl; 
  // 输出14, 编译器转成了utf16编码。
  // 不管管"你好"输入是以utf8输入,还是其他编码方案输入。

  return 0;
}