我无法理解std::string和std::wstring之间的区别。我知道wstring支持Unicode字符等宽字符。我有以下问题:
什么时候应该使用std::wstring而不是std::string?std::string可以保存整个ASCII字符集,包括特殊字符吗?所有流行的C++编译器都支持std::wstring吗?什么是“宽字符”?
我无法理解std::string和std::wstring之间的区别。我知道wstring支持Unicode字符等宽字符。我有以下问题:
什么时候应该使用std::wstring而不是std::string?std::string可以保存整个ASCII字符集,包括特殊字符吗?所有流行的C++编译器都支持std::wstring吗?什么是“宽字符”?
当前回答
这里有一些非常好的答案,但我认为关于Windows/Visible Studio,我可以补充一些内容。这是基于我在VS2015的经验。在Linux上,基本上答案是到处使用UTF-8编码的std::string。在Windows/VS上,它变得更加复杂。原因如下。Windows希望使用区域设置代码页对使用字符存储的字符串进行编码。这几乎总是ASCII字符集,后跟128个其他特殊字符,具体取决于您的位置。让我声明,这不仅仅是在使用WindowsAPI时,还有三个主要的地方,这些字符串与标准C++交互。这些是字符串文本,使用<<输出到std::cout,并将文件名传递给std::fstream。
我会在这里表明我是一名程序员,而不是语言专家。我理解USC2和UTF-16并不相同,但出于我的目的,它们足够接近,可以互换,我在这里使用它们。我实际上不确定Windows使用的是什么,但我通常也不需要知道。我已经在这个答案中说明了UCS2,所以如果我对这件事的无知让任何人感到不安,我很抱歉,如果我有错,我很乐意改变它。
字符串常量
如果您输入的字符串文字仅包含代码页可以表示的字符,则VS将它们存储在文件中,每个字符编码1字节,基于代码页。请注意,如果您更改代码页或将源代码交给另一个使用不同代码页的开发人员,那么我认为(但尚未测试)角色最终会不同。如果您在计算机上使用不同的代码页运行代码,那么我不确定字符是否也会改变。
如果您输入任何不能由代码页表示的字符串文字,VS将要求您将文件保存为Unicode。然后文件将被编码为UTF-8。这意味着所有非ASCII字符(包括代码页上的字符)将由2个或更多字节表示。这意味着如果你把你的消息源给了其他人,那么消息源看起来会是一样的。然而,在将源代码传递给编译器之前,VS将UTF-8编码的文本转换为代码页编码的文本,代码页中缺少的任何字符都将替换为?。
确保在VS中正确表示Unicode字符串文字的唯一方法是在字符串文字之前加上L,使其成为宽字符串文字。在这种情况下,VS将文件中的UTF-8编码文本转换为UCS2。然后需要将这个字符串文本传递到std::wstring构造函数中,或者需要将其转换为utf-8并放入std::string中。或者,如果您愿意,您可以使用Windows API函数对其进行编码,使用代码页将其放入std::字符串中,但也可以不使用宽字符串文本。
标准::cout
当使用<<输出到控制台时,只能使用std::string,而不是std::wstring,并且必须使用区域设置代码页对文本进行编码。如果您有std::wstring,则必须使用一个Windows API函数转换它,并且代码页上没有的字符将被替换为?(也许你可以改变角色,我不记得了)。
std::fstream文件名
Windows操作系统使用UCS2/UTF-16作为文件名,因此无论您的代码页是什么,您都可以使用任何Unicode字符的文件。但这意味着要访问或创建包含不在代码页上的字符的文件,必须使用std::wstring。没有其他办法。这是对std::fstream的Microsoft特定扩展,因此可能不会在其他系统上编译。如果使用std::string,则只能使用代码页上只包含字符的文件名。
您的选项
如果你只是在Linux上工作,那么你可能还没有走到这一步。只需在任何地方使用UTF-8 std::string。
如果您只是在Windows上工作,请在任何地方使用UCS2 std::wstring。一些纯粹主义者可能会说,使用UTF8然后在需要时进行转换,但为什么要麻烦呢。
如果你是跨平台的,那么坦率地说是一团糟。如果您尝试在Windows上到处使用UTF-8,那么您需要非常小心地处理字符串文本和输出到控制台。你很容易在那里破坏你的字符串。如果您在Linux上到处使用std::wstring,那么您可能无法访问std::fstream的广泛版本,因此您必须进行转换,但没有损坏的风险。所以我个人认为这是一个更好的选择。很多人会不同意,但我并不孤单——比如wxWidgets就走了这条路。
另一个选项可以是在Linux上将unicodestring类型定义为std::string,在Windows上将std::wstring,并有一个名为UNI()的宏,该宏在Windows上前缀为L,在Linux上前缀为nothing,然后代码
#include <fstream>
#include <string>
#include <iostream>
#include <Windows.h>
#ifdef _WIN32
typedef std::wstring unicodestring;
#define UNI(text) L ## text
std::string formatForConsole(const unicodestring &str)
{
std::string result;
//Call WideCharToMultiByte to do the conversion
return result;
}
#else
typedef std::string unicodestring;
#define UNI(text) text
std::string formatForConsole(const unicodestring &str)
{
return str;
}
#endif
int main()
{
unicodestring fileName(UNI("fileName"));
std::ofstream fout;
fout.open(fileName);
std::cout << formatForConsole(fileName) << std::endl;
return 0;
}
我想两个平台都可以。
答案
所以回答你的问题
1) 如果您一直在为Windows编程,如果是跨平台编程,那么可能一直都是这样,除非您想在Windows上处理可能的损坏问题,或者使用特定于平台的#ifdefs编写一些代码来解决这些差异,如果只是使用Linux,那么永远都不会。
2) 是的。此外,在Linux上,您也可以将其用于所有Unicode。在Windows上,如果选择使用UTF-8手动编码,则只能将其用于所有unicode。但是WindowsAPI和标准C++类将期望使用语言环境代码页对std::字符串进行编码。这包括所有ASCII码加上其他128个字符,这些字符根据计算机设置使用的代码页而变化。
3) 我相信是这样,但如果不是,那么它只是使用wchar_t而不是char的“std::basic_string”的简单typedef
4) 宽字符是大于1字节标准字符类型的字符类型。在Windows上是2字节,在Linux上是4字节。
其他回答
要存储“宽”(Unicode)字符时。是:其中255个(不包括0个)。对这是一篇介绍性文章:http://www.joelonsoftware.com/articles/Unicode.html
当您希望使用Unicode字符串而不仅仅是ascii时,这有助于国际化是的,但它与0的配合不好没有意识到宽字符是编译器特有的处理unicode字符固定长度表示的方式,对于MSVC,它是2字节字符,对于gcc,我理解它是4字节。以及+1表示http://www.joelonsoftware.com/articles/Unicode.html
如果应用程序不满足256个不同的字符,则可以选择使用宽字符(超过8位)或可变长度编码(C++术语中的多字节编码),如UTF-8。宽字符通常比可变长度编码需要更多的空间,但处理速度更快。处理大量文本的多语言应用程序通常在处理文本时使用宽字符,但在将文本存储到磁盘时将其转换为UTF-8。
字符串和wstring之间的唯一区别是它们存储的字符的数据类型。字符串存储的字符大小保证至少为8位,因此您可以使用字符串来处理例如ASCII、ISO-8859-15或UTF-8文本。该标准没有说明字符集或编码。
实际上,每个编译器都使用一个字符集,其前128个字符与ASCII对应。使用UTF-8编码的编译器也是如此。在UTF-8或其他可变长度编码中使用字符串时,需要注意的重要一点是,索引和长度是以字节而不是字符来度量的。
wstring的数据类型是wchar_t,其大小在标准中没有定义,除了它必须至少与一个字符一样大,通常是16位或32位。wstring可用于处理实现定义的宽字符编码中的文本。因为标准中没有定义编码,所以在字符串和wstring之间进行转换并不简单。也不能假设wstring具有固定长度编码。
如果您不需要多语言支持,那么可以只使用常规字符串。另一方面,如果您正在编写图形应用程序,则API通常只支持宽字符。然后,您可能希望在处理文本时使用相同的宽字符。请记住,UTF-16是一种可变长度编码,这意味着您不能假定length()返回字符数。如果API使用固定长度编码,例如UCS-2,则处理变得容易。在宽字符和UTF-8之间进行转换很难以可移植的方式进行,但话说回来,您的用户界面API可能支持这种转换。
所以,现在在座的每一位读者都应该清楚地了解事实和情况。如果没有,那么你必须阅读帕塞巴尔非常全面的回答[顺便说一句:谢谢!]。
我的务实结论非常简单:所有C++(和STL)“字符编码”的东西基本上都是残缺不全的。不管是否归咎于微软,这都无济于事。
经过深入调查后,我的解决方案是:
接受,你必须自己负责编码和转换的事情(你会发现很多事情都很琐碎)对任何UTF-8编码字符串使用std::string(仅为typedef std::字符串UTF8String)接受这样一个UTF8String对象只是一个愚蠢但廉价的容器。永远不要直接访问和/或操作其中的字符(不要搜索、替换等)。你可以,但你真的不想浪费时间为多字节字符串编写文本操作算法!即使其他人已经做了如此愚蠢的事情,也不要这样做!顺其自然!(好吧,在某些情况下,这是合理的……只需使用ICU图书馆即可)。对UCS-2编码字符串使用std::wstring(typedef std::wstring UCS2String)-这是一种妥协,也是对WIN32 API引入的混乱的让步)。UCS-2对我们大多数人来说已经足够了(稍后将详细介绍…)。每当需要逐字符访问(读取、操作等)时,请使用UCS2String实例。任何基于字符的处理都应该在非多字节表示中完成。它简单、快速、容易。添加两个实用函数以在UTF-8和UCS-2之间来回转换:UCS2字符串转换为UCS2(const UTF8String&str);UTF8字符串转换为UTF8(常量UCS2String&str);
转换很简单,谷歌应该在这里提供帮助。。。
就是这样。在内存非常宝贵的地方以及所有UTF-8 I/O都使用UTF8String。在必须解析和/或操作字符串的地方使用UCS2String。您可以随时在这两种表示之间进行转换。
替代方案和改进
从&到单字节字符编码(例如ISO-8859-1)的转换可以借助于普通转换表来实现,例如const wchar_ttt_iso88951[256]={0,1,2,…};以及用于转换到UCS2和从UCS2转换的适当代码。如果UCS-2不够,则切换到UCS-4(typedef std::basic_string<uint32_t>UCS2String)
ICU或其他unicode库?
先进的东西。
这里有一些非常好的答案,但我认为关于Windows/Visible Studio,我可以补充一些内容。这是基于我在VS2015的经验。在Linux上,基本上答案是到处使用UTF-8编码的std::string。在Windows/VS上,它变得更加复杂。原因如下。Windows希望使用区域设置代码页对使用字符存储的字符串进行编码。这几乎总是ASCII字符集,后跟128个其他特殊字符,具体取决于您的位置。让我声明,这不仅仅是在使用WindowsAPI时,还有三个主要的地方,这些字符串与标准C++交互。这些是字符串文本,使用<<输出到std::cout,并将文件名传递给std::fstream。
我会在这里表明我是一名程序员,而不是语言专家。我理解USC2和UTF-16并不相同,但出于我的目的,它们足够接近,可以互换,我在这里使用它们。我实际上不确定Windows使用的是什么,但我通常也不需要知道。我已经在这个答案中说明了UCS2,所以如果我对这件事的无知让任何人感到不安,我很抱歉,如果我有错,我很乐意改变它。
字符串常量
如果您输入的字符串文字仅包含代码页可以表示的字符,则VS将它们存储在文件中,每个字符编码1字节,基于代码页。请注意,如果您更改代码页或将源代码交给另一个使用不同代码页的开发人员,那么我认为(但尚未测试)角色最终会不同。如果您在计算机上使用不同的代码页运行代码,那么我不确定字符是否也会改变。
如果您输入任何不能由代码页表示的字符串文字,VS将要求您将文件保存为Unicode。然后文件将被编码为UTF-8。这意味着所有非ASCII字符(包括代码页上的字符)将由2个或更多字节表示。这意味着如果你把你的消息源给了其他人,那么消息源看起来会是一样的。然而,在将源代码传递给编译器之前,VS将UTF-8编码的文本转换为代码页编码的文本,代码页中缺少的任何字符都将替换为?。
确保在VS中正确表示Unicode字符串文字的唯一方法是在字符串文字之前加上L,使其成为宽字符串文字。在这种情况下,VS将文件中的UTF-8编码文本转换为UCS2。然后需要将这个字符串文本传递到std::wstring构造函数中,或者需要将其转换为utf-8并放入std::string中。或者,如果您愿意,您可以使用Windows API函数对其进行编码,使用代码页将其放入std::字符串中,但也可以不使用宽字符串文本。
标准::cout
当使用<<输出到控制台时,只能使用std::string,而不是std::wstring,并且必须使用区域设置代码页对文本进行编码。如果您有std::wstring,则必须使用一个Windows API函数转换它,并且代码页上没有的字符将被替换为?(也许你可以改变角色,我不记得了)。
std::fstream文件名
Windows操作系统使用UCS2/UTF-16作为文件名,因此无论您的代码页是什么,您都可以使用任何Unicode字符的文件。但这意味着要访问或创建包含不在代码页上的字符的文件,必须使用std::wstring。没有其他办法。这是对std::fstream的Microsoft特定扩展,因此可能不会在其他系统上编译。如果使用std::string,则只能使用代码页上只包含字符的文件名。
您的选项
如果你只是在Linux上工作,那么你可能还没有走到这一步。只需在任何地方使用UTF-8 std::string。
如果您只是在Windows上工作,请在任何地方使用UCS2 std::wstring。一些纯粹主义者可能会说,使用UTF8然后在需要时进行转换,但为什么要麻烦呢。
如果你是跨平台的,那么坦率地说是一团糟。如果您尝试在Windows上到处使用UTF-8,那么您需要非常小心地处理字符串文本和输出到控制台。你很容易在那里破坏你的字符串。如果您在Linux上到处使用std::wstring,那么您可能无法访问std::fstream的广泛版本,因此您必须进行转换,但没有损坏的风险。所以我个人认为这是一个更好的选择。很多人会不同意,但我并不孤单——比如wxWidgets就走了这条路。
另一个选项可以是在Linux上将unicodestring类型定义为std::string,在Windows上将std::wstring,并有一个名为UNI()的宏,该宏在Windows上前缀为L,在Linux上前缀为nothing,然后代码
#include <fstream>
#include <string>
#include <iostream>
#include <Windows.h>
#ifdef _WIN32
typedef std::wstring unicodestring;
#define UNI(text) L ## text
std::string formatForConsole(const unicodestring &str)
{
std::string result;
//Call WideCharToMultiByte to do the conversion
return result;
}
#else
typedef std::string unicodestring;
#define UNI(text) text
std::string formatForConsole(const unicodestring &str)
{
return str;
}
#endif
int main()
{
unicodestring fileName(UNI("fileName"));
std::ofstream fout;
fout.open(fileName);
std::cout << formatForConsole(fileName) << std::endl;
return 0;
}
我想两个平台都可以。
答案
所以回答你的问题
1) 如果您一直在为Windows编程,如果是跨平台编程,那么可能一直都是这样,除非您想在Windows上处理可能的损坏问题,或者使用特定于平台的#ifdefs编写一些代码来解决这些差异,如果只是使用Linux,那么永远都不会。
2) 是的。此外,在Linux上,您也可以将其用于所有Unicode。在Windows上,如果选择使用UTF-8手动编码,则只能将其用于所有unicode。但是WindowsAPI和标准C++类将期望使用语言环境代码页对std::字符串进行编码。这包括所有ASCII码加上其他128个字符,这些字符根据计算机设置使用的代码页而变化。
3) 我相信是这样,但如果不是,那么它只是使用wchar_t而不是char的“std::basic_string”的简单typedef
4) 宽字符是大于1字节标准字符类型的字符类型。在Windows上是2字节,在Linux上是4字节。