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

问题描述 投票:1回答:4

如何使用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
c++ macos unicode
4个回答
6
投票

打印wstring的方法是将其转换为基于UTF-8字符串的字符串。严重的是在Windows之外的wchar_t is pointless或其他各种其他平台库之一,不幸的是在它变得清楚之前使用了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”)正常工作。


4
投票

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

错误报告提到了两个解决方法,使用ios_base::sync_with_stdio(false)locale::global(...)


0
投票

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


0
投票

默认编码:

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

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

  1. 添加宏以检测平台。
#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 :: wstring转换为std :: string
#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。原始字符串后缀。

Linux代码。使用std :: cout直接打印std :: string,Linux上的Default Encoding是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工具链/远程)

QA

问:为什么不使用qazxsw poi头函数和类? A.不推荐的qazxsw poi不可能在VC ++上构建,但在g ++上没有问题。我更喜欢0警告和头痛。

问:Windows上的wstring是interchan。 A.弃用<codecvt>不可能在VC ++上构建,但在g ++上没有问题。我更喜欢0警告和头痛。

问:std :: wstring是跨平台的吗? 答:不.std :: wstring使用wchar_t元素。在Windows上,wchar_t大小为2个字节,每个字符以UTF-16单位存储,如果字符大于U + FFFF,则字符以两个UTF-16单位(2个wchar_t元素)表示,称为代理对。在Linux上,wchar_t size是4个字节,每个字符存储在一个wchar_t元素中,不需要代理对。检查Removed or deprecated features

问:std :: string是跨平台的吗? 答:是的。 std :: string使用char元素。保证char类型在所有编译器中都是相同的字节大小。 char类型大小为1个字节。检查Removed or deprecated features

© www.soinside.com 2019 - 2024. All rights reserved.