如何将C ++ wstring UTF-8字符打印到Mac OS或Unix终端?

时间:2012-07-16 21:13:38

标签: c++ macos unicode

如何使用std::wstring打印std::wcout

我尝试了以下内容,建议使用here,但它仅适用于打印此¡Hola!但不适用于此日本

#include <iostream>
#include <clocale>

int main(int argc, char* argv[])
{
  char* locale = setlocale(LC_ALL, ""); 
  std::cout << "locale: " << locale << std::endl; // "C" for me
  std::locale lollocale(locale);
  setlocale(LC_ALL, locale); 
  std::wcout.imbue(lollocale);
  std::wcout << L"¡Hola!" << std::endl; // ok
  std::wcout << L"日本" << std::endl;    // empty :(
  return 0;
}

以下(建议使用here)根本不打印日文字符:

#include <stdio.h>
#include <string>
#include <locale>
#include <iostream>

using namespace std;

int main()
{

        std::locale::global(std::locale(""));
        wstring japan = L"日本";
        wstring message = L"Welcome! Japan is ";

        message += japan;

        wprintf(message.c_str());
        wcout << message << endl;
}

所有这些都在Mac OS 10.6.8上。使用g ++ 4.2.1,使用终端2.1.2。

终端可以一般地显示字符,例如,当我cat源代码时。此外,此命令工作正常cout << "日本" << std::endl;,但我需要打印wstring

我的$LANG就是这样:

$ echo $LANG 
en_US.UTF-8

4 个答案:

答案 0 :(得分:6)

打印wstring的方法是将其转换为基于UTF-8字符串的字符串。严重的wchar_t is pointless以外的Windows或其他各种平台库之一,不幸的是在它变得清晰之前已经使用了wchar_t。

// move to clang and libc++ then
#include <codecvt>

int main(){
    std::wstring_convert<std::codecvt_utf8<wchar_t>,wchar_t> convert; // converts between UTF-8 and UCS-4 (given sizeof(wchar_t)==4)
    std:wstring s = L"日本";
    std::cout << convert.to_bytes(s);
}

只是为了解释你所展示的代码出了什么问题;

char* locale = setlocale(LC_ALL, ""); 
std::cout << "locale: " << locale << std::endl; // "C" for me

此处的区域设置字符串是应用更改后的区域设置名称。既然你说你得到“C”就意味着你正在使用“C”语言环境。通常会有一个像“en_US.UTF-8”这样的名称,但无论出于何种原因,您的环境都没有正确设置。您显示$LANG已正确设置,但可能其他区域设置环境变量之一设置不同。

在任何情况下,您都使用“C”语言环境,只需支持基本字符集。我相信OS X的行为是,任何char都会直接转换为相同的wchar_t值,而wchar_t支持的范围内只有char个值将转换回来。这实际上与使用基于ISO 8859-1的语言环境相同,因此日语字符不起作用。


如果你真的坚持让这个基于语言环境的东西工作,那么你需要得到一个合适的语言环境,一个使用UTF-8的语言环境。您可以找出您的环境有什么问题,也可以使用不可移植的显式语言环境名称。

std::wcout.imbue(std::locale("en_US.UTF-8"));
std::wcout << L"¡Hola!\n";
std::wcout << L"日本\n";

另外,如果您正在使用libstdc ++,您应该知道它在OS X上不能正确支持语言环境。您必须使用libc ++才能获得OS X的语言环境名称(例如,“en_US.UTF-8”)工作。

答案 1 :(得分:4)

