【问题标题】:Numeric value of literal UTF-8 characters文字 UTF-8 字符的数值
【发布时间】:2021-04-20 17:15:59
【问题描述】:

我正在研究一个字符串反转义函数,它将像 \uxxxx(其中 xxxx 是十六进制值)这样的文字序列转换为相应值的字节。我打算让函数获取xxxx序列的前两个字符,计算字节值,并与第二个序列相同。

但是我遇到了一个意外的结果,即文字类型的 UTF-8 字符。以下说明了我的问题:

#include <stdio.h>

int main()
{
    unsigned char *str1 = "abcĢ";
    unsigned char *str2 = "abc\x01\x22";
    for (unsigned i = 0; i < 5; i++)
        printf ("String 1 character #%u: %x\n", i, str1[i]);
    for (unsigned i = 0; i < 5; i++)
        printf ("String 2 character #%u: %x\n", i, str2[i]);

    return 0;
}

输出:

String 1 character #0: 61
String 1 character #1: 62
String 1 character #2: 63
String 1 character #3: c4
String 1 character #4: a2
String 2 character #0: 61
String 2 character #1: 62
String 2 character #2: 63
String 2 character #3: 1
String 2 character #4: 22

Unicode 字符 Ģ 的 e 十六进制值为 \x0122,因此我希望字节 #3 和 #4 分别为 \x01x22

c4a2 来自哪里?我想我不明白字符串中的多字节字符是如何用 C 编码的。任何帮助将不胜感激。

【问题讨论】:

  • c4,a2Ģ (U+0122) 的 UTF-8 字节序列 带有 Cedilla 的拉丁文大写字母 G
  • 那么我的问题是,将c4 a2 转换为0122 并返回的逻辑是什么?
  • 当然,如果有人知道将文字"\\u0122" 转换为"\u0122" 的现成函数,那将很好地解决我的问题......不要试图重新发明轮子。
  • UTF8 不仅是字符的序数,它还编码了表示该字符所需的字节数。
  • 好的;那么您需要编写需要反斜杠、小写 U 和 4 个十六进制数字(任何一种情况)的代码,并且您需要将 4 个十六进制数字转换为 16 位无符号整数或一些更大的类型(32 位 int,可选 unsigned)。不是那么难。注意必须有4个十六进制数字;这是一个硬性要求;三个或更少是没有明确解释的无效序列(错误)。额外的十六进制数字无关紧要;它们超出了\u 转义序列的范围(但不得转换;因此,strtol() 不可用,除非您将 4 位数字提取到字符串中)。

标签: c string unicode


【解决方案1】:

Unicode 字符 Ģ 的 e 十六进制值为 \x0122,所以我希望字节 #3 和 #4 分别为 \x01\x22

c4a2 来自哪里?

在 Unicode 中,Ģ 是代码点 U+0122 LATIN CAPITAL LETTER G WITH CEDILLA,在 UTF-8 中编码为字节 0xC4 0xA2

您的源文件保存为 UTF-8,或者您的编译器配置为将字符串文字保存为 UTF-8。无论哪种方式,在您的 str1 字符串中,文字 Ģ 都存储为 UTF-8。因此:

unsigned char *str1 = "abcĢ";

大致相当于这个:

unsigned char literal[] = {'a', 'b', 'c', 0xC4, 0xA2, '\0'};
unsigned char *str1 = &literal[0];

在转义序列中,整个序列代表一个单个数值。因此,\x01\x22 分别代表单独的数值 0x01 十六进制(1 十进制)和 0x22 十六进制(34 十进制)。因此:

unsigned char *str2 = "abc\x01\x22";

大致相当于这个:

unsigned char literal[] = {'a', 'b', 'c', 0x01, 0x22, '\0'};
unsigned char *str2 = &literal[0];

您只是输出str1str2 指向的字符串的原始字节。

转义序列 \u0122 表示数值 0x0122 十六进制(290 十进制),在 Unicode 中是代码点 U+0122,因此在 UTF-8 中是 C4 A2。所以,如果你有这样的输入字符串:

const char *str = "abc\\u0122"; // {'a', 'b', 'c', '\', 'u', '0', '1', '2', '2', '\0'}

如果您想将其解码为 UTF-8,则需要检测 "\u" 前缀,提取以下 "0122" 子字符串,将其作为十六进制数字解析为整数,将该整数解释为 Unicode 代码点, 并将其转换为 UTF-8(abc 在 UTF-8 中已经是有效字符)。

【讨论】:

    【解决方案2】:

    UTF-8 不能以简单的方式将大值分解为单个字节,因为它会模棱两可。你怎么区分"\u4142" (䅂)和两个字符串"AB"

    从 Unicode 码位编号生成 UTF-8 字节字符串的规则非常简单,并且消除了歧义。给定任何字节值序列,它要么定义明确的代码点,要么是无效序列。

    这是一个简单的函数,它将单个 Unicode 代码点值转换为 UTF-8 字节序列。

    void codepoint_to_UTF8(int codepoint, char * out)
    /* out must point to a buffer of at least 5 chars. */
    {
        if (codepoint <= 0x7f)
            *out++ = (char)codepoint;
        else if (codepoint <= 0x7ff)
        {
            *out++ = (char)(0xc0 | ((codepoint >> 6) & 0x1f));
            *out++ = (char)(0x80 | (codepoint & 0x3f));
        }
        else if (codepoint <= 0xffff)
        {
            *out++ = (char)(0xe0 | ((codepoint >> 12) & 0x0f));
            *out++ = (char)(0x80 | ((codepoint >> 6) & 0x3f));
            *out++ = (char)(0x80 | (codepoint & 0x3f));
        }
        else
        {
            *out++ = (char)(0xf0 | ((codepoint >> 18) & 0x07));
            *out++ = (char)(0x80 | ((codepoint >> 12) & 0x3f));
            *out++ = (char)(0x80 | ((codepoint >> 6) & 0x3f));
            *out++ = (char)(0x80 | (codepoint & 0x3f));
        }
        *out = 0;
    }
    

    请注意,此函数不进行错误检查,因此如果您给它一个超出有效 Unicode 范围 0 到 0x10ffff 的输入,它将生成一个不正确(但仍然有效)的 UTF-8 序列。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      相关资源
      最近更新 更多