Unicode存储在C char中

Mir*_*res 18 c unicode ascii

我现在正在Linux上学习C语言,我遇到了一些奇怪的情况.

据我所知,标准C的char数据类型是ASCII,1字节(8位).它应该意味着它只能包含ASCII字符.

在我使用的程序中char input[],它由getchar像这个伪代码的函数填充:

char input[20];
int z, i;
for(i = 0; i < 20; i++)
{
   z = getchar();
   input[i] = z;
}
Run Code Online (Sandbox Code Playgroud)

奇怪的是它不仅适用于ASCII字符,而且适用于我想象的任何字符,例如@&@{??ž?¶'`[?????¶?^€~[?^ø{&}?ž输入.

我的问题是 - 怎么可能?它似乎是C中许多美丽的例外之一,但我真的很感激解释.这是操作系统,编译器,隐藏语言的附加超级功能吗?

谢谢.

jsb*_*eno 22

这里没有什么魔力 - C语言让你可以访问原始字节,因为它们存储在comptuer内存中.如果您的终端使用utf-8(很可能),非ASCII字符在内存中占用多个字节.再次显示时,我们的终端代码将这些序列转换为单个显示的字符.

只需更改代码即可打印strlen字符串,您将看到我的意思.

要在C中正确处理utf-8非ASCII字符,你必须使用一些库来为你处理它们,比如glib,qt或许多其他字符.

  • 或尝试仅打印 input[0] 以查看它不会打印第一个字符,而仅打印最有可能是不可打印字符的第一个字节,然后尝试将 input[0] 和 input[1] 一起打印以查看多字节字符。 (2认同)

Mor*_*pfh 17

ASCII是一个7位字符集.在C中通常由8位字符表示.如果设置了8位字节中的最高位,则它不是 ASCII字符.

另请注意,您不能保证ASCII为基础,许多人忽略其他方案.如果你想检查"原始"字节是否是字母字符,你可以换句话说,在注意所有系统时,请说:

is_alpha = (c > 0x40 && c < 0x5b) || (c > 0x60 && c < 0x7b);
Run Code Online (Sandbox Code Playgroud)

相反,你必须使用ctype.h并说:

isalpha(c);
Run Code Online (Sandbox Code Playgroud)

唯一的例外,AFAIK,对于数字,至少在大多数表上,它们具有连续的值.

这样可行;

char ninec  = '9';
char eightc = '8';

int nine  = ninec  - '0';
int eight = eightc - '0';

printf("%d\n", nine);
printf("%d\n", eight);
Run Code Online (Sandbox Code Playgroud)

但这不能保证是'a':

alhpa_a = 0x61;
Run Code Online (Sandbox Code Playgroud)

不基于ASCII的系统,即使用EBCDIC ; 在这样的平台上的C仍然运行良好,但在这里它们(大多数)使用8位而不是7位,即A可以编码为十进制193而不是65ASCII.


但是对于ASCII; 具有十进制128 - 255(使用中的8位)的字节被扩展,而不是ASCII集的一部分.即ISO-8859使用此范围.

经常做什么; 也是将两个或多个字节组合成一个字符.因此,如果你打印两个字节后,定义为utf8 0xc3 0x98 ==Ø,那么你将得到这个字符.

这又取决于您所处的环境.在许多系统/环境中,打印ASCII值会在字符集,系统等中产生相同的结果.但打印字节> 127或双字节字符会根据本地配置给出不同的结果.

即:

A先生正在执行程序

JASN€

而B先生得到了

Jasπß

这可能与ISO-8859系列和扩展字符的单字节表示的Windows-1252特别相关.


  • UTF-8#Codepage_layout,在UTF-8中你有ASCII,那么你有特殊的byes序列.
    • 每个序列以字节> 127(最后一个ASCII字节)开头,
    • 然后是给定数量的字节,所有字节都以位开头10.
    • 换句话说,您永远不会在多字节UTF-8表示中找到ASCII字节.

那是; UTF-8中的第一个字节,如果不是ASCII,则表示该字符有多少字节.您也可以说ASCII字符表示不再有字节 - 因为最高位为0.

即如果文件被解释为UTF-8:

fgetc(c);

if c  < 128, 0x80, then ASCII
if c == 194, 0xC2, then one more byte follow, interpret to symbol
if c == 226, 0xE2, then two more byte follows, interpret to symbol
...
Run Code Online (Sandbox Code Playgroud)

举个例子.如果我们看一下你提到的一个角色.如果在UTF-8终端:

$ echo -n"č"| XXD

应该产量:

0000000:c48d ..

换句话说,"č"由两个字节0xc4和0x8d表示.将-b添加到xxd命令,我们得到字节的二进制表示.我们按如下方式剖析它们:

 ___  byte 1 ___     ___ byte 2 ___                       
|               |   |              |
0xc4 : 1100 0100    0x8d : 1000 1101
       |                    |
       |                    +-- all "follow" bytes starts with 10, rest: 00 1101
       |
       + 11 -> 2 bits set = two byte symbol, the "bits set" sequence
               end with 0. (here 3 bits are used 110) : rest 0 0100

Rest bits combined: xxx0 0100 xx00 1101 => 00100001101
                       \____/   \_____/
                         |        |
                         |        +--- From last byte
                         +------------ From first byte
Run Code Online (Sandbox Code Playgroud)

这给我们:00100001101 2 = 269 10 = 0x10D =>解码码点U + 010D =="č".

此数字也可以在HTML中用作&#269;==č

这个以及许多其他代码系统的共同点是8位字节是基础.


通常它也是关于背景的问题.作为一个例子取GSM SMS,与ETSI GSM 03.38/03.40(3GPP TS 23.038,3GPP 23038).在那里我们还找到了一个7位字符表,7位GSM默认字母表,但不是将它们存储为8位,而是存储为7位1.这样,您可以将更多字符打包到给定的字节数中.即标准SMS 160字符变为1280位或160字节作为ASCII和1120或140字节作为SMS.

1不例外,(故事更多).

即一个简单的字节示例,以SMS UDP格式保存为septets(7bit)C8329BFD06到ASCII:

                                _________
7 bit UDP represented          |         +--- Alphas has same bits as ASCII
as 8 bit hex                   '0.......'
C8329BFDBEBEE56C32               1100100 d * Prev last 6 bits + pp 1
 | | | | | | | | +- 00 110010 -> 1101100 l * Prev last 7 bits 
 | | | | | | | +--- 0 1101100 -> 1110010 r * Prev 7 + 0 bits
 | | | | | | +----- 1110010 1 -> 1101111 o * Last 1 + prev 6
 | | | | | +------- 101111 10 -> 1010111 W * Last 2 + prev 5
 | | | | +--------- 10111 110 -> 1101111 o * Last 3 + prev 4
 | | | +----------- 1111 1101 -> 1101100 l * Last 4 + prev 3
 | | +------------- 100 11011 -> 1101100 l * Last 5 + prev 2
 | +--------------- 00 110010 -> 1100101 e * Last 6 + prev 1
 +----------------- 1 1001000 -> 1001000 H * Last 7 bits
                                 '------'
                                    |
                                    +----- GSM Table as binary
Run Code Online (Sandbox Code Playgroud)

9个字节"unpacked"变为10个字符.

  • 这篇文章简直太棒了!感谢您的总结和概述。 (2认同)

小智 7

当然有很多库可以完成这项工作,但是要快速解码任何 UTF8 unicode,这个小函数很方便:

typedef unsigned char utf8_t;

#define isunicode(c) (((c)&0xc0)==0xc0)

int utf8_decode(const char *str,int *i) {
    const utf8_t *s = (const utf8_t *)str; // Use unsigned chars
    int u = *s,l = 1;
    if(isunicode(u)) {
        int a = (u&0x20)? ((u&0x10)? ((u&0x08)? ((u&0x04)? 6 : 5) : 4) : 3) : 2;
        if(a<6 || !(u&0x02)) {
            int b,p = 0;
            u = ((u<<(a+1))&0xff)>>(a+1);
            for(b=1; b<a; ++b)
                u = (u<<6)|(s[l++]&0x3f);
        }
    }
    if(i) *i += l;
    return u;
}
Run Code Online (Sandbox Code Playgroud)

考虑你的代码;您可以迭代字符串并读取 unicode 值:

int l;
for(i=0; i<20 && input[i]!='\0'; ) {
   if(!isunicode(input[i])) i++;
   else {
      l = 0;
      z = utf8_decode(&input[i],&l);
      printf("Unicode value at %d is U+%04X and it\'s %d bytes.\n",i,z,l);
      i += l;
   }
}
Run Code Online (Sandbox Code Playgroud)


ybu*_*ill 6

这就是UTF-8的神奇之处,您甚至不必担心它是如何工作的。唯一的问题是 C 数据类型被命名char(用于character),而它的实际含义是byte。字符和对它们进行编码的字节之间没有 1:1 的对应关系。

在您的代码中发生的事情是,从程序的角度来看,您输入了一个字节序列,它将字节存储在内存中,如果您打印文本,它将打印字节。这段代码不关心这些字节如何编码字符,它只是终端需要担心在输入时对它们进行编码并在输出时正确解释它们。


evi*_*tto 5

ASCII 是 7 位,而不是 8 位。achar []保存字节,可以是任何编码 - iso8859-1、utf-8,任何你想要的。C 不在乎。