【问题标题】:Convert Unicode code points to UTF-8 and UTF-32将 Unicode 代码点转换为 UTF-8 和 UTF-32
【发布时间】:2017-06-20 03:14:39
【问题描述】:

我想不出删除前导零的方法。我的目标是在 for 循环中创建每个数字的 UTF-8 和 UTF-32 版本。

例如,如果使用 UTF-8,我不需要删除前导零吗?有没有人有办法解决这个问题?基本上我要问的是:有人有一个简单的解决方案将 Unicode 代码点转换为 UTF-8 吗?

    for (i = 0x0; i < 0xffff; i++) {
        printf("%#x \n", i);
        //convert to UTF8
    }

下面是我尝试为每个i 完成的示例。

  • 例如:Unicode 值 U+0760(Base 16)将转换为 UTF8
    • 二进制:1101 1101 1010 0000
    • 十六进制:DD A0

基本上,我试图为每个 i 将其转换为 UTF-8 中的十六进制等效项。

我遇到的问题是,将 Unicode 转换为 UTF-8 的过程似乎涉及从位数中删除前导 0。我不确定如何动态地做到这一点。

【问题讨论】:

  • 当您说“每个数字”时,您的意思是整数 1234 会产生字符串“1234” UTF-8 编码吗?或者你的意思是它会在 UTF-8 中产生由 1234 表示的字符? (剧透:没有)或者你的意思是第 1234 个 Unicode 代码点?
  • @Schwern 我的目标是将所有 Unicode 字符 0x0 到 0x10FFFF 转换为 UTF8 和 UTF32 形式..
  • 这仍然模棱两可。你能举一个可靠的例子吗?你的意思是0x0到0xFFFF吗?这就是你的代码正在做的事情。
  • 有很多方法可以将代码点 0 到 10FFFF 转换为 UTF-8 的小字节序列。当然surrogates 不会转换。

标签: c utf-8 utf


【解决方案1】:

转换为UTF-32 很简单,只是Unicode code point

#include <wchar.h>

wint_t codepoint_to_utf32( const wint_t codepoint ) {
    if( codepoint > 0x10FFFF ) {
        fprintf( stderr, "Codepoint %x is out of UTF-32 range\n", codepoint);
        return -1;
    }

    return codepoint;
}

请注意,我使用 wint_t, w 表示“宽”。这是一个保证足够大以容纳任何wchar_t 以及EOF 的整数。 wchar_t(宽字符)保证足够宽以支持所有系统语言环境。

转换为 UTF-8 有点复杂,因为它的 codepage layout designed to be compatible with 7-bit ASCII。需要进行一些位移。

从 UTF-8 表开始。

U+0000  U+007F    0xxxxxxx
U+0080  U+07FF    110xxxxx  10xxxxxx
U+0800  U+FFFF    1110xxxx  10xxxxxx    10xxxxxx
U+10000 U+10FFFF  11110xxx  10xxxxxx    10xxxxxx    10xxxxxx

把它变成一个大的 if/else if 语句。

wint_t codepoint_to_utf8( const wint_t codepoint ) {
    wint_t utf8 = 0;

    // U+0000   U+007F    0xxxxxxx
    if( codepoint <= 0x007F ) {
    }
    // U+0080   U+07FF    110xxxxx  10xxxxxx
    else if( codepoint <= 0x07FF ) {
    }
    // U+0800   U+FFFF    1110xxxx  10xxxxxx    10xxxxxx
    else if( codepoint <= 0xFFFF ) {
    }
    // U+10000  U+10FFFF  11110xxx  10xxxxxx    10xxxxxx    10xxxxxx
    else if( codepoint <= 0x10FFFF ) {
    }
    else {
        fprintf( stderr, "Codepoint %x is out of UTF-8 range\n", codepoint);
        return -1;
    }

    return utf8;
}

然后开始填空。第一个很简单,只是代码点。

    // U+0000   U+007F    0xxxxxxx
    if( codepoint <= 0x007F ) {
        utf8 = codepoint;
    }

要做下一个,我们需要应用一个位掩码并进行一些位移。 C不支持二进制文字,所以我使用perl -wle 'printf("%x\n", 0b1100000010000000)'将二进制转换为十六进制

    // U+0080   U+07FF    110xxxxx  10xxxxxx
    else if( codepoint <= 0x00007FF ) {
        // Start at 1100000010000000
        utf8 = 0xC080;

        // 6 low bits using the bitmask 00111111
        // That fills in the 10xxxxxx part.
        utf8 += codepoint & 0x3f;

        // 5 high bits using the bitmask 11111000000
        // Shift over 2 to jump the hard coded 10 in the low byte.
        // That fills in the 110xxxxx part.
        utf8 += (codepoint & 0x7c0) << 2;
    }

