ANSI C UTF-8问题

6

首先,我使用 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?
}

感谢您。

1
你有“Nth字符”有用的案例示例吗? - R.. GitHub STOP HELPING ICE
mbstowcs不能保证做你所说的事情。它取决于你的区域设置,请参阅<locale.h>,并且通常与编码无关。如果要处理明确的编码,请使用iconv或类似工具。 - Kerrek SB
@R: 替换(char* str){对于(...){...获取字符(i)...} - Amir Saniyan
@kerrek:我应该使用ANSI C。我不想使用任何非标准头文件。 - Amir Saniyan
2
@Amir:你能循环指针而不是字符索引吗?例如,你可以这样做:for(p=str; *p!=NULL; move_one_char_forward(&p)) {...} - Todd Li
2
@Amir:ANSI C 不支持编码感知。你的问题明确要求 Unicode,因此唯一的两个答案是 a)在 ANSI C 中编写自己完整的 Unicode 库,或者 b)使用现有的、极为广泛和流行的符合 POSIX 标准的库。 - Kerrek SB
4个回答

8
也许你的想法有些错误。UTF-8是一种编码方式,非常适合对数据进行串行化处理,例如将其写入文件或网络中。但是,它是一种非常复杂的编码方式,Unicode代码点的原始字符串可能会以任意数量的编码字节形式出现。
如果你想要处理文本(根据你的描述),你应该在内部存储定长的原始字符串。如果你使用Unicode(应该这么做),那么每个代码点需要21位,所以最接近的整数类型是uint32_t。简单地说,将所有字符串在内部存储为整数数组。然后就可以随机访问每个代码点。
只有当你要写入文件或控制台时才将其编码为UTF-8,并在读取时解码为UTF-8。
顺便说一下,Unicode代码点仍然远远不是一个“字符”。字符的概念太高级了,没有简单的通用机制。(例如,“a”+“重音符号”——两个代码点,有多少个字符?)

1
错误。使用 uint32_t。你的 wchar_t 没有任何大小保证。如果你对这个主题感兴趣,可以查看我的最近抱怨 - Kerrek SB
@Amir:简而言之,因为它已经损坏了。长的答案是微软内部使用UTF-16编码。这有点傻,因为UTF16也是一种多字节编码,就像UTF8一样,所以这个做法是否有帮助还是值得商榷的。历史原因是当微软修复他们的标准时,Unicode注册的代码点少于65000个,所以每个人都认为“16位足够了”。我想那是在1999年;-) - Kerrek SB
@ninjalji:好的,我改口了,标准并没有提供绝对大小的保证。挑刺!:-) 如果我的执行字符集只有300个字符,那么我可以使用一个9位wchar来实现符合规范的实现。 - Kerrek SB
1
@ninjalji: 谢谢,正是我的观点!现在加入标准化并告诉我在任何有意义的文本数据处理模型中,“有多少个字符”应该是什么答案。这真的是一个相当高级的问题。零宽连字符算不算一个字符? - Kerrek SB
@Kerrek:ZWNJ 明确地不是一个字符。 - ninjalj
显示剩余6条评论

4

你无法做到。如果你确实需要大量这样的查询,可以为UTF-8字符串构建索引,或者事先将其转换为UTF-32。 UTF-32是更好的内存表示方法,而UTF-8在磁盘上表现良好。

顺便说一下,你列出的UTF-16代码也不正确。您可能要注意代理字符。


UTF-32 在处理单个字符时非常有用。但在大多数情况下,你不关心单个字符,只是想来回传输字符串,这就是为什么 UTF-8 如此流行的原因。 - ninjalj

1

你想要计算什么?正如Kerrek SB所指出的,你可能会遇到分解的字符组合,比如"é"可以被表示为单个字符(LATIN SMALL LETTER E WITH ACUTE U+00E9),或者两个字符(LATIN SMALL LETER E U+0065 COMBINING ACUTE ACCENT U+0301)。Unicode有组合和分解的规范化形式。

你真正感兴趣的不是字符,而是字形簇。你需要一些更高级别的库来处理这个问题,并且需要处理规范化形式、适当的(与区域设置相关的)排序方式、适当的换行方式、适当的大小写转换(例如德语ß->SS)、适当的双向支持等等...... 真正的国际化是复杂的。


1
而且,有关Unicode的良好讨论在您可能传统上使用“字符”的地方使用“代码点”,正是出于这个原因:由于历史包袱,“字符”在您想要区分字形/字形簇/连字等时太模糊了。 - tc.

0

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

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

将字符串拆分为字形簇所需的信息可以在附录29字符数据库中找到。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接