【问题标题】:How can char store two numbers?char 如何存储两个数字?
【发布时间】:2018-12-05 22:59:06
【问题描述】:

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

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

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

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

【问题讨论】:

  • 八进制数再次出现。
  • 没有c是char,只能存储1个字节。
  • @Broman 没有。字节是一个内存单元,char 是一种类型,能够表示实现的基本字符集中的一个字符。
  • 调试器没有显示两个数字。它显示了相同值的两种表示形式,一种以十进制表示,另一种表示为带有转义序列的字符。
  • @Broman:就标准而言,char 几乎是一个字节的定义,不管它有多大。你们两个正在混合这两个定义——“byte = octet”(现在软件社区中通常的意思)和“byte = 最小可寻址数量 = char”(C 和 C++ 标准对字节的含义)。

标签: c char cyrillic


【解决方案1】:

char 变量既可以用于存储一个小的1 整数,也可以用于存储一些定义不太明确的字符(更准确地说是代码单元),通常是 ASCII-基于编码。在这里,调试器只是试图通过显示c 内容的两个(有争议的)有意义的表示来提供帮助。


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

c = {char} 97 'a'

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

不幸的是,您可以将每个可能的字符都放入单个 8 位 char 值的想法是完全有缺陷的,因此目前使用最广泛的编码 (UTF-8),恰好是在您的机器,需要多个代码单元(≈字节)来表示单个代码点(≈逻辑字符)(更多细节in this question)。特别是,б表示为两个字节的字符串,即字节0xD0和0xB1。

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

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

至于'\320':这里的调试器想显示那个值的ASCII表示;但是,字节 0xD0 超出了 ASCII 字符范围2,因此在这里它以转义序列显示。您可能熟悉'\n' 表示换行符或\0 表示NUL 字符;一般来说,\ 后跟 C 中的一到三位数字表示具有相应 八进制 值的字节; 0320 对于 208 来说确实是八进制,对于 0xD0 来说是十进制。

所以,这并不神秘:c 仍然包含一个值(它只是你角色的“一半”);您所看到的只是其内容的两种(同样不方便)表示。


备注

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

【讨论】:

    【解决方案2】:

    西里尔字符 [使用 utf-8] 是 多字节 chars。您的十六进制“字符”是字符串/数组:

    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
    

    更新:

    那么,这个 char 是如何在内存中定位的?当涉及到西里尔字母时,C 是否能够使 char 成为 2 字节?

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

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

    你所说的西里尔字符就是utf-8 所说的“代码点”。

    当放置在文本文件中时,代码点变成如上所述的多字节序列。

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

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

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

    但是,strlen 会给你字符串中char 的数量。例如,在上面的 strlen 将返回 2,因为它将 D0B1 计算为 char 数组中的单独 char 值。

    而且,strchr [可能] 不起作用。您可能希望使用strstr 代替utf-8

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

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

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

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

    【讨论】:

    • 那么,这个字符是如何在内存中定位的呢?当涉及到西里尔文时,C 是否能够使 char 成为 2 字节?这是否意味着我可以分配给char c cyrillic б?虽然只有一个字节?
    • 好吧,我实际上只是了解scanf("%c") 的实际工作原理,它从西里尔文 б 中提取了一个字节,实际上是两个字节。但是/320 是什么?
    猜你喜欢
    • 2019-06-20
    • 2016-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多