有一个UTF-8编码的字符串,我可以从文件中读取它并将其写入另一个文件就好了。但是当我尝试逐个加载该字符串中的每个字符时,结果并不是连贯的。我很可能以非常错误的方式做这件事,但这样做的正确方法是什么?
source.txt
中的内容
afternoon_gb_1 ɑftənun
我写的代码是
while (source >> word >> word_ipa) {
for (char& c : word_ipa)
myfile <<word<<" is " << c<< endl;}
txt文件myfile
中的内容被写为
afternoon_gb_1 is �
afternoon_gb_1 is �
afternoon_gb_1 is f
afternoon_gb_1 is t
afternoon_gb_1 is �
afternoon_gb_1 is �
afternoon_gb_1 is n
afternoon_gb_1 is u
afternoon_gb_1 is n
答案 0 :(得分:4)
在UTF-8中,每个代码点(=逻辑字符)由多个代码单元(= char
)表示; ɑftənun,特别是:
ch| c.p. | c.u.
--+------+-------
ɑ | 0251 | c9 91
f | 0066 | 66
t | 0074 | 74
ə | 0259 | c9 99
n | 006e | 6e
u | 0075 | 75
n | 006e | 6e
(ch =字符; c.p:代码点编号; UTF-8中的c.p.代码单位表示; c.u。和c.p.以十六进制表示)
in many places解释了代码点如何映射到代码单元的确切细节。非常基本的是:
如果您自己打印出每个代码单元,那么您将破坏需要表达多个代码单元的代码点的UTF-8编码。第一行中的终端应用程序可以看到
c9 0a
(第一个代码单元后面跟一个换行符),并立即检测到这是一个破坏的UTF-8序列,因为c9设置了高位但下一个c.u.没有它;因此 角色。同样适用于第二个角色以及c.u.序列的一部分代表ə。
现在,如果您要打印完整的代码点(不是代码单元),std::string
将没有任何帮助 - std::string
一无所知这个东西,它本质上是一个荣耀的std::vector<char>
,完全没有编码问题;它只是存储/索引代码单元,而不是代码点。
然而,有第三方图书馆帮助解决这个问题; utf8-cpp是一个小但完整的;在您的情况下,utf8::next
函数将特别有用:
while (source >> word >> word_ipa) {
auto cur = word_ipa.begin();
auto end = word_ipa.end();
auto next = cur;
for(;cur!=end; cur=next) {
utf8::next(next, end);
myfile << word << "is ";
for(; cur!=next; ++cur) myfile<<*cur;
myfile << "\n";
}
}
utf8::next
这里只增加给定的迭代器,使其指向启动下一个代码单元的代码点;此代码确保我们将组成单个代码点的所有代码单元一起打印。
请注意,我们可以非常简单地重现其准系统行为,这只是阅读UTF-8规范的问题(请参阅上面维基百科链接中的第一个表):
template<typename ItT>
void safe_advance(ItT &it, size_t n, ItT end) {
size_t d = std::distance(it, end);
if(n>d) throw std::logic_error("Truncated UTF-8 sequence");
std::advance(it, n);
}
template<typename ItT>
void my_next(ItT &it, ItT end) {
uint8_t b = *it;
if(b>>7 == 0) safe_advance(it, 1, end);
else if(b>>5 == 6) safe_advance(it, 2, end);
else if(b>>4 == 14) safe_advance(it, 3, end);
else if(b>>3 == 30) safe_advance(it, 4, end);
else throw std::logic_error("Invalid UTF-8 sequence");
}
这里我们利用一个事实,即序列的第一个字节声明要完成代码单元的额外代码点数。
(请注意,这需要有效的UTF-8,并且不会尝试重新同步损坏的UTF-8序列;在这方面,库版本可能会更好一些)
OTOH,也可以内联保持相同代码单元的必要内容:
while (source >> word >> word_ipa) {
auto cur = word_ipa.begin();
auto end = word_ipa.end();
for(;cur!=end;) {
myfile << word << "is "<<*cur;
if(uint8_t(*cur++)>>7 != 0) {
for(; cur!=end && (uint8_t(*cur)>>6)==2; ++cur) myfile<<*cur;
}
myfile << "\n";
}
}
这里我们完全忽略了第一个c.u中的“声明计数”,我们只检查是否设置了高位;在这种情况下,只要我们得到c.u,我们就继续打印。最高的两个字节设置为10(二进制,十进制的AKA 2) - 自“继续c.u.”多c.u。 UTF-8序列都遵循这种模式。