【发布时间】:2017-07-25 14:16:22
【问题描述】:
尽管在论坛上看到很多关于 unicode 和字符串转换(在 C/C++ 中)的问题,并且在该主题上搜索了几个小时,但我仍然无法找到对我来说似乎是一个非常基本的过程的直接解释.这是我想做的:
我有一个字符串,它可能使用任何可能语言的任何字符。让我们以西里尔文为例。所以说我有:
std::string str = "сапоги";-
我想遍历组成该字符串的每个字符并且:
- 知道/打印字符的 Unicode 值
- 将该 Unicode 值转换为十进制值
我真的在谷歌上搜索了好几个小时,却找不到一个直接的答案。如果有人能告诉我如何做到这一点,那就太好了。
编辑
所以我设法做到了:
#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <locale>
#include <codecvt>
#include <iomanip>
// utility function for output
void hex_print(const std::string& s)
{
std::cout << std::hex << std::setfill('0');
for(unsigned char c : s)
std::cout << std::setw(2) << static_cast<int>(c) << ' ';
std::cout << std::dec << '\n';
}
int main()
{
std::wstring test = L"сапоги";
std::wstring_convert<std::codecvt_utf16<wchar_t>> conv1;
std::string u8str = conv1.to_bytes(test);
hex_print(u8str);
return 1;
}
结果:
04 41 04 30 04 3f 04 3e 04 33 04 38
哪个是正确的(它映射到 unicode)。问题是我不知道我是否应该使用 utf-8、16 或其他东西(正如 Chris 在评论中指出的那样)。有没有办法让我知道呢? (无论它最初使用什么编码或需要使用什么编码?)
编辑 2
我想我会通过第二次编辑来解决一些 cmets:
“将 Unicode 值转换为十进制值” 为什么?
我会解释原因,但我也想以友好的方式发表评论,我的问题不是“为什么”而是“如何”;-)。您可以假设 OP 有理由提出这个问题,但当然,我理解人们对为什么感到好奇......所以让我解释一下。我需要这一切的原因是因为我最终需要从字体文件中读取字形(TrueType OpenType 无关紧要)。碰巧这些文件有一个名为cmap 的表,它是某种关联数组,将字符的值(以代码点的形式)映射到字体文件中字形的索引。表中的代码点不是使用符号 U+XXXX 定义的,而是直接在该数字的十进制对应物中定义的(假设 U+XXXX 符号是 uint16 数字的十六进制表示[或 U+XXXXXX,如果大于 uint16 但更多稍后再说])。因此,总而言之,西里尔字母 ([gueu]) 中的字母 г 具有代码点值 U+0433,十进制形式为 1075。我需要值 1075 在 cmap 表中进行查找。
// utility function for output
void hex_print(const std::string& s)
{
std::cout << std::hex << std::setfill('0');
uint16_t i = 0, dec;
for(unsigned char c : s) {
std::cout << std::setw(2) << static_cast<int>(c) << ' ';
dec = (i++ % 2 == 0) ? (c << 8) : (dec | c);
printf("Unicode Value: U+%04x Decimal value of code point: %d\n", codePoint, codePoint);
}
}
std::string 与编码无关。它本质上存储字节。 std::wstring 很奇怪,虽然也没有定义为保存任何特定的编码。在 Windows 中,wchar_t 用于 UTF-16
是的,我认为当你理解“虽然”你认为(至少我是这样认为)字符串只是存储“ASCII”字符时(坚持),这似乎是错误的。事实上,评论所建议的 std::string 似乎只存储“字节”。虽然很明显,如果您查看字符串 english 的字节,您会得到:
std::string eng = "english";
hex_print(eng);
65 6e 67 6c 69 73 68
如果你用“сапоги”做同样的事情,你会得到:
std::string cyrillic = "сапоги";
hex_print(cyrillic );
d1 81 d0 b0 d0 bf d0 be d0 b3 d0 b8
我真正想知道/理解的是这种转换是如何隐式完成的?为什么在这里使用 UTF-8 编码而不是 UTF-16,是否有可能改变它(或者是由我的 IDE 或操作系统定义的?)?显然,当我在文本编辑器中复制粘贴字符串 сапоги 时,它实际上已经复制了一个 12 个字节的数组(这 12 个字节可能是 utf-8 或 utf-16)。
我认为 Unicode 和编码之间存在混淆。代码点(AFAIK)只是一个字符代码。 UTF 16 为您提供代码,因此您可以说您的 0x0441 是西里尔小写字母 es 的 с 代码点。据我了解,UTF16 与 Unicode 代码点一对一地映射,Unicode 代码点的范围为 1M 和一些字符。但是,其他编码技术,例如 UTF-8 并不直接映射到 Unicode 代码点。所以,我想,你最好坚持使用 UTF-16
没错!我发现这条评论确实非常有用。因为是的,关于你对 Unicode 代码点值的编码方式与 Unicode 值本身无关这一事实存在混淆(我很困惑),这有点像因为事实上事情可能会像我一样误导现在显示。 You can indeed encode the string сапоги using UTF8 and you will get:
d1 81 d0 b0 d0 bf d0 be d0 b3 d0 b8
很明显,它确实与字形的 Unicode 值无关。现在,如果您使用 UTF16 编码相同的字符串,您会得到:
04 41 04 30 04 3f 04 3e 04 33 04 38
其中 04 和 41 确实是字母 с(西里尔字母 [se])的两个字节(十六进制形式)。至少在这种情况下,unicode 值与其 uint16 表示之间存在直接映射。这就是为什么(根据维基的解释 [source]):
UTF-16 和 UCS-2 都将此范围内的代码点编码为单个 16 位代码单元,在数字上等于相应的代码点。
但正如评论中有人建议的那样,某些代码点值超出了您可以用 2 个字节定义的值。例如:
1D307 ????完整圆的 TETRAGRAM (Tai Xuan Jing Symbols)
这就是这条评论所暗示的:
据我所知,UTF-16 不会涵盖所有字符,除非您使用代理对。本来是打算的,当 65k 绰绰有余的时候,结果就跑偏了,现在是一个非常尴尬的选择
虽然是完全精确的 UTF-16,如 UTF-8 CAN 编码 ALL 字符,但它最多可以使用 4 个字节(正如你所建议的那样如果需要超过 2 个字节,请使用代理对)。
我尝试使用 mbrtoc32 转换为 UTF-32,但在 Mac 上奇怪地缺少 cuchar。
顺便说一句,如果你不知道surrogate pair 是什么(我不知道),那么a nice post about this on the forum。
【问题讨论】:
-
你想使用类似
std::string str = L"сапоги"的东西吗? -
我不知道。我的目标是找到组成字符串的每个字符的 unicode 值并将其转换为十进制值。
-
请提供minimal reproducible example。你现在的例子我太模糊了。
-
您需要知道字符串的编码(例如,UTF-8),然后最好找到一个允许您遍历代码点的库。