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

什么时候应该使用std::wstring而不是std::string?std::string可以保存整个ASCII字符集,包括特殊字符吗?所有流行的C++编译器都支持std::wstring吗?什么是“宽字符”?


当前回答

当您希望使用Unicode字符串而不仅仅是ascii时,这有助于国际化是的,但它与0的配合不好没有意识到宽字符是编译器特有的处理unicode字符固定长度表示的方式,对于MSVC,它是2字节字符,对于gcc,我理解它是4字节。以及+1表示http://www.joelonsoftware.com/articles/Unicode.html

其他回答

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

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

在下面的示例中,英语/日语多字节混合字符串的搜索在两个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());
}

一串wstring?

std::string是在char上模板化的basicstring,而std::wstring是在wchart上模板化。

字符与wchar_t

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

那么Unicode呢?

问题是char和wchar_t都没有直接绑定到unicode。

在Linux上?

让我们以Linux操作系统为例:我的Ubuntu系统已经支持unicode。当我使用字符串时,它是以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

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

因此,在Linux上使用字符时,您通常会在不知道的情况下使用Unicode。由于std::string可以使用字符,所以std::字符串已经可以使用Unicode。

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

在Windows上?

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

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

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

使用字符的应用程序称为“多字节”(因为每个字形由一个或多个字符组成),而使用wchar_t的应用程序则称为“宽字符”(因为每一个字形由一或两个wchar_t)。有关详细信息,请参阅MultiByteToWideChar和WideCharToMultiByteWin32转换API。

因此,如果你在Windows上工作,你很想使用wchar_t(除非你使用一个隐藏它的框架,如GTK或QT…)。事实是,在幕后,Windows使用wchar_t字符串,所以即使是历史应用程序,在使用SetWindowText()(用于在Win32 GUI上设置标签的低级API函数)等API时,也会将其字符字符串转换为wchar_t。

内存问题?

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获取更多信息。

结论

什么时候应该在std::string上使用std::wstring?在Linux上?几乎从未(§)。在Windows上?几乎总是(§)。跨平台代码?取决于您的工具包。。。(§):除非您使用的工具包/框架另有说明std::string可以保存所有ASCII字符集,包括特殊字符吗?注意:std::string适合保存“binary”缓冲区,而std::wstring不是!在Linux上?对在Windows上?只有特殊字符可用于Windows用户的当前区域设置。编辑(Johann Gerell发表评论后):一个std::字符串将足以处理所有基于字符的字符串(每个字符都是从0到255的数字)。但是:ASCII应该从0到127。较高的字符不是ASCII码。从0到127的字符将被正确保存从128到255的字符将根据您的编码(unicode、非unicode等)而有意义,但只要以UTF-8编码,它将能够保存所有unicode字形。几乎所有流行的C++编译器都支持std::wstring吗?大多数情况下,除了移植到Windows的基于GCC的编译器。它适用于我的g++4.3.2(在Linux下),我从Visual C++6开始在Win32上使用Unicode API。宽字符到底是什么?在C/C++上,它是一种wchar_t编写的字符类型,比简单的char字符类型更大。它应该用于放置索引(如Unicode字形)大于255(或127,取决于…)的字符。

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

1.何时应该使用std::wstring而不是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::string是否可以保存整个ASCII字符集,包括特殊字符?

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

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

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

4.什么是“宽字符”?

宽字符主要表示使用2个八位字节或4个八位字符来容纳所有国家的字符。2个八位字节UCS2是一个代表性示例,此外,例如英语“a”,其内存为0x0061的2个八位数(而ASCII“a”的内存为1个八位位0x61)

这里有一些非常好的答案,但我认为关于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字节。

当您希望在字符串中存储宽字符时。宽取决于实现。如果我没记错的话,Visual C++默认为16位,而GCC默认值取决于目标。这里有32位长。请注意wchar_t(宽字符类型)与unicode无关。它只是保证它可以存储实现所支持的最大字符集的所有成员,并且至少可以存储char。也可以使用utf-8编码将unicode字符串精细地存储到std::string中。但它无法理解unicode代码点的含义。因此str.size()不会给出字符串中逻辑字符的数量,而只给出该字符串/wstring中存储的char或wchar_t元素的数量。出于这个原因,gtk/glib C++包装人员开发了一个可以处理utf-8的glib::ustring类。如果wchar_t是32位长,那么可以使用utf-32作为unicode编码,并且可以使用固定(utf-32是固定长度)编码来存储和处理unicode字符串。这意味着wstring的s.size()函数将返回正确数量的wchar_t元素和逻辑字符。是的,char总是至少8位长,这意味着它可以存储所有ASCII值。是的,所有主要的编译器都支持它。