使用C/C++对Unicode字符进行转义

7
我需要将输入字符串中的Unicode字符转义为UTF-16或UTF-32转义序列。例如,输入字符串字面值“Eat,drink,愛”应该被转义为“Eat,drink,\u611b”。以下是一些规则:

转义 | Unicode码点


'\u' HEX HEX HEX HEX | 一个Unicode码点,在U+0到U+FFFF范围内(包括), 对应于编码的十六进制值。


'\U' HEX HEX HEX HEX HEX HEX HEX HEX | 一个Unicode码点,在U+0到U+10FFFF范围内, 对应于编码的十六进制值。


通常来说,检测Unicode字符很简单,因为ASCII的第二个字节是0:

L"a" = 97, 0

这里有一个不会被转义的HTML标签。在Unicode字符中,第二个字节永远不是0:

L"愛" = 27, 97

这段文本涉及到IT技术,需要进行翻译。其中包含一个被转义的Unicode字符“\u611b”。但是如何检测UTF-32编码的字符串呢?因为它与UTF-16不同,需要用8个十六进制数字进行转义。

检测UTF-32编码的字符串并不像简单地检查字符串大小那样容易,因为UTF-16字符是多字节的,例如:

L"प्रे" = 42, 9, 77, 9, 48, 9, 71, 9

我被委托转义未转义的输入字符串文字,例如Eat, drink, 愛,并将它们以转义的文字形式Eat, drink, \u611b(UTF-16示例)存储到磁盘中。如果我的程序发现一个UTF-32字符,它也应该以\U8902611b(UTF-32示例)的形式进行转义,但我无法确定如何可靠地区分在输入字节数组中处理UTF-16或UTF-32。那么,我如何能够可靠地区分字符串或字节数组中的UTF-16和UTF-32字符?

我不明白你在问什么。你是以什么形式存储输入的 Eat, drink, 愛?你的问题似乎是将其存储为未指定编码的字节数组,并尝试猜测每个单独字符的编码,而不是整个数组的编码。 - user743382
2
请注意,Windows的wchar_t是2个字节,Linux的wchar_t是4个字节。 Windows的wchar_t可以使用UTF-16,但不能使用UTF-32。 Linux的wchar_t可以同时使用两者。如果您只有一串字节流,则无法区分,除非序列开头有一个字节顺序标记(BOM)字符。 - cup
包含基本源字符集(ASCII的子集)之外任何字符的文字是依赖于实现的,处理它们的任何方法都是不可移植的。你的文字是UTF-16、UTF-32、UTF-8还是其他任何东西,完全取决于你的实现,而不是字符串的内容。 - n. m.
您可以要求输入格式的必要知识(例如由平台隐含或通过BOM或其他手段),以产生单个转换输出,也可以生成两个可能的输出。在没有编码知识的情况下,无法很好地区分UTF-16字节和UTF-32字节,除了统计学方法。Windows API中有一个基于统计学的检测器,但它已经做出了一些臭名昭著的错误鉴定。 - Cheers and hth. - Alf
@hvd 微软编译器不符合标准,这是众所周知的事实。 - n. m.
显示剩余4条评论
1个回答

19

你的问题包含多个方面,我会尝试回答其中最重要的几个。

问:我有一个C++字符串,例如"Eat, drink, 愛",它是UTF-8、UTF-16还是UTF-32字符串?
答:这是由具体实现定义的。在许多实现中,这将是一个UTF-8字符串,但这并不是标准所规定的。请参考文档。

问:我有一个宽的C++字符串,例如L"Eat, drink, 愛",它是UTF-8、UTF-16还是UTF-32字符串?
答:这是由具体实现定义的。在许多实现中,这将是一个UTF-32字符串。在其他一些实现中,它将是一个UTF-16字符串。这些都不是标准规定的。请参考文档。

问:如何编写可移植的UTF-8、UTF-16或UTF-32 C++字符串字面量?
答:在C++11中有一种方法:

u8"I'm a UTF-8 string."
u"I'm a UTF-16 string."
U"I'm a UTF-32 string."