剩下的交给你。

我们可以使用涉及每条逻辑的各种有趣的值来测试它。

int main() {    
    // https://codepoints.net/U+0041
    printf("LATIN CAPITAL LETTER A: %x\n", codepoint_to_utf8(0x0041));
    // https://codepoints.net/U+00A2
    printf("Cent sign: %x\n", codepoint_to_utf8(0x00A2));
    // https://codepoints.net/U+2603
    printf("Snowman: %x\n", codepoint_to_utf8(0x02603));
    // https://codepoints.net/U+10160
    printf("GREEK ACROPHONIC TROEZENIAN TEN: %x\n", codepoint_to_utf8(0x10160));

    printf("Out of range: %x\n", codepoint_to_utf8(0x00200000));
}

这是一个有趣的练习,但如果你想真正使用预先存在的库。 Gnome Lib has Unicode manipulation functions,还有很多缺失的 C 片段。

【讨论】:

  • 初始测试if (codepoint &gt; 0x001FFFFF)不正确。它应该是 if (codepoint &gt; 0x0010FFFF) 并且如果类型 win_t 已签名,则还需要对负值进行额外测试。更糟糕的是,win_t 可能只有 15 个值位……真是一团糟。
  • @chqrlie 谢谢,我很喜欢 UTF-32 检查。至于wint_t,根据我的阅读,它必须足够大以容纳系统上的所有语言环境,并且假设系统具有 UTF-8 似乎是合理的(我考虑过提出断言,但现在是 2017 年);这不是真的吗?它确实拖累了负面因素。 uint32_t 可能会更好,但它使返回错误代码变得困难。
  • 您可以使用int32_twchar_t 在 Windows 系统上通常为 16 位宽,这显然不足以容纳任何 Unicode 代码点。
  • @Smiley1000 微软违反标准是相当标准的。我想这就是他们全力以赴 UTF-16 时留下的遗产。我不会假装理解微软编译器中 C 和 C++ 的混搭,但他们声称你可以使用 char8_t 来表示 UTF-8。docs.microsoft.com/en-us/cpp/cpp/… 和一个有趣的提议 backport char8_t to C
  • 对,你可以用那个。我认为最好使用明确大小的字符类型(char8_tchar16_tchar32_t)。
【解决方案2】:

正如 Wikipedia UTF-8 页面所述,每个 Unicode 代码点(0 到 0x10FFFF)都以 UTF-8 字符编码为一到四个字节。

