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即可解决问题。