iostreams - 将`wchar_t`或`charXX_t`值打印为字符

7
如果您将,或值提供给窄ostream,则它将打印代码点的数字值。
#include <iostream>
using std::cout;
int main()
{
    cout << 'x' << L'x' << u'x' << U'x' << '\n';
}

打印 x120120120。这是因为特定组合的 basic_ostream 和其 charT 有一个 operator<<,但其他字符类型没有类似的运算符,所以它们会被静默转换为 int 并以这种方式打印。同样,非窄字符串文字 (L"x", u"x", U"X") 将被静默转换为 void* 并作为指针值打印,而非窄字符串对象 (wstring, u16string, u32string) 甚至无法编译。

那么问题来了:在窄 ostream 上以字符形式而不是代码点数值打印 wchar_tchar16_tchar32_t 值,最少可怕的方法是什么?它应正确地将 所有 可表示为 ostream 编码的代码点转换为该编码,并在代码点不可表示时报告错误。(例如,给定 u'…' 和一个 UTF-8 ostream,则应将三字节序列 0xE2 0x80 0xA6 写入流; 但是,如果给定 u'â' 和一个 KOI8-R ostream,则应报告错误。)

类似地,如何在窄 ostream 上打印非窄 C-字符串或字符串对象,并将其转换为输出编码?

如果这不能在 ISO C++11 中完成,则接受特定平台的答案。

(灵感来自这个问题。)


2
简而言之,你必须要么使用宽 ostream,要么自己将宽字符数据转换为窄编码(这是一种可能会有损失的转换)。ostream 无法为您执行此转换。请查看 std::wstring_convert,或使用类似 ICONVICU 的库。 - Remy Lebeau
1个回答

3
正如你所指出的,对于窄流(narrow ostream)而言,不存在 operator<<(std::ostream&, const wchar_t)。但是,如果你想使用这种语法,你可以教会 ostream 如何处理 wchar,从而使得该例程被视为更好的重载,而不是需要先将其转换为整数的那个例程。
如果你感到有冒险精神:
namespace std {
  ostream& operator<< (ostream& os, wchar_t wc) {
    if(unsigned(wc) < 256) // or another upper bound
      return os << (unsigned char)wc;
    else
      throw your_favourite_exception; // or handle the error in some other way
  }
}

否则,可以创建一个简单的struct,透明地包含一个wchar_t,并具有自定义的friend operator<<,在输出宽字符之前将其转换为该结构。 编辑:为了实现即时的转换和区域设置,您可以使用<cwchar>中的函数,例如:
ostream& operator<< (ostream& os, wchar_t wc) {
    std::mbstate_t state{};
    std::string mb(MB_CUR_MAX, '\0');
    size_t ret = std::wcrtomb(&mb[0], wc, &state);
    if(ret == static_cast<std::size_t>(-1))
        deal_with_the_error();
    return os << mb;
}

不要忘记将你的本地化设置为系统默认值:

std::locale::global(std::locale(""));
std::cout << L'ŭ';

这不会将值转换为窄输出编码。 这是必要的,也是我还不知道如何做的部分。 - zwol
例如,当窄输出编码为UTF-8时,它应将L"…"转换为三字节序列0xE2 0x80 0xA6。 - zwol
我认为显而易见的是,我想要的是能够处理窄输出编码支持的 所有 字符,而不仅仅是 ASCII。 - zwol
我明白了!我以为输出编码是ASCII。iconv并不难使用,我会尝试将其应用进去。 - The Vee
@zwol 请看更新,结果发现还有更好的方法。 - The Vee
显示剩余2条评论

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