协慌网

登录 贡献 社区

std :: wstring VS std :: string

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

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

答案

stringwstring

std::string是在char上模板化的basic_string ,而std::wstringwchar_t上模板化。

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 的 char 字符串)。如下代码:

#include <cstring>
#include <iostream>

int main(int argc, char* argv[])
{
   const char text[] = "olé" ;


   std::cout << "sizeof(char)    : " << sizeof(char) << std::endl ;
   std::cout << "text            : " << text << std::endl ;
   std::cout << "sizeof(text)    : " << sizeof(text) << std::endl ;
   std::cout << "strlen(text)    : " << strlen(text) << std::endl ;

   std::cout << "text(ordinals)  :" ;

   for(size_t i = 0, iMax = strlen(text); i < iMax; ++i)
   {
      std::cout << " " << static_cast<unsigned int>(
                              static_cast<unsigned char>(text[i])
                          );
   }

   std::cout << std::endl << std::endl ;

   // - - - 

   const wchar_t wtext[] = L"olé" ;

   std::cout << "sizeof(wchar_t) : " << sizeof(wchar_t) << std::endl ;
   //std::cout << "wtext           : " << wtext << std::endl ; <- error
   std::cout << "wtext           : UNABLE TO CONVERT NATIVELY." << std::endl ;
   std::wcout << L"wtext           : " << wtext << std::endl;

   std::cout << "sizeof(wtext)   : " << sizeof(wtext) << std::endl ;
   std::cout << "wcslen(wtext)   : " << wcslen(wtext) << std::endl ;

   std::cout << "wtext(ordinals) :" ;

   for(size_t i = 0, iMax = wcslen(wtext); i < iMax; ++i)
   {
      std::cout << " " << static_cast<unsigned int>(
                              static_cast<unsigned short>(wtext[i])
                              );
   }

   std::cout << std::endl << std::endl ;

   return 0;
}

输出以下文本:

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

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

因此,在 Linux 上使用char时,通常通常甚至在不知道的情况下最终使用 Unicode。由于std::stringchar ,因此std::string已经可以使用 Unicode 了。

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

在 Windows 上?

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

因此,他们的解决方案很有趣:如果应用程序使用char ,则使用计算机上的本地 charset / codepage 将 char 字符串编码 / 打印 / 显示在 GUI 标签上。例如,在法语本地化的 Windows 中,“olé” 将是 “olé”,但是在西里尔语本地化的 Windows 中,“olé” 将有所不同(如果使用Windows-1251,则为 “olй”)。因此,“历史应用程序” 通常仍将以相同的旧方式工作。

对于基于 Unicode 的应用程序,Windows 使用 2 字节宽的wchar_t ,并以UTF-16编码,而UTF-16是 2 字节字符的 Unicode 编码(或者至少是兼容程度最高的 UCS-2,这几乎是 IIRC 一样)。

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

因此,如果您在 Windows 上工作,则非常想使用wchar_t (除非您使用隐藏该框架的框架,例如GTK +QT ...)。事实是,在幕后,Windows 使用了wchar_t字符串,因此即使历史应用程序在使用SetWindowText()这样的 API SetWindowText()在 Win32 GUI 上设置标签的低级 API 函数SetWindowText()时,也将在wchar_t转换其char字符串。

内存问题?

UTF-32 是每个字符 4 个字节,因此,如果仅 UTF-8 文本和 UTF-16 文本将始终比 UTF-32 文本使用更少或相同的内存量(通常更少),则无需添加太多内容)。

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

不过,对于其他语言(中文,日文等),与 UTF-16 相比,UTF-8 使用的内存将相同或稍大。

总而言之,UTF-16 每个字符最多将使用 2 个字节,有时还会使用 4 个字节(除非您要处理某种深奥的语言字形(Klingon?Elvish?),而 UTF-8 则需要 1 到 4 个字节。

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

结论

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

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

    (§):除非您使用工具箱 / 框架,否则

  2. std::string容纳所有 ASCII 字符集,包括特殊字符吗?

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

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

    编辑(在Johann Gerell发表评论之后):
    一个std::string足以处理所有基于char的字符串(每个char为 0 到 255 之间的数字)。但:

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

    通常,除了移植到 Windows 的基于 GCC 的编译器。
    它适用于我的 g ++ 4.3.2(在 Linux 下),并且自 Visual C ++ 6 起,我就在 Win32 上使用了 Unicode API。

  4. 什么是宽字?

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

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

我的共同作者http://utf8everywhere.org中概述了我的观点。

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

现在,回答您的问题:

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

因此,现在这里的每个读者都应该对事实,情况有清楚的了解。如果没有,那么您必须阅读 paercebal 出色的综合答案 [btw:谢谢!]。

我的务实结论非常简单:所有 C ++(和 STL)“字符编码” 的内容都被破坏了并且毫无用处。无论是否责怪微软,这都无济于事。

经过深入调查后,我的解决方案是:

  1. 接受,您必须自己对编码和转换负责(并且您会发现其中的许多内容都很琐碎)

  2. 对任何 UTF-8 编码的字符串使用 std :: string(只是一个typedef std::string UTF8String

  3. 接受这样的 UTF8String 对象只是一个愚蠢但便宜的容器。永远不要直接访问和 / 或操纵其中的字符(不要搜索,替换等)。您可以,但是您确实真的真的不想浪费时间为多字节字符串编写文本操作算法!即使其他人已经做了这种愚蠢的事情,也不要那样做!随它去! (嗯,在某些情况下这是有意义的…… 只需使用 ICU 库即可)。

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

  5. 每当需要逐个字符访问(读取,操作等)时,请使用 UCS2String 实例。任何基于字符的处理都应以 NON-multibyte 表示形式进行。它简单,快速,容易。

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

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

转换非常简单,Google 应该在这里提供帮助...

而已。在内存很宝贵的地方和所有 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 库?

对于高级的东西。