当前位置: 首页 > 工具软件 > UTF-8 CPP > 使用案例 >

C++ 字符编码转换之UTF-8/UTF-16/UTF-32

梁骞仕
2023-12-01

字符编码

C++编码格式

参考链接(什么是辅助平面)
参考链接(什么是UCS-2)
window内部编码格式
UCS-2对每一个Unicode码位使用2bytes字集(16位bit);
UCS-4对每一个Unicode码位使用4bytes字集(32位bit);
UTF-16可看成是UCS-2的父集。在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。现在若有软件声称自己支持UCS-2编码,那其实是暗指它不能支持在UTF-16中超过2bytes的字集。对于小于0x10000的UCS码,UTF-16编码就等于UCS码。
UTF-32 原本是 UCS-4 的子集,但JTC1/SC2/WG2声明,所有未来对字符的指定都将会限制在BMP及其14个补充平面。于是就现状而言,除了 UTF-32 标准包含额外的 Unicode 意涵,UCS-4 和 UTF-32 大体是相同的。

编码转换

参考链接(编码转换)
C++11新增的std::wstring_convert可以很方便地在std::string和std::wstring之间进行转换。
将std::wstring转换成以UTF-8编码的std::string:

std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::string string = converter.to_bytes(L"这是一个宽字符串");

将UTF-8编码的std::string转换成std::wstring:

std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::wstring wide_string = converter.from_bytes("\xe5\xad\x97\xe7\xac\xa6\xe4\xb8\xb2”);  //字符串的内容为“字符串”

std::wstring_convert使用模板参数中指定的codecvt进行实际的转换工作,也就是说,std::string使用哪种字符编码由这个codecvt来决定。上面的例子用的是std::codecvt_utf8,即UTF-8编码。理论上,指定不同的codecvt,即可支持各种字符编码。但是,如何得到合适codecvt则是不小的问题。

codecvt

template <class internT, class externT, class stateT> class codecvt;

codecvt standard facet 的作用是在两种不同的字符编码之间转换:内部字符类型(internT:通常为某种类型的宽字符)和外部字符类型(externT:通常代表多字节序列)。
codecvt类模板有一个受保护的析构函数:程序只能构造派生类的对象,或是在locale对象中的对象(通过use_facet)。
所有standard locale 对象至少支持codecvt类模板的以下方面实例化(作为ctype类别的一部分):

facets in locale objectsdescription
codecvt<char,char,mbstate_t>不执行转换
codecvt<wchar_t,char,mbstate_t>在本机宽字符集和窄字符集之间转换
codecvt<char16_t,char,mbstate_t>在UTF16和UTF8编码之间转换
codecvt<char32_t,char,mbstate_t>在UTF32和UTF8编码之间转换

来看一下std::wstring_convert其中一个构造函数:

wstring_convert(codecvt* pcvt = new codecvt);

在构造std::wstring_convert对象的时候需要传入一个codecvt对象的指针,如果没有传入,则默认使用new codecvt来创建。std::wstring_convert自行维护codecvt对象的生命周期,它的析构函数会调用delete操作符来删除该对象。这就限制了只能使用通过new操作符来创建的codecvt,而不能使用从std::locale中获取的codecvt。
在C++标准提供的codecvt中,能够直接用于std::wstring_convert的只有三个:std::codecvt_utf8std::codecvt_utf16以及std::codecvt_utf8_utf16。可见,标准只支持UTF族的字符编码。

为了获取其它字符编码的codecvt,需要使用std::codecvt_byname,这个类可以通过字符编码的名称来创建一个codecvt。这看起来挺不错,但遗憾的是,字符编码的名称并没有统一的标准,各个平台的支持情况都不一样。
例如,在Windows下可以使用“chs”来创建简体中文编码的codecvt;
在Mac OS X下则要使用“zh_cn.gb2312”;甚至在Mac OS X下,即使成功创建了这个codecvt,它也不能正常地转换。

下面以Windows为例,说明如何将std::codecvt_byname用于std::wstring_convert。由于历史原因,std::codecvt_byname的析构函数是protected的,std::wstring_convert不能对它调用delete,所以首先要自行定义一个类来继承std::codecvt_byname:

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

chs_codecvt的默认析构函数是public的,从而让std::wstring_convert可以删除它。为方便起见,在chs_codecvt的构造函数中,直接把“chs”传给了std::codecvt_byname。
接下来的用法跟本文开头的例子基本一致:

std::wstring_convert<chs_codecvt> converter;
std::string string = converter.to_bytes(L"你好");
std::wstring wide_string = converter.from_bytes("\xc4\xe3\xba\xc3”);  //字符串的内容为“你好”

综上所述,只有UTF编码的转换是完全符合C++标准并且真正能够跨平台的,使用这些国际化的字符编码能够减少很多不必要的麻烦。

示例

// codecvt_utf8: writing UTF-32 string as UTF-8
#include <iostream>
#include <locale>
#include <string>
#include <codecvt>
#include <fstream>

int main ()
{
  std::u32string str ( U"\U00004f60\U0000597d" );  // ni hao (你好)

  //转换成char32_t
  std::locale loc (std::locale(), new std::codecvt_utf8<char32_t>);
  //创建一个文件流,向文件“test.txt”中写入文件 写入的编码类型是char32_t
  std::basic_ofstream<char32_t> ofs ("test.txt");
  //设置当前区域
  ofs.imbue(loc);

  std::cout << "Writing to file (UTF-8)... ";
  //将str写入文件
  ofs << str;
  std::cout << "done!\n";
  return 0;
}

imbue()

std::locale& imbue( const std::locale& loc );

替换当前区域设置。
参数loc:
新区域设置
返回值:
返回的上一个区域设置。

 类似资料: