接下来的情况:我有西里尔文符号“б”。运行下一个代码:
int main() {
char c;
scanf("%c", &c);
printf("%d\n", c);
return 0;
}
显示-48
。但是,当我调试此变量c
时,下一个显示给我:-48 '\320'
。
那么这如何工作?这是2长度数组的指针吗?或者它如何存储两个数字?
答案 0 :(得分:5)
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中的一到三位表示具有相应 octal 值的字节; 0320
实际上对于208是八进制的,对于0xD0是十进制的。
因此,这里没有什么神秘之处:c
仍然包含一个值(仅是您字符的“一半”);您所看到的只是其内容的两种(相当不便)表示。
注释
char
的签名(不幸的是,它是实现定义的)。答案 1 :(得分:3)
[使用utf-8
的西里尔字符为多字节 char
。您的十六进制“字符”是字符串/数组:
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
所称的“代码点”。
放置在文本文件中时,代码点将变为多字节序列,如上所述。
scanf
和printf
对此一无所知。例如,printf
仅发送字符串:XXXXXXX\0
,其中X可以是单个ASCII字符或多字符代码点的一部分。
这取决于终端仿真器并从utf-8字体集中输出正确的字符(该字体集包含西里尔字符,希腊字符,法语字符等)
strlen
和strcpy
之类的功能 only 关心尾随的0x00 EOS字符。因此,从技术上讲,它们可以正常工作,并且通常可以像通过EOS一样容易地通过utf-8
字符串和ASCII一样通过。
但是,strlen
将为您提供字符串中char
的数量。例如,在上面的strlen
中将返回2,因为它将D0
和B1
作为char
数组中单独的char
值进行计数。
而且,strchr
[可能]不起作用。您可能想在strstr
的地方使用utf-8
。
当然,其中的西里尔字符只有一个 代码点,因此utf-8
感知功能必须以不同的方式处理数组。例如,在计算代码点数时,他们需要查看D0B1
是一个单个代码点,因此结果计数为一个
一般规则是ASCII(0x01-0x7F
)作为单个utf-8
直接映射到char
。设置了高位(0x80
)的任何内容都是utf-8
多字节代码点的一部分。 0x40
用于指示序列的 start [最左侧]字节。序列中所有其余字节的格式(以位为单位):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
感知功能可以在向前或向后扫描时检测并跳过代码点。并且,可以区分两个[或更多]相邻的多字节代码点。