848

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

  1. 我应该什么时候使用std::wstringover std::string
  2. 可以std::string容纳整个 ASCII 字符集,包括特殊字符吗?
  3. std::wstring所有流行的 C++ 编译器都支持吗?
  4. 究竟什么是“宽字符”?
4

12 回答 12

1095

string? wstring?

std::string是在 a和a 上basic_string模板化的。charstd::wstringwchar_t

char对比wchar_t

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

那么Unicode呢?

问题是既不char也不wchar_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"; <- error
    std::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)    : 1
text            : olé
sizeof(text)    : 5
strlen(text)    : 4
text(ordinals)  : 111 108 195 169

sizeof(wchar_t) : 4
wtext           : UNABLE TO CONVERT NATIVELY.
wtext           : ol�
sizeof(wtext)   : 16
wcslen(wtext)   : 3
wtext(ordinals) : 111 108 233

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

因此,char在 Linux 上使用 a 时,您通常应该在不知不觉中使用 Unicode。并且与std::string一起使用char,因此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_t2 字节宽,并以UTF-16编码,这是 Unicode 编码在 2 字节字符上(或者至少是 UCS-2,它只是缺少代理对和因此 BMP 之外的字符 (>= 64K))。

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

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

内存问题?

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

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

尽管如此,对于其他语言(中文、日文等),UTF-8 使用的内存将相同,或者略大于 UTF-16。

