Unicode和std::string在C++中的含义是什么?

11

如果我在C++中写入一些Unicode字符组成的随机字符串到文件中,我的文本编辑器会告诉我我没有创建一个有效的UTF-8文件。

// Code example
const std::string charset = "abcdefgàèíüŷÀ";
file << random_string(charset); // using std::fstream

我该怎么解决这个问题?我需要做很多额外的手动编码吗?我的理解是,std::string不关心编码,只关心字节,所以当我传递一个unicode字符串并将其写入文件时,那个文件应该包含相同的字节,并被识别为UTF-8编码的文件,对吗?


3
你正在看 std::wstring 吗? - Chubsdad
@Charles:这就像是我一样 :) 但我怀疑不会,因为std::string构造函数会丢弃字符串字面值中的空字符,并且random_string函数只是从字符集字符串中随机选择一个字符。 - user1481860
1
std::string并不一定会从字符串字面量中丢弃空字符。通常,它内部将字符串表示为以空字符结尾的C字符串,以便轻松实现std::string::c_str()函数。 - Charles Salvia
我进行了确认,charset[charset.length() - 1] 不会返回 null,这意味着我的函数不应该输出 null。 - user1481860
1
字符:'àèíüŷÀ' 不是 UTF-8 编码。请注意,UTF-8 是一种多字节字符集。这意味着 charset[x] 不能保证获取到一个完整的字符,因为它可能跨越多个字符分割。 - Martin York
显示剩余4条评论
4个回答

14
"random_string很可能是罪魁祸首;我想知道它是如何实现的。如果您的字符串确实是UTF-8编码的,并且random_string看起来像"
std::string random_string(std::string const &charset)
{
    const int N = 10;
    std::string result(N);
    for (int i=0; i<N; i++)
        result[i] = charset[rand() % charset.size()];
    return result;
}

然后它将从字符集charset中随机选择字符,UTF-8(正如其他帖子所指出的)中这些字符不是Unicode代码点,而是简单的字节。如果它选择了UTF-8多字节字符中间的一个随机字节作为第一个字节(或者将其放在一个7位ASCII兼容字符之后),那么你的输出就不会是有效的UTF-8。请参见WikipediaRFC 3629

解决方案可能是在random_string中进行UTF-32的transform。我相信在Linux上wchar_tstd::wstring使用UTF-32。UTF-16也是安全的,只要你保持在Basic Multilingual Plane内。


1
如果一个名为“str”的std::string包含“àỳ”,那么str [0]不会返回“à”吗? str [1]不会返回“ỳ”吗? - user1481860
3
不,它将返回这些字符中多字节编码的第一个字节。C++是20世纪80年代发明的,旨在与C语言(20世纪70年代)和ASCII(20世纪60年代)兼容,而Unicode和UTF-8则于90年代初引入。UTF-8旨在保持大多数旧程序和算法的工作正常,看起来你使用了其中一个会出错的算法。如果 random_string 的功能大致是这样的话。 - Fred Foo
没错。我猜这意味着每当我想操作一个Unicode字符串时,我必须使用wstring。我会阅读相关的可移植性问题等内容。无论如何,答案已被接受。 - user1481860
更正之前的评论:str[1]将返回à编码中的第二个字节。 - Fred Foo
@dan:wstring 如何“使用”UTF-16? - user1481860
显示剩余6条评论

11
你需要做什么来解决这个问题?需要手动编码吗?据我所知,std::string不关心编码,只关心字节,因此当你传递一个Unicode字符串并将其写入文件时,该文件应该包含相同的字节,并被识别为UTF-8编码的文件。您是正确的,std::string是编码无关的,它只是保存char元素的数组。如何将这些char元素解释为文本取决于环境。如果您的区域设置未设置为某种形式的Unicode(即UTF-8或UTF-16),则当您输出字符串时,它将不会显示/解释为Unicode。您确定您的字符串文字"abcdefgàèíüŷÀ"实际上是Unicode而不是Latin-1(ISO-8859-1或可能是Windows-1252)吗?您需要确定您的平台当前配置使用的区域设置。
我认为我知道你的问题所在:在你的charset字符串文字中,有些Unicode字符,比如重音符号"À",是双字节字符(假设使用UTF-8编码)。当你在random_string函数中使用[]运算符访问字符集字符串时,你会返回一个Unicode字符的一半。因此,random_string函数创建了一个无效的字符字符串。
例如,考虑以下代码:
std::string s = "À";
std::cout << s.length() << std::endl;

在将字符串字面量解释为UTF-8的环境中,此程序将输出2。因此,字符串的第一个字符(s[0])只有一个Unicode字符的一半,因此无效。由于您的random_string函数使用[]运算符通过单个字节寻址字符串,因此您正在创建无效的随机字符串。

所以,是的,您需要使用std::wstring,并使用L前缀创建您的字符集字符串字面量。


这可能是问题所在,因为此前我已经能够将一个使用 UTF-8 编码的 Unicode 字符串从文件中读取到 std::string 中,然后输出到另一个文件。我会仔细研究一下。 - user1481860
这正是我说你不能在std::string中存储多字节编码的原因。但出于某种原因,我被投票踩到了无人问津。 - Šimon Tóth
@Let_Me_Be,因为您可以std::string中存储多字节编码。就像我在上面的示例中所做的那样。但是,您无法使用[]运算符访问字符串中的单个多字节字符。 - Charles Salvia
@Charles 是的,我可以使用链表进行随机访问。 - Šimon Tóth
@Let_Me_Be,好的,我没有给你投反对票。但是无论如何,你建议使用std::vector<char>会导致相同的问题。你无法处理单个完整的多字节字符。 - Charles Salvia
@Charles 是的,但与 std::string 不同,std::vector 旨在存储原始数据。 - Šimon Tóth

1
在您的代码示例中,std::string charset 存储了您所写的内容。也就是说,如果您使用 UTF-8 文本编辑器编写此内容,则在文件输出时将会得到完全相同的 UTF-8 文本。
UTF-8 只是一种编码方案,其中不同的字符使用不同的字节大小。但是,如果您使用 UTF-8 编辑器,它将使用两个字节对 'ñ' 进行编码,并且当您将其写入文件时,它将具有这两个字节(再次符合 UTF-8)。
问题可能是您用于创建源 C++ 文件的编辑器。它可能使用 latin1 或其他编码方式。

0
要编写UTF-8,您需要使用像这个这样的codecvt facet。可以在这里看到如何使用它的示例。

2
这些用于将wchar_t(UTF-16 / UTF-32)转换为UTF-8。由于字符串已经是UTF-8,因此不需要进行转换。 - Martin York
@Martin:不能保证字符串是UTF-8编码。如果源文件使用代码页437保存,字符“à”将是一个值为133的单字节。(在Unicode中,“à”由代码点U+00E0表示,UTF-8将其编码为字节序列[0xc3, 0xa0]。) - Marcelo Cantos

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