C++:在Windows上使用Unicode的正确姿势

1. 需要关注的字符编码

在处理程序的字符编码问题时,应该关注3个阶段的编码设置。

  • 源代码的编码
    源代码本身就是文本文件。在编译时,编译器需要知道源代码的编码格式,如果编码错误,将无法通过编译。
  • 运行时的编码
    程序在处理字符时,需要确定字符用什么编码在内存中表示。
  • 终端的编码
    终端在向程序输入字符时,需要一种编码来将用户的输入转换成二进制。反之,在接受程序的输出时,也需要将二进制反编码成字符。
    接下来将详细说明关于这三项编码的设置。

2. 使用UTF-8编写源代码

2.1 Visual Studio 的默认编码

Visual Studio 的源代码保存格式默认为ANSI,即“服从系统区域设置”。中文版Windows的ANSI一般为GB系列编码,英文版的则是ASCII码,不同地区皆有不同。以ANSI保存的文件受系统区域设置影响,拷贝到不同区域设置的电脑上可能无法打开。

幸运的是,Unicode解决了这个问题。使用Unicode的文件在任何电脑上都能打开。Unicode有不同实现版本,这里我们将采用最常见的UTF-8格式(关于Unicode和UTF-8的关系和其他细节,本文不再赘述)。UTF-8分为带BOM和不带BOM的版本。在Windows上最简单的方式是将源代码保存为带BOM的版本,这样MSVC编译器能自动识别。当然,如果你是纯粹UTF-8的信仰者,可以在编译时加上/utf-8(方法看这里)来使用无BOM的UTF-8。

2.2 设置保存编码

设置方式非常简单,在Viusal Studio中,进入文件→高级保存选项。修改编码即可。
高级保存选项

如果没有高级保存选项,可以打开 工具→自定义→命令,选中菜单栏按钮,右边改成文件。
添加命令

点击“添加命令”,在“文件”类别中找到“高级保存选项”命令,添加即可。

3. 终端编码设置

在C++代码调用system函数,执行CHCP指令。

 system("CHCP 65001");

如此,终端的编码即被设为UTF-8。这条代码执行后,终端中如果出现“Active code page 65001”的提示,则说明设置成功。

4. Unicode字符的运行时处理

4.1 UTF-8格式的字符串字面量

u8 开头的字符串字面量在程序内部以UTF-8格式表示。如

u8"你好Unicode!"

这样的字符串要用 string 类型接收。

string hello = u8"你好Unicode!"

4.2 UTF-8格式的输入/输出流

如果你的终端已设置成UTF-8的格式,则可以用 string 类型接受由 std::cin 传入的UTF-8字符,向 std::cout 输出UTF-8字符。

string input;
std::cin >> input;
std::cout << u8"复读机说:" << input << endl;

你可能听说过还有一种针对 wstring 的输出流 std::wcout,但个人不建议使用 wcout,即使是要输出 wstring。原因有两点(wcin同理):

  • 根据cpp reference的说明,wcout 不能和 cout 直接混用。
  • 不同系统可能要为 wcout 绑定不同的 locale 信息,这样十分麻烦,且对可移植性有害。

如果要输出 wstring,建议先将其转换为 string 再输出到 cout 中。这并不麻烦,下面有演示。

4.3 多字节字符串的切分

根据UTF-8格式的要求,一个字符用1-4个字节表示,反映到string中,就是1-4个char表示一个字符。这种字符串一般称为多字节字符串。多字节字符串可以胜任各种对字符串的整体操作,比如拷贝、连接等。然而在需要遍历字符,对字符一一操作时,就需要进行切分。切分后的结果为wstring,即宽字符串 类型。wstring由wchar_t(宽字符)组成,其中每个 wchar_t 只表示一个字符。通过遍历wstring,即可对每个宽字符进行操作。

C++ 标准库中的codecvt头文件提供了多字节字符串与宽字符串的转换API。他们是
wstring_convert 模板类中的两个方法。

  • wstring from_bytes(string)
    将string(多字节字符串)切分为wstring(宽字符串)。
  • string to_bytes(wstring)string to_bytes(wchar_t)
    将wstring(宽字符串)或wchar_t(宽字符)转为string(多字节字符串)。

例如,将string转为wstring可以用如下代码。

#include <codecvt>
//......
using namespace std;
string input = u8"我是UTF-8字符串";
wstring_convert<codecvt_utf8<wchar_t>> converter;//一个wstring_convert对象可以多次使用
wstring output = converter.from_bytes(input);

然后,我们可以遍历wstring,将每个wchar_t转换成string,再一一输出至cout(不直接输出至wcout的原因前文已叙述)。

for (size_t i = 0; i < output.length(); i++) 
    cout << converter.to_bytes(output[i]) << endl;

输出如下

我
是
U
T
F
-
8
字
符
串

这是一个简单的宽字符/多字节字符串互相操作的例子。总体来说,需要严格区分字符与字符之间的边界的操作需要将string转为wstring,其他情况下一般使用string即可解决问题。

参考

https://docs.microsoft.com/en-us/cpp/build/reference/execution-charset-set-execution-character-set?view=vs-2019

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容