总而言之,UTF-16 将主要使用 2 个字节,有时每个字符使用 4 个字节(除非您正在处理某种深奥的语言字形(克林贡语?精灵语?),而 UTF-8 将花费 1 到 4 个字节。

有关更多信息,请参阅https://en.wikipedia.org/wiki/UTF-8#Compared_to_UTF-16

结论

  1. 我什么时候应该在 std::string 上使用 std::wstring?

    在 Linux 上?几乎从不 (§)。在 Windows 上?几乎总是 (§)。在跨平台代码上?取决于你的工具包...

    (§) :除非您使用工具包/框架另有说明

  2. 可以std::string容纳包括特殊字符在内的所有 ASCII 字符集吗?

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

    在 Linux 上?是的。在 Windows 上?只有特殊字符可用于 Windows 用户的当前语言环境。

    编辑(在Johann Gerell发表评论后): astd::string足以处理所有char基于 - 的字符串(每个字符串char都是从 0 到 255 的数字)。但:

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

    大多数情况下,移植到 Windows 的基于 GCC 的编译器除外。它适用于我的 g++ 4.3.2(在 Linux 下),自 Visual C++ 6 起我在 Win32 上使用 Unicode API。

  4. 什么是宽字符?

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

于 2008-12-31T12:47:29.907 回答
94

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

我的观点总结在http://utf8everywhere.org中,我是其中的合著者。

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

现在,回答您的问题:

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

所以,现在在座的每一位读者都应该对事实、情况有一个清醒的认识。如果没有,那么您必须阅读 paercebal 非常全面的答案[顺便说一句:谢谢!]。

我的实用结论非常简单:所有 C++(和 STL)“字符编码”的东西都被严重破坏和无用。归咎于微软与否,无论如何这都无济于事。

我的解决方案,经过深入调查,非常沮丧和随之而来的经验如下:

  1. 接受,你必须自己负责编码和转换的东西(你会发现其中大部分都是微不足道的)

  2. 对任何 UTF-8 编码的字符串使用 std::string (只是一个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 );
    

转换很简单,谷歌应该在这里提供帮助......

而已。在内存宝贵的地方和所有 UTF-8 I/O 使用 UTF8String。在必须解析和/或操作字符串的任何地方使用 UCS2String。您可以随时在这两种表示之间进行转换。

替代方案和改进

  • 从 & 到单字节字符编码(例如 ISO-8859-1)的转换可以在普通翻译表的帮助下实现,例如const wchar_t tt_iso88951[256] = {0,1,2,...};从 UCS2 到 & 的转换的适当代码。

  • 如果 UCS-2 不够用,则切换到 UCS-4 ( typedef std::basic_string<uint32_t> UCS2String)

ICU 或其他 unicode 库?

对于高级的东西。

于 2011-11-07T06:07:09.977 回答
26
  1. 当您想在字符串中存储宽字符时。wide取决于实施。如果我没记错的话,Visual C++ 默认为 16 位,而 GCC 默认取决于目标。这里是 32 位长。请注意 wchar_t(宽字符类型)与 unicode 无关。它只是保证它可以存储实现支持的语言环境支持的最大字符集的所有成员,并且至少与 char 一样长。您也可以unicode 字符串很好地存储到std::string使用utf-8编码中。但它不会理解 unicode 代码点的含义。所以str.size()不会为您提供字符串中的逻辑字符数量,而只会提供存储在该字符串/wstring 中的 char 或 wchar_t 元素的数量。出于这个原因,gtk/glib C++ 包装人员开发了一个Glib::ustring可以处理 utf-8 的类。

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

  2. 是的,char 总是至少 8 位长,这意味着它可以存储所有 ASCII 值。
  3. 是的,所有主要编译器都支持它。
于 2008-12-31T11:48:20.757 回答
7

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

例如,我在将代码与 Tcl 解释器连接时使用 utf-8。

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

于 2008-12-31T04:33:23.913 回答
6

一个好问题!我认为数据编码(有时还涉及字符集)是一种内存表达机制,以便将数据保存到文件或通过网络传输数据,所以我回答这个问题:

1. 我应该什么时候使用 std::wstring 而不是 std::string?

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

2. std::string 可以保存整个 ASCII 字符集,包括特殊字符吗?

是的。但请注意:美国 ASCII,表示每个 0x00~0xFF 八位位组代表一个字符,包括可打印文本,例如“123abc&*_&”,您说的是特殊字符,大多打印为 '.' 避免混淆编辑器或终端。而其他一些国家扩展了他们自己的“ASCII”字符集,例如中文,使用 2 个八位字节来代表一个字符。

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

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

4. 究竟什么是“宽字”?

宽字符主要表示使用 2 个八位字节或 4 个八位字节来保存所有国家/地区的字符。2 octet UCS2 是一个有代表性的样本,再比如英文'a',它的内存是2 octet of 0x0061(vs in ASCII 'a's memory 是1 octet 0x61)

于 2013-10-29T09:56:25.017 回答
5
  1. 当您想要存储“宽”(Unicode)字符时。
  2. 是:其中 255 个(不包括 0 个)。
  3. 是的。
  4. 这是一篇介绍性文章:http ://www.joelonsoftware.com/articles/Unicode.html
于 2008-12-31T04:16:01.123 回答
4

这里有一些非常好的答案,但我认为我可以添加一些关于 Windows/Visual Studio 的内容。这是基于我对 VS2015 的经验。std::string在 Linux 上,基本上答案是到处使用 UTF-8 编码。在 Windows/VS 上,它变得更加复杂。这就是为什么。Windows 期望使用chars 存储的字符串使用语言环境代码页进行编码。这几乎总是 ASCII 字符集后跟 128 个其他特殊字符,具体取决于您所在的位置。让我声明一下,这不仅在使用 Windows API 时,还有其他三个主要地方这些字符串与标准 C++ 交互。这些是字符串文字,输出到std::coutusing<<并将文件名传递给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 函数对其进行编码,使用代码页将其放入 a 中std::string,但您可能还没有使用宽字符串文字。

标准::cout

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

std::fstream 文件名

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

您的选择

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

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

如果您是跨平台的,那么坦率地说,这是一团糟。如果您尝试在 Windows 上的任何地方使用 UTF-8,那么您需要非常小心您的字符串文字并输出到控制台。您可以在那里轻松损坏您的字符串。如果您std::wstring在 Linux 上的任何地方都使用,那么您可能无法访问std::fstream. 所以我个人认为这是一个更好的选择。许多人会不同意,但我并不孤单 - 例如 wxWidgets 采用的路径。

另一种选择可能是在 Linux 和 Windows 上使用 typedef unicodestringstd::stringstd::wstring有一个名为 UNI() 的宏,它在 Windows 上以 L 为前缀,而在 Linux 上则没有,然后是代码

#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。但是 Windows API 和标准 C++ 类将期望std::string使用语言环境代码页进行编码。这包括所有 ASCII 加上另外 128 个字符,这些字符会根据您的计算机设置使用的代码页而变化。

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

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

于 2018-08-17T13:17:23.187 回答
2

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

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

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

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

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

于 2011-09-11T09:28:00.243 回答
1
  1. 当您想使用 Unicode 字符串而不仅仅是 ascii 时,有助于国际化
  2. 是的,但它不适合0
  3. 不知道任何不知道的
  4. 宽字符是编译器特定的处理 unicode 字符的固定长度表示的方式,对于 MSVC,它是 2 字节字符,对于 gcc,我理解它是 4 字节。和http://www.joelonsoftware.com/articles/Unicode.html的 +1
于 2008-12-31T04:16:48.593 回答
-3

1) 正如 Greg 所说,wstring 有助于国际化,即您将以英语以外的语言发布您的产品

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

于 2008-12-31T04:24:41.853 回答
-7

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

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

显然,我是在翻转,但实际上,现在是 21 世纪。127 个字符早就不够用了。是的,您可以使用 UTF8,但为什么还要麻烦呢?

于 2009-06-10T23:26:56.517 回答