【问题标题】:setw() imbues wrong output on strings containing UTF-8 multi-byte characters/code pointssetw() 在包含 UTF-8 多字节字符/代码点的字符串上注入错误的输出
【发布时间】:2016-03-06 19:57:25
【问题描述】:

我需要输出一些可能是 UTF-8 多字节的数据,并且我需要使用setw() 对其进行格式化。

当字符为多字节序列时,对齐丢失,setw() 无法正常工作。

//#include <stdio.h>
#include <locale>
#include <iostream>
//#include <fstream>
#include <iomanip>
//#include <sstream>

int main(int argc, char **argv)
{ 
    std::locale l=std::locale("en_US.utf8");
    std::locale::global(l); 
    std::cout.imbue(l);
    std::cout<<std::endl;
    std::cout<<std::setw(40)<<std::right<<"hi “my” friend"<<std::endl;
    std::cout<<std::setw(40)<<std::right<<"hi -my- friend"<<std::endl;
    return 0;
}

输出是:

                  hi “my” friend
                      hi -my- friend

我错过了什么?

我必须指出, 这两个字符不是普通的",而是另外两个字符,在 UTF-8 中每个字符由三个字节表示。

【问题讨论】:

  • 遗憾的是,注入 UTF-8 语言环境不会使格式化函数识别 UTF-8。完成任务的最简单方法是将所有内容都转换为 wchar_t 并使用宽字符流。

标签: c++ utf-8 locale cout setw


【解决方案1】:

字符串文字 "hi -my- friend" 包含 14 个字符。字符串文字 "hi “my” friend" 包含 18 个字符:symbols and are encoded by 3 characters/bytescout 按原样输出这些字符,它是将 3 字节序列转换为单个符号的目标终端。

所以,从流的角度来看,一切都很好:它输出 (width - strlen(literal) ) 填充字符,然后是 strlen(literal) 字符,width 总计。它不处理可能的多字节序列,也不知道目标终端将几个字符转换为一个符号。

【讨论】:

  • 预计具有区域设置知识的流将处理必要的转换。如果不是,则“setw”将无用,因为它不会执行用户期望的操作。 “灌输”是什么意思?显然,不仅需要终端,还需要文件以及这些文件可能包含 utf8 文本(或选择的任何编码)。
  • @GeorgeKourtis 如果您查看locale 类,您会发现它实际上与多字节编码无关。整个本地化库和所有标准流都需要固定宽度的编码。它唯一提供的是在编码之间进行转换的wstring_convertcodecvt_* 类。在将数据传递给标准库设施之前,您应该将数据转换为固定宽度的编码。简而言之:你给它提供了它无法处理的数据。要么将数据转换为固定宽度,要么不依赖任何额外的东西,除了原始字符输出
【解决方案2】:

您可以通过计算字符串在宽表示中的字符数来完成此格式化,然后计算字符串长度与宽表示之间的差异,然后将该差异添加到传递给 @ 的内容中987654321@,如:

std::mbstate_t state = std::mbstate_t();
std::string s = "hi “my” friend";
const char *cp = s.c_str();
size_t len = mbsrtowcs(nullptr, &cp, s.size(), &state);
std::cout << setw(40 + (s.size() - len)) << std::right << s << std::endl;

您可以将此功能编码为一个函数,该函数将字符串作为参数并仅返回要添加到 setw 调用的差异:

size_t f(const std::string &s)
{
  std::mbstate_t state = std::mbstate_t();
  const char *cp = s.c_str();
  size_t len = mbsrtowcs(nullptr, &cp, s.size(), &state);
  return s.size() - len;
}
...

std::string s = "hi “my” friend";
std::cout << std::setw(40 + f(s)) << std::right << s << std::endl;


【讨论】:

  • “宽”字符不需要是 UTF-16、UTF-32 或任何 Unicode 编码。所以不能保证这段代码会产生预期的结果。即使这样,如果“宽”字符是 UTF-16,它也只会对适合单个 UTF-16 代码单元的代码点产生有用的结果。
  • 这会产生 19,其中只需要 4 来纠正对齐。这似乎是由len 触发的最大 ulong64_t 值触发下或溢出以及所有有趣的东西。看起来必须明确定义语言环境才能使其工作
猜你喜欢
  • 2011-08-16
  • 1970-01-01
  • 2019-09-21
  • 1970-01-01
  • 2012-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-06
相关资源
最近更新 更多