比较std::wstring和std::string

6

我该如何比较wstring(如L"Hello")和string类型的字符串?如果需要它们的类型相同,又该如何进行转换?


使用 mbstowcs() 将字符串转换为宽字符串。但是,你必须使用 char* 进行操作。 - Kerrek SB
你有没有考虑过只使用std::wstring? - MFH
3个回答

7

既然您提出了要求,这里是我的标准字符串转宽字符串的转换函数,使用了C++的std::stringstd::wstring类实现。

首先,请确保在程序中使用set_locale函数开始:

#include <clocale>

int main()
{
  std::setlocale(LC_CTYPE, "");  // before any string operations
}

现在让我们来了解一下函数。首先,从窄字符串获取宽字符串:

#include <string>
#include <vector>
#include <cassert>
#include <cstdlib>
#include <cwchar>
#include <cerrno>

// Dummy overload
std::wstring get_wstring(const std::wstring & s)
{
  return s;
}

// Real worker
std::wstring get_wstring(const std::string & s)
{
  const char * cs = s.c_str();
  const size_t wn = std::mbsrtowcs(NULL, &cs, 0, NULL);

  if (wn == size_t(-1))
  {
    std::cout << "Error in mbsrtowcs(): " << errno << std::endl;
    return L"";
  }

  std::vector<wchar_t> buf(wn + 1);
  const size_t wn_again = std::mbsrtowcs(buf.data(), &cs, wn + 1, NULL);

  if (wn_again == size_t(-1))
  {
    std::cout << "Error in mbsrtowcs(): " << errno << std::endl;
    return L"";
  }

  assert(cs == NULL); // successful conversion

  return std::wstring(buf.data(), wn);
}

回到正题,将宽字符串转换为窄字符串。我称窄字符串为“本地化字符串”,因为它采用与当前语言环境相关的平台特定编码:

// Dummy
std::string get_locale_string(const std::string & s)
{
  return s;
}

// Real worker
std::string get_locale_string(const std::wstring & s)
{
  const wchar_t * cs = s.c_str();
  const size_t wn = std::wcsrtombs(NULL, &cs, 0, NULL);

  if (wn == size_t(-1))
  {
    std::cout << "Error in wcsrtombs(): " << errno << std::endl;
    return "";
  }

  std::vector<char> buf(wn + 1);
  const size_t wn_again = std::wcsrtombs(buf.data(), &cs, wn + 1, NULL);

  if (wn_again == size_t(-1))
  {
    std::cout << "Error in wcsrtombs(): " << errno << std::endl;
    return "";
  }

  assert(cs == NULL); // successful conversion

  return std::string(buf.data(), wn);
}

注意事项:

  • 如果您没有std::vector::data(),可以使用&buf[0]代替。
  • 我发现在Windows上,r风格的转换函数mbsrtowcswcsrtombs无法正常工作。您可以使用mbstowcswcstombs代替:mbstowcs(buf.data(), cs, wn + 1);wcstombs(buf.data(), cs, wn + 1);


针对您的问题,如果想比较两个字符串,您可以将它们都转换为宽字符串,然后进行比较。如果您要从磁盘读取已知编码的文件,则应使用iconv()将文件从已知编码转换为WCHAR,然后与宽字符串进行比较。

但请注意,复杂的Unicode文本可能具有多个不同的代码点序列表示形式,您可能希望将其视为相等。如果存在这种可能性,您需要使用更高级的Unicode处理库(例如ICU),并将字符串规范化为某些公共可比较的形式。


我已经更新了帖子,摆脱了可变长度数组。请使用最新版本再次检查。对此我很抱歉 - 我在我的私人代码中使用了VLAs,但是为了公共消费,最好像我在这里做的那样使用向量。我还添加了一些必需的头文件。 - Kerrek SB
谢谢您的回复。我通过您的函数将我的UTF8字符串转换为宽字符串(wstring),但与其他宽字符串进行比较时失败了。但是昨天我发现我的真正问题是将UTF8的std::string转换为UTF16的std::wstring,这解决了我的问题:https://dev59.com/Omw05IYBdhLWcg3wmCwo。谢谢! - aliakbarian
C++中的stringwstring类以及mbstowcs/wcstombs函数完全不受编码限制。您无法控制任何字符串最终采用的编码方式。如果您需要确定的编码方式,您需要使用像iconv()这样的工具将WCHAR转换为确定的编码方式。 - Kerrek SB
你能告诉我如何使用iconv()吗? - aliakbarian
我猜我可以,但不能在评论中——难道你不能查找文档或搜索互联网吗?一定有无数的例子。在Linux上,输入 man 3 iconv 获取简介。您必须按顺序使用 iconv_open()iconv()iconv_close() - Kerrek SB
@KerrekSB +1 我已经修改了这个答案,以便在这里使用。不太确定,但如果你认为它是一个重复的问题,我想让你知道,这样你就可以关闭它。 - Jonathan Mee

