【问题标题】:Compare std::wstring and std::string比较 std::wstring 和 std::string
【发布时间】:2011-08-21 21:20:29
【问题描述】:

如何将wstring(例如L"Hello")与string 进行比较?如果我需要相同的类型,如何将它们转换为相同的类型?

【问题讨论】:

  • 使用mbstowcs() 将字符串变成宽字符串。不过,您必须使用 char*s。
  • 您是否考虑过只使用 std::wstring?

标签: c++


【解决方案1】:

既然你问了,这是我从字符串到宽字符串的标准转换函数,使用 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(),你可以改成&amp;buf[0]
  • 我发现r 风格的转换函数mbsrtowcswcsrtombs 在Windows 上不能正常工作。在那里,您可以改用mbstowcswcstombsmbstowcs(buf.data(), cs, wn + 1);wcstombs(buf.data(), cs, wn + 1);


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

但请注意,复杂的 Unicode 文本可能有多个 不同 表示作为代码点序列,您可能会认为它们是相等的。如果有可能,您需要使用更高级别的 Unicode 处理库(例如 ICU)并将您的字符串规范化为一些常见的、可比较的形式。

【讨论】:

  • 我已经更新了帖子以摆脱可变长度数组。使用最新版本再次检查。抱歉 - 我在我的私人代码中使用了 VLA,但对于公共消费,最好像我在这里一样使用向量。我还添加了一些更多必需的标题。
  • 感谢您的回复..我通过您的函数将我的 utf8 字符串转换为 wstring,但我与其他 wstring 的比较失败了。但昨天我发现我真正的问题是将 utf8 std::string 转换为 utf16 std::wstring 并解决了我的问题:stackoverflow.com/questions/7153935/…
  • C++ stringwstring 类以及 mbstowcs/wcstombs 完全与编码无关。您无法控制任何字符串的最终编码方式。如果您需要明确的编码,则需要使用 iconv() 之类的东西从 WCHAR 转换为明确的编码。
  • 你能告诉我如何使用 iconv() 吗?
  • 我想我可以,但不是在评论中——你不能查一下文档,或者搜索互联网吗?那里一定有无数的例子。在 Linux 上,键入 man 3 iconv 以获得概要。您必须按顺序使用iconv_open()iconv()iconv_close()
【解决方案2】:

您应该使用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;
}

我不完全确定&amp;target[0] 的使用是否完全符合标准,如果有人对此有很好的答案,请在 cmets 中告诉我。此外,还有一个隐含的假设,即转换后的字符串(wchar_ts 的数量)不会比原始字符串的chars 的数量长 - 这是一个我仍然不确定它是否被涵盖的逻辑假设标准。

另一方面,似乎没有办法向 mbstowcs 询问所需缓冲区的大小,所以要么你走这条路,要么使用 Unicode 库中的(做得更好和定义更好的)代码(be它是 Windows API 或 iconv 等库)。

不过,请记住,在不使用特殊函数的情况下比较 U​​nicode 字符串很容易出错,两个等价的字符串在按位比较时可能会得出不同的结果。

长话短说:这应该可行,而且我认为这是您仅使用标准库所能做的最大工作,但在处理 Unicode 方面很大程度上取决于实现,我不太相信它。一般来说,最好在应用程序中坚持编码并避免这种转换,除非绝对必要,而且,如果您使用明确的编码,请使用不太依赖于实现的 API。

【讨论】:

  • wstring 不能在单个数字上构造,即使可以,您最终也会得到长度错误的字符串。您确实必须调用mbstowcs 两次才能获得所需的目标长度。
  • 请注意,如果字符串包含非英文文本或 UTF-8,这可能不起作用。正如@Don Reba 所建议的那样,在 Windows 中有 MBTWC,但如果这对 OP 很重要,它显然不是可移植的。
  • @eran: mbstowcs 完全与编码无关。它只是在“系统的多字节表示”和“系统的宽字符”之间进行转换。没有要求其中任何一个是你能识别的任何东西。如果你想要一个明确的编码,你必须用iconv() 将WCHAR 转换为你最喜欢的(Unicode?)编码。这是我关于这个主题的little rant
  • @Kerrek,我会赞成你的咆哮,但我不能。大约一个半月前我已经这样做了......
  • @RétroX:直接写入std::wstring 的想法是避免在另一个堆分配/释放上浪费时间,如果经常进行转换,可能会影响性能。问题是,虽然我不止一次看到这个成语,但我不确定这件事是否真的被标准允许。
【解决方案3】:

在做这件事之前要三思而后行——你可能一开始就不想比较它们。如果您确定这样做并且您使用的是 Windows,则将 string 转换为 wstringMultiByteToWideChar,然后与 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];
}

【讨论】:

  • 标准的宽字符函数不仅仅是“在windows下不可移植”。它们是非便携期。这些函数使用的编码(“多字节”和“宽字符”编码)完全由实现定义。
猜你喜欢
  • 2014-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-12
  • 2011-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多