在C++03中,没有这样的运气。

问:字符串"Eat, drink, 愛"至少包含一个UTF-32字符吗?
答:没有所谓的UTF-32字符(以及UTF-16和UTF-8)。它们都是UTF-32等字符串。它们都包含Unicode字符

问:什么是Unicode字符?
答:它是由Unicode标准定义的编码字符集中的一个元素。在C++程序中,它可以用不同的方式表示,最简单和直接的方法是用一个与字符代码点对应的单个32位整数值。(为简单起见,在此忽略复合字符,并将“字符”和“代码点”视为相等,除非另有说明)。

问:给定一个Unicode字符,如何转义它?
答:检查它的值。如果它在256到65535之间,则打印一个2字节(4个十六进制数字)转义序列。如果大于65535,则打印一个3字节(6个十六进制数字)转义序列。否则,按正常方式打印它。

问:给定一个UTF-32编码的字符串,如何分解为字符?
答:字符串的每个元素(称为代码单元)对应于一个单独的字符(代码点)。只需一个接一个地获取它们,不需要特殊处理。

问:给定一个UTF-16编码的字符串,如何分解为字符?
答:值(代码单元)位于0xD800到0xDFFF范围之外,对应于具有相同值的Unicode字符。对于每个这样的值,打印一个普通字符或一个2字节(4个十六进制数字)转义序列。在0xD800到0xDFFF范围内的值被分组成一对,每对表示U+10000到U+10FFFF范围中的一个单一字符(代码点)。对于这样的一对,打印一个3字节(6个十六进制数字)转义序列。要将一对(v1,v2)转换为其字符值,使用以下公式:

c = (v1 - 0xd800) >> 10 + (v2-0xdc00)

请注意,对于一个合法的pair来说,第一个元素必须在0xd800..0xdbff范围内,第二个元素必须在0xdc00..0xdfff范围内,否则该pair将是不合法的。

问:如何将一个UTF-8编码的字符串分解成字符?
答:UTF-8编码比UTF-16编码更为复杂,我这里不会详细说明。你可以在互联网上搜索到很多描述和示例实现。

问:我的L"प्रे"字符串怎么了?
答:它是一个由四个Unicode代码点组成的组合字符,分别是U+092A、U+094D、U+0930、U+0947。请注意,这与用代理对表示高代码点的情况不同,关于此,请参见答案中的UTF-16部分。这是“字符”与“代码点”不同的情况。请分别转义每个代码点。在这个抽象层次上,你正在处理代码点,而不是实际的字符。当你将它们显示给用户或者计算它们在打印文本中的位置时,字符才会发挥作用,但处理字符串编码时不会。


1
有没有简单的方法将包含"\u60A8\u597D\u4E16\u754C"的字符串转换为Unicode字符串并打印出来? - Zingam
@Zingam 我不知道什么是“Unicode字符串”。我的回答是否暗示了这样的事情? - n. m.
如果我写:“一系列Unicode字符”,那会更加技术上正确吗? 有没有简单的方法将Unicode字符转义序列“\u60A8\u597D\u4E16\u754C”转换为一系列未编码为字符转义序列的Unicode字符? 这个问题可以吗?谢谢! - Zingam
1
Unicode字符是数学抽象,序列也是如此。要将某些内容输出到物理文件/设备,您需要一个表示该抽象的编码字符串。您需要知道所需的编码才能开始。您的字符串已经是UTF-16编码的字符串。我建议您提出一个新问题,因为在评论中回答无法正常工作。 - n. m.
1
除非您指的是“\u…”(反斜杠后跟着字符u),否则您需要解析某种转义序列。 - n. m.
@Zingam 您可以将包含 Unicode 转义序列的 ASCII 字符串转换为 UTF8 并像这样在控制台上打印: python3 -c "print('\u60A8\u597D\u4E16\u754C')" 对我来说显示类似于:您好世界 如果您想在另一种语言(如 C 或 C++)中执行此操作,则需要查找或编写适当的解析器。 - Maximilian

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