c++ 中文字符的 setw 格式化打印问题

问题引入

考虑这样的一段代码:

#include <iostream>
#include <iomanip>
#include <string>
using namespace std;

void aligned_string() {
	string cstr1 = "中文", cstr2 = "对齐";
	string estr1 = "English", estr2 = "Alignment";
	cout << setw(20) << left << cstr1 << cstr2 << endl;
	cout << setw(20) << left << estr1 << estr2 << endl;
}

int main() {
	aligned_string();
}

在默认的windows中文环境下(源代码文件和控制台的编码都是936,即一种GBK),setw()函数在中英文混合的场景下可以正常工作。

在这里插入图片描述
但是在unix环境下(默认使用UTF-8作为源代码和控制台的编码),setw()函数在中英文混合的场景下并不能正常工作。

在这里插入图片描述

产生原因

  • setw()函数的特性
    setw()函数中传入的参数,作为最终约定的“宽度”,并不是字符数,而是字节数。中英文在不同的编码方式下,有着不同的编码方式,每个字符所对应的字节数也不尽相同。

  • 编码方式
    Windows环境下的GBK和unix环境下的UTF-8对于英文字符均使用相同的单字节的编码方式(即ASCII),而对于中文字符的编码处理并不相同:

    • GBK:使用定长的双字符编码。因此由于等宽字体中,中文字符的宽度是英文字符的两倍,同时字节也是两倍,所以最终得到了正常的打印效果。
    • UTF-8:使用变长的字符编码。

使用以下的函数来按照小端序打印字符串"中文"

void print_string_bytes(string str) {
	for (int i = 0; i < str.size(); ++i)
		cout << hex << (unsigned)(unsigned char)str[i] << " ";
	cout << endl;
}

Windows平台的输出如下:

在这里插入图片描述
在线平台的转换结果相同

在这里插入图片描述
Unix平台的输出如下:

在这里插入图片描述
和使用在线平台进行UTF-8转换的结果相同

在这里插入图片描述

解决方法

根据上面的分析可以得出结论:unix平台下setw()对于中文字符处理的问题在于变长字符编码。因此只要将字符串转换为定长字符编码(比如说GBK),之后计算两者之间的差值修改setw()的参数即可。

使用以下函数计算两种编码方式之间字节数量的差值:

#include <codecvt>
#include <locale>

class chs_codecvt : public std::codecvt_byname<wchar_t, char, std::mbstate_t> {
 public:
  chs_codecvt() : codecvt_byname("zh_CN.GBK") {}
};

int encoding_diff(string str) {
  wstring_convert<codecvt_utf8<wchar_t>> cv1;
  wstring wstr = cv1.from_bytes(str);
  wstring_convert<chs_codecvt> cv2;
  return str.size() - cv2.to_bytes(wstr).size();
}

其中codecvt_byname的构造函数是protected的,因此需要自定义一个转换器类,继承codecvt_byname

需要说明的是,在Linux环境下需要进行locale-gen,编辑/etc/locale.gen,删除zh_CN.GBK前面的注释符号,并执行locale-gen命令。

最终将打印函数修改如下:

void aligned_string() {
  string cstr1 = "中文", cstr2 = "对齐";
  string estr1 = "English", estr2 = "Alignment";
  string cestr1 = "中文English", cestr2 = "对齐Alignment";
  cout << setw(20 + encoding_diff(cstr1)) << left << cstr1 << cstr2 << endl;
  cout << setw(20 + encoding_diff(estr1)) << left << estr1 << estr2 << endl;
  cout << setw(20 + encoding_diff(cestr1)) << left << cestr1 << cestr2 << endl;
}

打印效果如下:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值