char如何存储两个数字?

How can char store two numbers?

下一个案例:我有西里尔字母“б”。 运行 下一个代码:

int main() {
    char c;
    scanf("%c", &c);
    printf("%d\n", c);
    return 0;
}

显示-48。但是当我调试这个变量 c 时,它会显示下一个:-48 '0'.

那么这是如何工作的呢?这是一个指向 2 长度数组的指针吗?或者它是如何存储两个数字的?

西里尔字符 [using utf-8] 是 multibyte chars。您的十六进制 "character" 是 string/array:

D0B1

因此,您不能使用%c检索它。您需要使用 %s:

#include <stdio.h>

int
main(void)
{
    char utf[1000];
    char *cp;

    scanf("%s", utf);
    printf("%s\n", utf);

    for (cp = utf;  *cp != 0;  ++cp)
        printf(" %2.2X",*cp & 0xFF);
    printf("\n");

    return 0;
}

这是输出:

б
 D0 B1

更新:

So, how does that char is located in memory? Is C able to make char 2-byte when it comes to cyrillic?

首先,请参阅:https://en.wikipedia.org/wiki/UTF-8

当您通过键盘输入西里尔字符时,键盘硬件、终端仿真器程序和文本编辑器的组合将键盘序列转换为 utf-8 序列,最终出现在文本中您正在编辑的文件。

你所说的西里尔字符就是 utf-8 所说的 "code point"。

放在文本文件中时,代码点变成了上面提到的多字节序列。

scanfprintf 对此一无所知。例如,printf 仅发送字符串:XXXXXXX[=23=] 其中 X 可以是单个 ASCII 字符或多字符代码点的一部分。

由终端仿真器来理解这一点并从 utf-8 字体集中输出正确的字符[包含西里尔字符、希腊字符、法语字符等]

strlenstrcpy 等函数仅 关心尾随的 0x00 EOS 字符。因此,从技术上讲,它们可以工作并且通常可以像 ASCII 一样轻松地传递 utf-8 字符串,因为 EOS 是相同的。

但是,strlen 会给出字符串中 char 的个数。例如,在上面的 strlen 中将 return 2 因为它将 D0B1 计为 char 数组中的单独 char 值。

而且,strchr [可能] 行不通。您可能想使用 strstr 代替 utf-8.

当然,其中只有 一个 西里尔字符代码点,因此 utf-8 感知函数必须以不同方式处理数组。例如,在计算代码点数时,他们需要看到 D0B1 是一个 单个 代码点,因此得到的计数是 one

一般规则是 ASCII (0x01-0x7F) 作为单个 char 直接映射到 utf-8。设置了高位 (0x80) 的任何内容都是 utf-8 多字节代码点的一部分。 0x40 用于表示序列的 start [leftmost] 字节。序列中所有剩余字节的形式(以位为单位):10xxxxxx。序列中剩余字节数由起始字节中前缀 1 的位数表示。在下面的 table 中,它显示了如何解码字节序列(x 表示作为代码点值的一部分的位):

# of    Start       Remaining Bytes
bytes   Byte
1       0xxxxxxx
2       110xxxxx    10xxxxxx
3       1110xxxx    10xxxxxx    10xxxxxx
4       11110xxx    10xxxxxx    10xxxxxx    10xxxxxx

因此,utf-8 感知功能可以在向前或向后扫描时检测并跳过代码点。并且,可以区分两个[或多个]相邻的多字节代码点。

一个char变量可以用来存储一个小的1整数,或者一个字符(更准确地说,代码单元)在一些不太好的定义的、通常基于 ASCII 的编码。这里调试器只是试图通过显示 c.

内容的两个(有争议的)有意义的表示来提供帮助

让我们想象一下,您实际上写了 a 而不是 б;在这种情况下,调试器会写类似

c = {char} 97 'a'

因为c中实际存储的数字是97,解码为ASCII,对应字母a.

不幸的是,您可以将所有可能的字符放入单个 8 位 char 值中的想法是完全错误的,因此当今使用最广泛的编码 (UTF-8) 恰好是一个在您的机器上使用,需要多个 代码单元 (≈字节)来表示单个代码点(≈逻辑字符)(更多细节 )。特别地,б表示为两个字节的字符串,即字节0xD0和0xB1。

C 对 UTF-8 或代码点一无所知;如果您将 %c 指定为 scanf,它会读取一个字节,而不管它是否足以表示完整的 UTF-8 代码点。因此,只有第一个字节被读取,c 只包含 0xD0 值; 0xB1 仍在缓冲区中,尚未读取。

回到调试器显示的值,首先必须注意,在您的平台上(不幸的是,在许多平台上),char 已签名。因此,0xD0 字节被解释为带符号的值 -48(实际上,0xD0 = 208,"wraps around" 为 127;208 - 256 = -48)。

至于'0':此处的调试器希望显示该值的ASCII 表示;但是,字节 0xD0 超出了 ASCII 字符范围 2,因此此处显示为转义序列。您可能熟悉 '\n' 表示换行符或 [=24=] 表示 NUL 字符;通常,C 中的 \ 后跟一到三个数字表示具有相应 octal 值的字节; 0320确实是八进制的208,也就是十进制的0xD0。

所以,这里没有什么神秘之处:c 仍然包含一个值(只是你角色的 "half");您所看到的只是其内容的两个(同样不方便)表示。


备注

  1. 在大多数平台上,[-128, 127] 或 [0, 255],取决于 char 的符号(不幸的是,这是实现定义的)。
  2. 的确,UTF-8 扩展了 ASCII,它的多字节序列仅使用设置了高位的字节(ASCII 未使用);这确保它们不会被误解为 ASCII 文本。