【问题标题】:ANSI C UTF-8 problemANSI C UTF-8 问题
【发布时间】:2011-09-24 18:10:48
【问题描述】:

首先,我使用 ANSI C(不是 C++ 和任何非标准库,如 MS CRT 或 glibc,...)开发了一个独立的平台库。

经过几次搜索,我发现在 ANSI C 中实现国际化的最佳方法之一是使用 UTF-8 编码。

在 utf-8 中:

  • strlen(s):始终计算字节数
  • mbstowcs(NULL,s,0):字符数可以统计。

但是当我想随机访问 utf-8 字符串的元素(字符)时,我遇到了一些问题。

ASCII 编码:

char get_char(char* assci_str, int n)
{
  // It is very FAST.
  return assci_str[n];
}

在 UTF-16/32 编码中:

wchar_t get_char(wchar_t* wstr, int n)
{
  // It is very FAST.
  return wstr[n];
}

这里是我的 UTF-8 编码问题:

// What is the return type?
// Because sizeof(utf-8 char) is 8 or 16 or 24 or 32.
/*?*/ get_char(char* utf8str, int n)
{
  // I can found Nth character of string by using for.
  // But it is too slow.
  // What is the best way?
}

谢谢。

【问题讨论】:

  • 你有没有使用“Nth character”的例子?
  • mbstowcs 不保证按照您的要求行事。这取决于您的语言环境设置,请参阅<locale.h>,并且通常与编码无关。如果您处理明确的编码,请使用iconv 或类似的东西。
  • @R: replace(char* str){for(...){...get_char(i)...}
  • @Amir:你能循环指针而不是字符索引吗?例如,你可以这样做:for(p=str; *p!=NULL; move_one_char_forward(&p)) {...}
  • @Amir:ANSI C 不支持编码。您的问题明确要求使用 Unicode,因此只有两个答案是 a) 用 ANSI C 编写您自己的完整 Unicode 库,或 b) 采用现有的、极其广泛且流行的符合 POSIX 的库。

标签: c string utf-8


【解决方案1】:

也许您对此的想法有些错误。 UTF-8 是一种编码,可用于序列化数据,例如将其写入文件或网络。不过,这是一种非常重要的编码,Unicode 代码点的原始字符串可以以任意数量的编码字节结束。

如果您想处理文本(根据您的描述),您可能应该做的是在内部存储 raw、固定宽度的字符串。如果您要使用 Unicode(您应该这样做),那么每个代码点需要 21 位,因此最接近的整数类型是 uint32_t。简而言之,将所有字符串在内部存储为整数数组。然后你可以随机访问每个代码点。

仅在写入文件或控制台时编码为 UTF-8,读取时从 UTF-8 解码。

顺便说一句,Unicode 代码点距离字符还有很长的路要走。角色的概念对于一个简单的通用机制来说还远远不够高级。 (例如“a”+“accent Grave”——两个代码点,多少个字符?)

【讨论】:

  • 是的,你是对的,最好使用固定大小的字符而不是使用 utf-8 编码。现在我想知道哪种类型更适合 UNICODE 字符串? whar_t 还是 uint32_t?我的答案是 wchar_t。但这是正确的选择还是错误的选择?
  • 错了。使用uint32_t。您的wchar_t 不提供任何尺寸保证。如果您对这个主题感兴趣,请查看my recent rant
  • @Kerrek SB:在标准中,wchar_t 带有一个非常明确的大小保证:能够存储执行字符集的任何字符。某些 C 库已损坏且不提供此保证是另一回事。
  • @Amir:简而言之,因为它坏了。长答案是 MS 在内部使用 UTF-16。这有点傻,因为 UTF16 也是一种多字节编码,就像 UTF8 一样,所以它有什么帮助是值得怀疑的。历史原因是,当 MS 修正他们的标准时,Unicode 的注册码点少于 65000 个,所以每个人都认为“16 位就足够了”。我想那是在 1999 年 ;-)
  • @ninjalji:谢谢,正是我的意思!现在混合规范化并告诉我在任何有意义的文本数据处理模型中“多少个字符”的答案应该是什么。这确实是一个相当高级的问题。零宽度连接符是字符吗?
【解决方案2】:

你根本做不到。如果您确实需要大量此类查询,您可以为 UTF-8 字符串构建索引,或预先将其转换为 UTF-32。 UTF-32 在内存中表现更好,而 UTF-8 在磁盘上表现更好。

顺便说一句,您列出的 UTF-16 代码也不正确。您可能需要处理代理字符。

【讨论】:

  • UTF-32 在您需要处理单个字符时很有用。大多数情况下,您并不关心,只想来回移动字符串,这就是 UTF-8 如此扩展的原因。
【解决方案3】:

你想数什么?正如 Kerrek SB 所指出的,您可以分解字形,即“é”可以表示为单个字符 (LATIN SMALL LETTER E WITH ACUTE U+00E9),也可以表示为两个字符 (LATIN SMALL LETER E U+0065 COMBINING ACUTE ACCENT U+0301) . Unicode 具有组合和分解的规范化形式。

您可能对计数感兴趣的不是字符,而是字形簇。您需要一些更高级别的库来处理这个问题,并处理规范化形式,以及适当的(依赖于语言环境的)排序规则、适当的换行、适当的大小写折叠(例如德语 ß->SS)适当的双向支持等。 .. 真正的 I18N 很复杂。

【讨论】:

  • 关于 Unicode 的体面讨论使用“代码点”,而您可能传统上使用“字符”正是出于这个原因:历史包袱意味着当您想区分字形/字形时“字符”太含糊了/字素簇/连字/...
【解决方案4】:

与其他人所说的相反,我真的没有看到使用 UTF-32 而不是 UTF-8 的好处:在处理文本时,字形簇(或“用户感知字符”)比 Unicode 字符有用得多(即原始代码点),因此即使是 UTF-32 也必须被视为可变长度编码。

如果您不想使用专用库,我建议使用 UTF-8 作为磁盘上的字节序不可知表示和修改后的 UTF-8(它与 UTF-8 不同,将零字符编码为两个 -字节序列)作为与 ASCIIZ 兼容的内存表示。

可以在annex 29character database 中找到将字符串拆分为字素簇的必要信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-22
    • 1970-01-01
    • 2012-01-08
    • 2023-03-19
    • 2015-10-06
    • 1970-01-01
    • 2014-07-02
    • 2011-11-30
    相关资源
    最近更新 更多