根据libstdc ++的多个错误报告(例如http://gcc.gnu.org/bugzilla/show_bug.cgi?id=35353),C运行时和libstdc ++之间存在讨厌的交互,似乎没有人愿意尝试修复它,可能是因为utf-8“正常工作”对于大多数情况。

错误报告使用ios_base::sync_with_stdio(false)locale::global(...)提及两种解决方法。

答案 2 :(得分:0)

使用nowide库以最简单的方式转换为UTF-8。然后,使用常规printf。

答案 3 :(得分:0)

默认编码为:

  
      
  • Windows UTF-16。
  •   
  • Linux UTF-8。
  •   
  • MacOS UTF-8。
  •   

我的解决方案步骤,包括空字符\ 0(避免被截断)。在Windows.h标头上不使用函数:

  
      
  1. 添加宏以检测平台。
  2.   
#if defined (_WIN32) 
#define WINDOWSLIB 1

#elif defined (__ANDROID__) || defined(ANDROID)//Android
#define ANDROIDLIB 1

#elif defined (__APPLE__)//iOS, Mac OS
#define MACOSLIB 1

#elif defined (__LINUX__) || defined(__gnu_linux__) || defined(__linux__)//_Ubuntu - Fedora - Centos - RedHat
#define LINUXLIB 1
#endif
  
      
  1. 创建函数以将std :: w 字符串转换为std :: string
  2.   
#include <locale>
#include <iostream>
#include <string>
#ifdef WINDOWSLIB
#include <Windows.h>
#endif

using namespace std::literals::string_literals;

std::string WidestringToString(const std::wstring& wstr, const std::string& locale)
{
    if (wstr.empty())
    {
        return std::string();
    }
    size_t pos;
    size_t begin = 0;
    std::string ret;
    size_t  size;
#ifdef WINDOWSLIB
    _locale_t lc = _create_locale(LC_ALL, locale.c_str());
    pos = wstr.find(static_cast<wchar_t>(0), begin);
    while (pos != std::wstring::npos && begin < wstr.length())
    {
        std::wstring segment = std::wstring(&wstr[begin], pos - begin);
        _wcstombs_s_l(&size, nullptr, 0, &segment[0], _TRUNCATE, lc);
        std::string converted = std::string(size, 0);
        _wcstombs_s_l(&size, &converted[0], size, &segment[0], _TRUNCATE, lc);
        ret.append(converted);
        begin = pos + 1;
        pos = wstr.find(static_cast<wchar_t>(0), begin);
    }
    if (begin <= wstr.length()) {
        std::wstring segment = std::wstring(&wstr[begin], wstr.length() - begin);
        _wcstombs_s_l(&size, nullptr, 0, &segment[0], _TRUNCATE, lc);
        std::string converted = std::string(size, 0);
        _wcstombs_s_l(&size, &converted[0], size, &segment[0], _TRUNCATE, lc);
        converted.resize(size - 1);
        ret.append(converted);
    }
    _free_locale(lc);
#elif defined LINUXLIB
    std::string currentLocale = setlocale(LC_ALL, nullptr);
    setlocale(LC_ALL, locale.c_str());
    pos = wstr.find(static_cast<wchar_t>(0), begin);
    while (pos != std::wstring::npos && begin < wstr.length())
    {
        std::wstring segment = std::wstring(&wstr[begin], pos - begin);
        size = wcstombs(nullptr, segment.c_str(), 0);
        std::string converted = std::string(size, 0);
        wcstombs(&converted[0], segment.c_str(), converted.size());
        ret.append(converted);
        ret.append({ 0 });
        begin = pos + 1;
        pos = wstr.find(static_cast<wchar_t>(0), begin);
    }
    if (begin <= wstr.length()) {
        std::wstring segment = std::wstring(&wstr[begin], wstr.length() - begin);
        size = wcstombs(nullptr, segment.c_str(), 0);
        std::string converted = std::string(size, 0);
        wcstombs(&converted[0], segment.c_str(), converted.size());
        ret.append(converted);
    }
    setlocale(LC_ALL, currentLocale.c_str());
#elif defined MACOSLIB
#endif

    return ret;
}

如果需要逆运算,则附加。

std::wstring StringToWideString(const std::string& str, const std::string& locale)
{
    if (str.empty())
    {
        return std::wstring();
    }

    size_t pos;
    size_t begin = 0;
    std::wstring ret;
    size_t  size;

#ifdef WINDOWSLIB
    _locale_t lc = _create_locale(LC_ALL, locale.c_str());
    pos = str.find(static_cast<char>(0), begin);
    while (pos != std::string::npos) {
        std::string segment = std::string(&str[begin], pos - begin);
        std::wstring converted = std::wstring(segment.size() + 1, 0);
        _mbstowcs_s_l(&size, &converted[0], converted.size(), &segment[0], _TRUNCATE, lc);
        converted.resize(size - 1);
        ret.append(converted);
        ret.append({ 0 });
        begin = pos + 1;
        pos = str.find(static_cast<char>(0), begin);
    }
    if (begin < str.length()) {
        std::string segment = std::string(&str[begin], str.length() - begin);
        std::wstring converted = std::wstring(segment.size() + 1, 0);
        _mbstowcs_s_l(&size, &converted[0], converted.size(), &segment[0], _TRUNCATE, lc);
        converted.resize(size - 1);
        ret.append(converted);
    }
    _free_locale(lc);
#elif defined LINUXLIB
    std::string currentLocale = setlocale(LC_ALL, nullptr);
    setlocale(LC_ALL, locale.c_str());
    pos = str.find(static_cast<char>(0), begin);
    while (pos != std::string::npos) {
        std::string segment = std::string(&str[begin], pos - begin);
        std::wstring converted = std::wstring(segment.size(), 0);
        size = mbstowcs(&converted[0], &segment[0], converted.size());
        converted.resize(size);
        ret.append(converted);
        ret.append({ 0 });
        begin = pos + 1;
        pos = str.find(static_cast<char>(0), begin);
    }
    if (begin < str.length()) {
        std::string segment = std::string(&str[begin], str.length() - begin);
        std::wstring converted = std::wstring(segment.size(), 0);
        size = mbstowcs(&converted[0], &segment[0], converted.size());
        converted.resize(size);
        ret.append(converted);
    }
    setlocale(LC_ALL, currentLocale.c_str());
#elif defined MACOSLIB
#endif

    return ret;
}
  
      
  1. 打印std :: string。   选中RawString Literals。原始字符串后缀。
  2.   

Linux代码。使用std :: cout直接打印std :: string,Linux上的默认编码为UTF-8,不需要其他功能。

std::wstring x = L"\0\001日本ABC\0DE\0F\0G\0"s;
std::string result = WidestringToString(x, "en_US.UTF-8");
std::cout << "RESULT=" << result << std::endl;
std::cout << "RESULT_SIZE=" << result.size() << std::endl;

在Windows上,如果您需要打印unicode。我们可以使用WriteConsole从std :: wstring或std :: string打印Unicode字符。

#ifdef WINDOWSLIB
void WriteLineUnicode(std::wstring ws)
{
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), ws.c_str(), ws.length(), NULL, NULL);
    std::cout << std::endl;
}