3
请三思而后行——您可能根本不想首先比较它们。如果您确定要比较,并且正在使用Windows,则使用MultiByteToWideCharstring转换为wstring,然后使用CompareStringEx进行比较。
如果您没有使用Windows,则类似的函数是mbstowcswcscmp。标准的宽字符C++函数在Windows下通常不可移植;例如mbstowcs已被弃用。
处理Unicode的跨平台方式是使用ICU库。
请注意使用特殊函数进行Unicode字符串比较,不要手动比较。两个Unicode字符串可能具有不同的字符,但仍然相同。
wstring ConvertToUnicode(const string & str)
{
    UINT  codePage = CP_ACP;
    DWORD flags    = 0;
    int resultSize = MultiByteToWideChar
        ( codePage     // CodePage
        , flags        // dwFlags
        , str.c_str()  // lpMultiByteStr
        , str.length() // cbMultiByte
        , NULL         // lpWideCharStr
        , 0            // cchWideChar
        );
    vector<wchar_t> result(resultSize + 1);
    MultiByteToWideChar
        ( codePage     // CodePage
        , flags        // dwFlags
        , str.c_str()  // lpMultiByteStr
        , str.length() // cbMultiByte
        , &result[0]   // lpWideCharStr
        , resultSize   // cchWideChar
        );
    return &result[0];
}

2
标准的宽字符函数不仅仅在Windows下“不可移植”,它们根本就不可移植。这些函数使用的编码(包括“多字节”和“宽字符”编码)完全由实现定义。 - André Caron

3
你应该使用 mbstowcschar 字符串转换为 wchar_t 字符串,然后比较这两个字符串。请注意,mbstowcs 可以处理 char */wchar *,因此你可能需要进行如下操作:
std::wstring StringToWstring(const std::string & source)
{
    std::wstring target(source.size()+1, L' ');
    std::size_t newLength=std::mbstowcs(&target[0], source.c_str(), target.size());
    target.resize(newLength);
    return target;
}

我不确定&target[0]的使用是否符合标准规范,如果有人对此有好的答案,请在评论中告诉我。此外,有一个默认假设是转换后的字符串长度(以wchar_t为单位)不会长于原始字符串长度(以char为单位)- 这是一个逻辑上的假设,但我仍然不确定它是否符合标准。

另一方面,似乎没有办法询问mbstowcs所需缓冲区的大小,因此要么使用这种方法,要么使用Unicode库(如Windows API或类似iconv的库)提供更好的代码(更好地定义)。

还要记住,在不使用特殊函数比较Unicode字符串时,可能会遇到问题,两个等效的字符串可能会在按位比较时评估出现差异。

长话短说:这应该可以工作,我认为这是您只使用标准库可以做到的最大程度,但实现方式与Unicode处理方式的关系很大,并且我不太信任它。一般来说,最好在应用程序内部使用编码,并避免这种类型的转换,除非绝对必要,并且如果您正在使用明确的编码,请使用不太依赖于实现的API。


3
无法使用单个数字构造wstring,即使可以,也会得到错误长度的字符串。你需要调用mbstowcs两次以获取所需的目标长度。 - Kerrek SB
1
请注意,如果字符串包含非英文文本或UTF-8编码,这种方法可能无法正常工作。在Windows系统中有MBTWC,正如@Don Reba所建议的那样,但显然它不具备可移植性,如果这对问题提出者很重要的话。 - Eran
3
@eran说:mbstowcs完全与编码无关,它仅在“系统的多字节表示”和“系统的宽字符”之间进行转换。对于这两者中的任何一个,都没有要求必须是您熟悉的内容。如果您想要确定的编码,则必须使用iconv()从WCHAR转换为您喜欢的(Unicode?)编码。这是我对该主题的小抱怨 - Kerrek SB
1
@Kerrek,我想给你的那篇发泄文点赞,但我不能。我一个半月前已经这样做了... - Eran
1
@RétroX:直接写入std::wstring的想法是为了避免浪费时间进行另一次堆分配/清除,如果转换经常发生,可能会影响性能。问题是,尽管我看到这种习惯用法不止一次,但我不确定那个东西是否实际上被标准允许。 - Matteo Italia
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接