std::wstring vs std::string

我无法理解std::stringstd::wstring之间的区别。我知道wstring支持宽字符,例如Unicode字符。我有以下问题:

  1. 什么时候应该使用std::wstring而不是std::string
  2. std::string可以保存整个ASCII字符集,包括特殊字符吗?
  3. std::wstring是否被所有流行的C++编译器支持?
  4. 什么是“宽字符”?
389228 次浏览
  1. 当您想存储“宽”(Unicode)字符时。
  2. 有:255个(不包括0个)。
  3. 是滴
  4. 这是一篇介绍性文章:http://www.joelonsoftware.com/articles/Unicode.html
  1. 当你想使用Unicode字符串而不仅仅是ascii时,有助于国际化
  2. 是的,但它与0不起作用
  3. 不知道任何不知道的
  4. 宽字符是编译器处理Unicode字符固定长度表示的特定方式,对于MSVC它是一个2字节的字符,对于gcc我知道它是4字节。http://www.joelonsoftware.com/articles/Unicode.html的+1

1)正如Greg提到的,wstring有助于国际化,那时你将以英语以外的语言发布产品

4)检查这个宽字符http://en.wikipedia.org/wiki/Wide_character

我经常使用std::字符串来保存utf-8字符,没有任何问题。我衷心建议在与使用utf-8作为本机字符串类型的API接口时这样做。

例如,当我的代码与Tcl解释器接口时,我使用utf-8。

主要的警告是std::字符串的长度,不再是字符串中的字符数。

  1. 当你想要在字符串中存储宽字符时。wide取决于实现。如果我没记错的话,视觉C++默认为16位,而GCC默认取决于目标。这里有32位长。请注意wchar_t(宽字符类型)与Unicode无关。它只是保证它可以存储实现所在语言环境支持的最大字符集的所有成员,并且至少与char一样长。您也可以使用utf-8编码将商店 Unicode精细字符串转换为std::string。但它不会理解Unicode代码点的含义。因此,str.size()不会给出字符串中逻辑字符的数量,而只是存储在字符串/wstring中的char或wchar_t元素的数量。因此,gtk/glibC++包装器人员开发了一个Glib::ustring类,可以处理utf-8。

    如果您的wchar_t是32位长,那么您可以使用utf-32作为Unicode编码,并且您可以使用固定(utf-32是固定长度)编码来存储处理Unicode字符串。这意味着您的wstring的s.size()函数将然后返回适量的wchar_t元素逻辑字符。

  2. 是的,char总是至少8位长,这意味着它可以存储所有ASCII值。
  3. 是的,所有主要的编译器都支持它。

stringwstring

std::stringchar上的basic_string模板,wchar_t上的std::wstring模板。

char vs.wchar_t

char应该包含一个字符,通常是一个8位字符。wchar_t应该包含一个宽字符,然后,事情变得棘手:在Linux上,wchar_t是4个字节,而在Windows上,它是2个字节。

那么Unicode呢?

问题是charwchar_t都没有直接绑定到Unicode。

Linux?

让我们Linux操作系统:我的Ubuntu系统已经支持Unicode。当我使用char字符串时,它被本机编码为UTF-8(即Unicode字符字符串)。以下代码:

#include <cstring>#include <iostream>
int main(){const char text[] = "olé";

std::cout << "sizeof(char)    : " << sizeof(char) << "\n";std::cout << "text            : " << text << "\n";std::cout << "sizeof(text)    : " << sizeof(text) << "\n";std::cout << "strlen(text)    : " << strlen(text) << "\n";
std::cout << "text(ordinals)  :";
for(size_t i = 0, iMax = strlen(text); i < iMax; ++i){unsigned char c = static_cast<unsigned_char>(text[i]);std::cout << " " << static_cast<unsigned int>(c);}
std::cout << "\n\n";
// - - -
const wchar_t wtext[] = L"olé" ;
std::cout << "sizeof(wchar_t) : " << sizeof(wchar_t) << "\n";//std::cout << "wtext           : " << wtext << "\n"; <- errorstd::cout << "wtext           : UNABLE TO CONVERT NATIVELY." << "\n";std::wcout << L"wtext           : " << wtext << "\n";
std::cout << "sizeof(wtext)   : " << sizeof(wtext) << "\n";std::cout << "wcslen(wtext)   : " << wcslen(wtext) << "\n";
std::cout << "wtext(ordinals) :";
for(size_t i = 0, iMax = wcslen(wtext); i < iMax; ++i){unsigned short wc = static_cast<unsigned short>(wtext[i]);std::cout << " " << static_cast<unsigned int>(wc);}
std::cout << "\n\n";}