void WriteUnicode(std::wstring ws)
{
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), ws.c_str(), ws.length(), NULL, NULL);
}

void WriteLineUnicode(std::string s)
{
    std::wstring unicode = StringToWideString(s);
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), unicode.c_str(), unicode.length(), NULL, NULL);
    std::cout << std::endl;
}

void WriteUnicode(std::string s)
{
    std::wstring unicode = StringToWideString(s);
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), unicode.c_str(), unicode.length(), NULL, NULL);
}
#endif

Windows代码。使用WriteConsole函数。

std::wstring x = L"\0\001日本ABC\0DE\0F\0G\0"s;
std::string result = WidestringToString(x, "en_US.UTF-8");
WriteLineUnicode(u8"RESULT" + result);
WriteLineUnicode(u8"RESULT_SIZE" + std::to_string(result.size()));

最后在Windows上。您需要在控制台中对Unicode字符提供强大而完整的支持。 我推荐ConEmu并设置为default terminal on Windows。您需要将Visual Studio挂接到ConEmu。请记住,Visual Studio的exe文件是 devenv.exe

  
      
  • 在带有VC ++的Microsoft Visual Studio 2017上测试; std = c ++ 17。 (Windows项目)
  •   
  • 使用g ++在Microsoft Visual Studio 2017上测试; std = c ++ 17。 (Linux项目)
  •   
  • 使用g ++在Jetbrains Clion 2018.3上进行测试; std = c ++ 17。 (Linux工具链/远程)
  •   

质量检查

  

。为什么不使用<codecvt>标头函数和类?    A。弃用Removed or deprecated features不能在VC ++上构建,但在g ++上则没有问题。我更喜欢0警告和头痛。

     

Q。 Windows上的wstring是interchan。
   A。弃用Removed or deprecated features不能在VC ++上构建,但在g ++上则没有问题。我更喜欢0警告和头痛。

     

。std :: wstring是跨平台的吗?
   A。号std :: wstring使用wchar_t元素。在Windows上,wchar_t的大小为2个字节,每个字符以UTF-16单位存储,如果字符大于U + FFFF,则该字符以称为代理对的两个UTF-16单位(2个wchar_t元素)表示。在Linux上,wchar_t的大小为4个字节,每个字符存储在一个wchar_t元素中,不需要代理对。选中Standard data types on UNIX, Linux, and Windows

     

问。 std :: string是跨平台的吗?
   A。是。 std :: string使用char元素。保证char类型在所有编译器中都是相同的字节大小。 char类型的大小为1个字节。选中Standard data types on UNIX, Linux, and Windows