setw() 在包含 UTF-8 多字节字符/码位的字符串上输入错误的输出

setw() imbues wrong output on strings containing UTF-8 multi-byte characters/code points

本文关键字:字符串 输入 输出 错误 包含 UTF-8 字符 多字节 setw      更新时间:2023-10-16

我需要输出一些可能是 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 中,每个字符由三个字节表示。

字符串

文字"hi -my- friend"包含 14 个字符。字符串文本"hi “my” friend"包含 18 个字符:符号由 3 个字符/字节编码。 cout按原样输出这些字符,它是将 3 字节序列转换为单个符号的目标终端。

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

您可以通过计算字符串在宽表示形式中将有多少个字符来完成此格式设置,然后取字符串长度与宽表示之间的差值,然后将该差值添加到传递给setw的内容中,例如:

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;