输出以下文本:

sizeof(char)    : 1text            : olésizeof(text)    : 5strlen(text)    : 4text(ordinals)  : 111 108 195 169
sizeof(wchar_t) : 4wtext           : UNABLE TO CONVERT NATIVELY.wtext           : ol�sizeof(wtext)   : 16wcslen(wtext)   : 3wtext(ordinals) : 111 108 233

您将看到char中的“olé”文本实际上是由四个字符构造的:110、108、195和169(不包括尾随零)。(我将让您学习wchar_t代码作为练习)

因此,在Linux上使用char时,您通常会在不知情的情况下使用Unicode。由于std::stringchar一起使用,因此std::string已经可以使用Unicode了。

请注意,std::string与C字符串API一样,会认为“olé”字符串有4个字符,而不是3个字符。因此,在截断/播放Unicode字符时应该谨慎,因为UTF-8中禁止某些字符组合。

在Windows上?

在Windows上,这有点不同。在Unicode出现之前,Win32必须支持大量使用char和世界各地生产的不同字符集/代码页的应用程序。

所以他们的解决方案很有趣:如果一个应用程序使用char,那么char字符串使用机器上的本地字符集/代码页被编码/打印/显示在GUI标签上,不能在很长一段时间内是UTF-8。例如,“olé”在法语本地化的Windows中会是“olé”,但在西里尔语本地化的Windows上会有所不同(如果你使用Windows-1251,则是“olé”)。因此,“历史应用程序”通常仍然以同样的方式工作。

对于基于Unicode的应用程序,Windows使用wchar_t,它的宽度为2字节,编码为UTF-16,它是Unicode编码的2字节字符(或者至少是UCS-2,它只是缺少代理对,因此缺少BMP之外的字符(>=64K))。