这是一个简单的示例函数,根据我之前的一篇文章进行了编辑。我现在也从整数常量中删除了U 后缀。 (.. 其意图是提醒人类程序员,常量是明确无符号的(根本不考虑负代码点),并且它确实假设 unsigned int code - 编译器不在乎,可能是因为即使对于这里的长期会员来说,这种做法似乎也很奇怪和令人困惑,所以我放弃并停止尝试包含此类提醒。:()

static size_t code_to_utf8(unsigned char *const buffer, const unsigned int code)
{
    if (code <= 0x7F) {
        buffer[0] = code;
        return 1;
    }
    if (code <= 0x7FF) {
        buffer[0] = 0xC0 | (code >> 6);            /* 110xxxxx */
        buffer[1] = 0x80 | (code & 0x3F);          /* 10xxxxxx */
        return 2;
    }
    if (code <= 0xFFFF) {
        buffer[0] = 0xE0 | (code >> 12);           /* 1110xxxx */
        buffer[1] = 0x80 | ((code >> 6) & 0x3F);   /* 10xxxxxx */
        buffer[2] = 0x80 | (code & 0x3F);          /* 10xxxxxx */
        return 3;
    }
    if (code <= 0x10FFFF) {
        buffer[0] = 0xF0 | (code >> 18);           /* 11110xxx */
        buffer[1] = 0x80 | ((code >> 12) & 0x3F);  /* 10xxxxxx */
        buffer[2] = 0x80 | ((code >> 6) & 0x3F);   /* 10xxxxxx */
        buffer[3] = 0x80 | (code & 0x3F);          /* 10xxxxxx */
        return 4;
    }
    return 0;
}

您为它提供一个无符号字符数组、四个或更大的字符以及 Unicode 代码点。该函数将返回在 UTF-8 中编码代码点所需的字符数,并在数组中分配。对于0x10FFFF 以上的代码,该函数将返回 0(未编码),但它不会检查 Unicode 代码点是否有效。 IE。它是一个简单的编码器,它所知道的关于 Unicode 的所有信息就是代码点从 00x10FFFF,包括在内。例如,它对代理对一无所知。

请注意,由于代码点显式为无符号整数,负参数将根据 C 规则转换为无符号。

您需要编写一个函数,打印出每个无符号字符中的至少 8 个有效位(C 标准确实允许更大的字符大小,但 UTF-8 仅使用 8 位字符)。然后,使用上述函数将 Unicode 代码点(00x10FFFF,包括)转换为 UTF-8 表示,并为数组中的每个 unsigned char 调用您的位函数,按递增顺序计算unsigned char 为该代码点返回的上述转换函数。

【讨论】:

  • 整数文字上的所有这些U 后缀都是多余且令人困惑的。 unsigned 参数类型不需要它们。顺便说一句:我不知道你为什么不使用 0xFFFF0x10FFFF 的后缀,但是在任何地方删除它们会提高可读性。
  • @chqrlie:见 cmets here。如果我删除它们,我将删除“提示”(如果您将参数切换为int code,则行为会发生变化)。算了,不值得努力。将编辑。
  • 确实不值得,chux 更新了自己的答案并删除了U 后缀。
  • @chqrlie:不,chux 从来没有。我自己也很喜欢它们,因为它们告诉我有一个特定的原因(尽管不是那个原因)导致未签名。我需要找到一种更好的方法来包含此类提示,即某些修改可能包含当前代码形式有意避免的意外副作用。我相信他们会帮助那些阅读和试验代码的人从中学习。此外,对于那些简单地获取代码并对其进行最低限度修改以呈现为他们自己的课程作业的人来说,这种导致错误的更改是一种“陷阱”。有什么建议吗?
  • (在任何较大的项目或生产代码中,我在实现之前的注释块中包含了这样的假设和选择;在声明之前的注释块中包含接口选择和它们的原因。当然,没有意义然后在任何类型的“陷阱”中。我确实尝试在学习示例和实际生产内容之间保持很大的差异。)
【解决方案3】:

很多方法来做这个有趣的练习,将code point 转换为UTF-8

为了不放弃所有编码经验,以下是开始 OP 的伪代码。

#define UTF_WIDTH1_MAX       0x7F
#define UTF_WIDTH2_MAX       0x7FF
#define UTF_WIDTH3_MAX       0xFFFF
#define UTF_WIDTH4_MAX       0x10FFFF

void PrintCodepointUTF8(uint32_t codepoint) {
  uint8_t first;
  uint8_t continuation_bytes[3];
  unsigned continuation_bytes_n;
  if (codepoint <= UTF_WIDTH1_MAX) {
    first = codepoint;
    continuation_bytes = 0;
  } else if (codepoint <= UTF_WIDTH2_MAX) {
    // extract 5 bits for first and 6 bits for one continuation_byte
    // and set some bits
    first = ...;
    continuation_bytes = ...
    continuation_bytes_n = 1;
  } else   if (codepoint <= UTF_WIDTH4_MAX) {
    if (isasurrogate(codepoint)) fail.
    // else extract 4 bits for first and 6 bits for each continuation_byte
    // and set some bits
    first = ...;
    continuation_bytes = ...
    continuation_bytes_n = 2;
  } else   if (codepoint <= UTF_WIDTH4_MAX) {
    // extract 3 bits for first and 6 bits for each continuation_byte
    // and set some bits
    first = ...;
    continuation_bytes = ...
    continuation_bytes_n = 3;
  } else {
    fail out of range.
  }
  print first and 0-3 continuation_bytes
}

【讨论】:

  • 我真的希望以二进制打印是这里的关键点,因为否则我基本上只是将他们的代码放在盘子上(在我的回答中;在我写我的时候没有看到你的) .讨厌那个;更愿意帮助学习,而不是跳过工作。
  • @NominalAnimal 嗯,肯定的different stroke for different folks。顺便说一句:为什么code &lt; 1114112U 中的十进制常量?还有很多Us。不需要,但也不错。那里的替代测试也很好 - 现在对于 OP 来说可能太多了,也许下周添加。
  • 没注意到它是十进制的! :) 因为我为代码点明确使用无符号整数参数,所以我拒绝负代码点太大(因为它们根据 C 规则转换为无符号整数)。 Us 是为了提出您提出的问题;即*“为什么这些常量明确无符号?”。我假设那些盲目复制代码的人只会将unsigned ints 转换为ints 并删除Us,这将使代码因负代码点而失败。如果我为此测试其他人的代码,我会测试一个常见错误:EOFWEOF
猜你喜欢
  • 1970-01-01
  • 2012-06-20
  • 2013-03-23
  • 1970-01-01
  • 2015-07-17
  • 2021-06-15
  • 2010-12-24
  • 2020-06-07
相关资源
最近更新 更多