【问题标题】:How to convert between character and byte position in Objective-C/C/C++如何在 Objective-C/C/C++ 中的字符和字节位置之间进行转换
【发布时间】:2013-02-23 08:24:08
【问题描述】:

我需要将 UTF-8 字符串中的字节位置转换为 Objective-C 中相应的字符位置。我确信必须有一个库来执行此操作,但我找不到 - 任何人都可以(尽管显然任何 C 或 C++ 库都可以在这里完成这项工作)。

我意识到我可以在需要的字符处截断 UTF-8 字符串,将其转换为 NSString,然后读取 NSString 的长度以得到我的答案,但这似乎是解决问题的一个有点笨拙的解决方案,可以用 C 语言中的一个小 FSM 非常简单地解决。

感谢您的帮助。

【问题讨论】:

  • 使用mblen循环遍历字符串。
  • @n.m.:这还不够,因为NSString 使用 UTF-16 偏移量。
  • @DietrichEpp: NSString 在这里没有作用。
  • @n.m.: NSString 在问题中被提及两次,并且也在标签中。 NSString 文档没有使用与 Unicode 标准相同的字符和位置术语,这很遗憾,但我怀疑问题是关于计算代码点。
  • @DietrichEpp:作为发帖人认为不合适的方法的一部分,它被提及。它不在要求中。

标签: c++ objective-c c utf-8 nsstring


【解决方案1】:

“字符”是一个有点模棱两可的术语,它在不同的上下文中意味着不同的东西。我猜您想要的结果与您的示例相同,[NSString length]

NSString 文档并未完全说明这一点,但[NSString length] 计算了字符串中 UTF-16 代码单元 的数量。所以 U+0000..U+FFFF 每个算一个,但 U+10000..U+10FFFF 每个算两个。并且不要拆分代理对!

您可以根据每个 UTF-8 字符的前导字节来计算 UTF-16 代码点的数量。尾随字节使用一组不相交的值,因此您根本不需要跟踪 任何 状态,除了您在字符串中的位置(好消息:有限状态机是多余的)。

static const unsigned char BYTE_WIDTHS[256] = {
    // 1-byte: 0xxxxxxx
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    // Trailing: 10xxxxxx
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    // 2-byte leading: 110xxxxx
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    // 3-byte leading: 1110xxxx
    // 4-byte leading: 11110xxx
    // invalid: 11111xxx
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0
};

size_t utf8_utf16width(const unsigned char *string, size_t len)
{
    size_t i, utf16len = 0;
    for (i = 0; i < len; i++)
        utf16len += BYTE_WIDTHS[string[i]];
    return utf16len;
}

该表是 1 表示 1 字节、2 字节和 3 字节 UTF-8 前导字符,2 表示 4 字节 UTF-8 前导字符,因为这些字符在转换为时会变成两个字符NSString.

我在 Haskell 中生成了表格:

elems $ listArray (0,256) (repeat 0) //
    [(n,1) | n <- ([0x00..0x7f] ++ [0xc0..0xdf] ++ [0xe0..0xef])] //
    [(n,2) | n <- [0xf0..0xf7]]

【讨论】:

  • 即使 (1) char 是有符号的,而不是无符号的,并且 (2) 一个字节不是 8 位(即CHAR_BIT != 8),上述解决方案是否正确?对于大多数现代应用程序,第二个问题可能会被忽略。我不知道有任何带有CHAR_BIT != 8 的现代平台。但第二个问题似乎更令人担忧,从那时起,string[i] 返回的chars 及其高位设置将被解释为对BYTE_WIDTHS 的负偏移,不是吗?
  • 啊,关于第 1 点,没关系;我在考虑std::string,它从operator[] 返回char,但是你的函数是根据const unsigned char * 参数编写的,所以这无关紧要。不过,我仍然对第 2 点感到好奇;当一个字节不是 8 位时,这一切是如何工作的? UTF-8 标准是否假定 8 位字节?
  • @bhaller: CHAR_BIT 无关紧要有两个原因。 #1:CHAR_BIT != 8 是如此非常罕见,以至于谈论它基本上毫无意义。您可能会谈论让独角兽编写您的代码。 #2:如果 CHAR_BIT != 8 则值 >= 256 在 UTF-8 中仍然是不允许的。 Unicode 标准没有“假定”8 位字节,它只是将术语“字节”定义为表示 8 位整数。这并不意味着您不能在 CHAR_BIT != 8 的系统上使用 Unicode,它只是意味着 Unicode 标准和 C 标准在该平台上对字节有不同的定义。
【解决方案2】:

查看 UTF-8 encoding 并注意代码点以以下 8 位模式开头:

76543210 <- bit
0xxxxxxx <- ASCII chars
110xxxxx \
1110xxxx  } <- more byte(s) (of form 10xxxxxx) follow
11110xxx /

这是您在搜索代码点开头时应该寻找的内容。

但这只是解决方案的一部分。您需要考虑Combining characters。您需要将变音符号与它们之前的主要字符组合在一起,您不能将它们分开并视为独立字符。

可能还有更多。

【讨论】:

  • 更重要的是,NSString API 计算 UTF-16 代码单元,而不是代码点。因此,您需要将 11110xxx 字节数计为两个。另请注意,您的图表来自已失效的 UTF-8 版本,当前标准停止在 11110xxx111110xx1111110x 无效)。
  • @DietrichEpp 谢谢,我已经删除了最后两个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-09
  • 2016-03-10
  • 2011-02-10
  • 1970-01-01
  • 1970-01-01
  • 2011-02-12
相关资源
最近更新 更多