使用char的应用程序被称为“多字节”(因为每个字形由一个或多个char组成),而使用wchar_t的应用程序被称为“widechar”(因为每个字形由一个或两个wchar_t组成。有关更多信息,请参阅多字节宽字符实现宽字符转多字节宽字符转多字节宽字符转多字节宽字符 Win32转换API。

因此,如果您在Windows上工作,您将使用wchar_t(除非您使用隐藏它的框架,例如GTKQT…)。事实是,在幕后,Windows使用wchar_t字符串,因此即使是历史应用程序在使用API时也会将其char字符串转换为wchar_t,例如SetWindowText()(在Win32 GUI上设置标签的低级API函数)。

记忆问题?

UTF-32是每个字符4个字节,所以没有什么可添加的,只要UTF-8文本和UTF-16文本总是比UTF-32文本使用更少或相同数量的内存(通常更少)。

如果有内存问题,那么您应该知道,与大多数西方语言相比,UTF-8文本将比相同的UTF-16文本使用更少的内存。

尽管如此,对于其他语言(中文、日文等),UTF-8使用的内存要么相同,要么比UTF-16稍大。

总而言之,UTF-16将主要使用每个字符2个字节,偶尔使用4个字节(除非您正在处理某种深奥的语言字形(Klingon?Elvish?),而UTF-8将花费1到4个字节。

查看https://en.wikipedia.org/wiki/UTF-8#Compared_to_UTF-16了解更多信息。

结论

  1. 当我应该使用std::wstring在std::字符串?

    Linux?几乎从来没有(§)。在Windows上?几乎总是(§)。关于跨平台代码?取决于您的工具包…

    (§):除非您使用另一种工具包/框架

  2. #0可以保存所有的ASCII字符集,包括特殊字符吗?

    注意:std::string适合保存“二进制”缓冲区,而std::wstring则不适合!

    Linux?是的。在Windows上?只有特殊字符可用于Windows用户的当前区域设置。

    编辑(在JohannGerell的评论之后):std::string足以处理所有基于char的字符串(每个char是从0到255的数字)。但是:

    1. ASCII应该从0到127。较高的char不是ASCII。
    2. 从0到127的char将被正确持有
    3. 从128到255的char将具有取决于您的编码(Unicode、非Unicode等)的含义,但只要它们以UTF-8编码,它将能够保存所有Unicode字形。
  3. 几乎所有流行的C++编译器都支持#0吗?

    大多数情况下,除了移植到Windows的基于GCC的编译器。它适用于我的g++4.3.2(Linux下),并且我从VisualC++6开始在Win32上使用Unicode API。

  4. 什么是宽字符?

    在C/C++上,它是一种写入wchar_t的字符类型,比简单的char字符类型大。它应该用于放入索引(如Unicode字形)大于255(或127,具体取决于…)的字符。

什么时候不应该使用宽字符?

当你在1990年之前编写代码时。

显然,我是在胡思乱想,但实际上,现在已经是21世纪了。127个字符早已不再足够了。是的,你可以使用UTF8,但为什么要头疼呢?

我建议在Windows或其他地方避免std::wstring,除非接口需要,或任何靠近Windows API调用和相应编码转换作为语法糖的地方。

我的观点总结在http://utf8everywhere.org中,我是其中的共同作者。

除非你的应用程序是以API调用为中心的,例如主要是UI应用程序,否则建议将Unicode字符串存储在std::字符串中,并以UTF-8编码,在API调用附近执行转换。文章中概述的好处超过了转换的明显烦恼,尤其是在复杂的应用程序中。这对于多平台和库开发尤其如此。

现在,回答你的问题:

  1. 一些薄弱的原因。它的存在是出于历史原因,其中Widechars被认为是支持Unicode的正确方式。它现在用于连接更喜欢UTF-16字符串的API。我仅在此类API调用的直接附近使用它们。
  2. 这与std::字符串无关。它可以保存您输入的任何编码。唯一的问题是如何处理其内容。我的建议是UTF-8,因此它将能够正确保存所有Unicode字符。这是Linux的常见做法,但我认为Windows程序也应该这样做。
  3. 不。
  4. 宽字符是一个令人困惑的名称。在Unicode的早期,人们认为一个字符可以用两个字节编码,因此得名。今天,它代表“两个字节长的字符的任何部分”。UTF-16被视为此类字节对的序列(又名宽字符)。UTF-16中的字符需要一对或两对。

不满足于仅256个不同字符的应用程序可以选择使用宽字符(超过8位)或可变长度编码(C++术语中的多字节编码),例如UTF-8。宽字符通常需要比可变长度编码更多的空间,但处理速度更快。处理大量文本的多语言应用程序在处理文本时通常使用宽字符,但在将其存储到磁盘时将其转换为UTF-8。

stringwstring之间的唯一区别是它们存储的字符的数据类型。字符串存储的char的大小保证至少为8位,因此您可以使用字符串进行处理,例如ASCII、ISO-8859-15或UTF-8文本。标准没有说明字符集或编码。

实际上,每个编译器都使用前128个字符与ASCII对应的字符集。使用UTF-8编码的编译器也是如此。在UTF-8或其他可变长度编码中使用字符串时,需要注意的重要一点是索引和长度以字节为单位,而不是字符。

wstring的数据类型是wchar_t,其大小没有在标准中定义,除了它必须至少与char一样大,通常是16位或32位。wstring可用于处理实现定义的宽字符编码中的文本。因为编码没有在标准中定义,所以在字符串和wstring之间转换并不简单。也不能假设wstring具有固定长度的编码。

如果您不需要多语言支持,您可能可以只使用常规字符串。另一方面,如果您正在编写图形应用程序,通常情况下API仅支持宽字符。那么您可能希望在处理文本时使用相同的宽字符。请记住,UTF-16是一种可变长度编码,这意味着您不能假设length()返回字符数。如果API使用固定长度编码,例如UCS-2,处理变得容易。宽字符和UTF-8之间的转换很难以可移植的方式进行,但话又说回来,您的用户交互界面可能支持转换。

所以,现在在座的每一位读者都应该对事实和情况有一个清楚的了解。如果没有,那么你必须阅读paercebal非常全面的回答[顺便说一句:谢谢!]。

我的实用结论非常简单:所有C++(和STL)“字符编码”的东西基本上都是坏的和无用的。不管是否归咎于微软,这都无济于事。

我的解决方案,经过深入调查,很多挫折和相应的经验是:

  1. 接受,你必须自己负责编码和转换的事情(你会看到大部分都是微不足道的)

  2. 将std::字符串用于任何UTF-8编码的字符串(仅typedef std::string UTF8String

  3. 接受这样一个UTF8String对象只是一个愚蠢但廉价的容器。永远不要直接访问和/或操作其中的字符(没有搜索、替换等)。你可以,但你真的只是真的,真的不想浪费时间为多字节字符串编写文本操作算法!即使其他人已经做了这样愚蠢的事情,也不要这样做!顺其自然!(好吧,有一些场景是有意义的……使用ICU库即可)。

  4. 对UCS-2编码的字符串使用std::wstring(typedef std::wstring UCS2String)-这是一种妥协,也是对WIN32 API引入的混乱的让步)。UCS-2对我们大多数人来说已经足够了(稍后会详细介绍…)。

  5. 在需要逐个字符访问(读取、操作等)时使用UCS2String实例。任何基于字符的处理都应该以非多字节表示形式完成。它简单、快速、容易。

  6. 添加两个实用函数以在UTF-8和UCS-2之间来回转换:

    UCS2String ConvertToUCS2( const UTF8String &str );UTF8String ConvertToUTF8( const UCS2String &str );

The conversions are straightforward, google should help here ...

That's it. Use UTF8String wherever memory is precious and for all UTF-8 I/O. Use UCS2String wherever the string must be parsed and/or manipulated. You can convert between those two representations any time.

Alternatives & Improvements

  • conversions from & to single-byte character encodings (e.g. ISO-8859-1) can be realized with help of plain translation tables, e.g. const wchar_t tt_iso88951[256] = {0,1,2,...}; and appropriate code for conversion to & from UCS2.

  • if UCS-2 is not sufficient, than switch to UCS-4 (typedef std::basic_string<uint32_t> UCS2String)

ICU or other unicode libraries?

For advanced stuff.

问得好!我认为数据编码(有时也涉及CHARSET)是记忆表达机制,以便将数据保存到文件或通过网络传输数据,所以我回答这个问题:

1.何时应该使用std::wstring over std::string?

如果编程平台或API函数是单字节的,我们想处理或解析一些Unicode数据,例如从Windows读取。REG文件或网络2字节流,我们应该声明std::wstring变量来轻松处理它们。例如:wstring ws=L"中国a"(6个八位字节内存:0x4E2D 0x56FD 0x0061),我们可以使用ws[0]获取字符'中',ws[1]获取字符'国',ws[2]获取字符'a'等。

2. std::字符串可以容纳整个ASCII字符集,包括特殊字符吗?

是的。但请注意:美国ASCII是指每个0x00~0xFF八位字节代表一个字符,包括可打印的文本,例如“123abc&*_&”和你说的特殊字符,主要将其打印为'.',以免混淆编辑器或终端。还有一些国家扩展了自己的“ASCII”字符集,例如中文,使用2个八位字节代表一个字符。

3.所有流行的C++编译器都支持std::wstring吗?

也许吧,或者大部分。我用过:VC++6和GCC 3.3,是的

4.什么是“宽字符”?

宽字符主要表示使用2个八位字节或4个八位字节来保存所有国家的字符。2个八位字节UCS2是一个代表性样本,进一步例如英语'a',其内存是2个八位字节0x0061(vs在ASCII'a的内存是1个八位字节0x61)

这里有一些非常好的答案,但我认为我可以添加一些关于Windows/Visual Studio的内容。这是基于我对VS2015的经验。在Linux上,基本上答案是在任何地方使用UTF-8编码的std::string。在Windows/VS上,它变得更加复杂。这就是原因。Windows期望使用char存储的字符串使用语言环境代码页进行编码。这几乎总是ASCII字符集,然后是128个其他特殊字符,具体取决于您的位置。让我声明这不仅仅是在使用Windows API时,还有其他三个主要地方这些字符串与标准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::string,但您也可能没有使用宽字符串文字。

圣d::c

当使用<<输出到控制台时,您只能使用std::string,而不是std::wstring,并且文本必须使用您的语言环境代码页进行编码。如果您有std::wstring,那么您必须使用Windows API函数之一进行转换,并且代码页上没有的任何字符都将被?替换(也许您可以更改字符,我不记得了)。

std::f流文件名

Windows OS使用UCS2/UTF-16作为其文件名,因此无论您的代码页如何,您都可以拥有具有任何Unicode字符的文件。但这意味着要访问或创建代码页上没有字符的文件,您必须使用std::wstring。没有其他方法。这是Microsoft对std::fstream的特定扩展,因此可能无法在其他系统上编译。如果您使用std::字符串,那么您只能使用仅包含代码页上字符的文件名。

你的选择

如果你只是在Linux,那么你可能还没有走到这一步。只要在任何地方使用UTF-8std::string

如果您只是在Windows上工作,只需在任何地方使用UCS2std::wstring。一些纯粹主义者可能会说使用UTF8然后在需要时进行转换,但为什么要麻烦呢?

如果你是跨平台的,那么坦率地说,这是一个混乱。如果你试图在Windows上到处使用UTF-8,那么你需要非常小心你的字符串文字和输出到控制台。你很容易在那里损坏你的字符串。如果你在Linux上到处使用std::wstring,那么你可能无法访问std::fstream的宽版本,所以你必须进行转换,但没有损坏的风险。所以我个人认为这是一个更好的选择。很多人会不同意,但我并不孤单——这是wxWidget采取的道路。

另一种选择是在Linux上将defunicodestring键入为std::string,在Windows上键入为std::wstring,并有一个名为UNI()的宏,它在Windows上以L为前缀,在Linux上没有任何前缀,然后代码

#include <fstream>#include <string>#include <iostream>#include <Windows.h>
#ifdef _WIN32typedef std::wstring unicodestring;#define UNI(text) L ## textstd::string formatForConsole(const unicodestring &str){std::string result;//Call WideCharToMultiByte to do the conversionreturn result;}#elsetypedef std::string unicodestring;#define UNI(text) textstd::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。但是Windows API和标准C++类将期望使用语言环境代码页对std::string进行编码。这包括所有ASCII加上另外128个字符,这些字符会根据您的计算机设置使用的代码页而变化。

3)我相信是这样,但如果不是,那么它只是一个简单的typedef'std::basic_string'使用wchar_t而不是char

4)宽字符是大于1字节标准char类型的字符类型。在Windows上它是2字节,在Linux它是4字节。

如果你保持字符串的可移植性,你可以使用tstring、tchar。它是很久以前广泛使用的技术。在这个示例中,我使用自定义的TCHAR,但你可以在互联网上找到linux的tchar. h实现。

这个想法意味着wstring/wchar_t/UTF-16在windows上使用,string/char/utf-8(或ASCII…)在Linux上使用。

在下面的示例中,英语/日语多字节混合字符串的搜索在Windows/Linux平台上都可以很好地工作。

#include <locale.h>#include <stdio.h>#include <algorithm>#include <string>using namespace std;
#ifdef _WIN32#include <tchar.h>#else#define _TCHAR char#define _T#define _tprintf printf#endif
#define tstring basic_string<_TCHAR>
int main() {setlocale(LC_ALL, "");tstring s = _T("abcあいうえおxyz");
auto pos = s.find(_T("え"));auto r = s.substr(pos);_tprintf(_T("r=%s\n"), r.c_str());}