

在当今全球化的时代,软件开发需要支持各种语言和字符集。Unicode 作为一种通用的字符编码标准,为解决多语言文本处理提供了有效的方案。C++11 引入了对 Unicode 的更好支持,特别是 Unicode string literals,使得开发者能够更方便地处理不同编码的字符串。本文将带领小白从入门到精通 C++11 Unicode string literals。
Unicode 是计算机领域的一项行业标准,它对世界上绝大部分的文字进行整理和统一编码。Unicode 的编码空间可以划分为 17 个平面(plane),每个平面包含 2 的 16 次方(65536)个码位。17 个平面的码位可表示为从 U+0000 到 U+10FFFF,共计 1114112 个码位。第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0),其他平面称为辅助平面(Supplementary Planes)。基本多语言平面内,从 U+D800 到 U+DFFF 之间的码位区段是永久保留不映射到 Unicode 字符的,所以有效码位为 1112064 个。
Unicode 的实现方式有多种,其中最流行的是 UTF - 8、UTF - 16、UCS2 和 UCS4/UTF - 32 等,细分的话还有大小端的区别。

C++11 引入了两种新的字符类型:char16_t 和 char32_t,它们各自被设计用来存储 UTF - 16 以及 UTF - 32 的字符。char16_t 为 16 位,char32_t 为 32 位。而对于 UTF - 8 编码的 Unicode 字符,C++11 仍然使用 8 位的 char 类型数组来表示。
示例代码如下:
#include <iostream>
int main() {
char16_t c16 = u'\u4f60'; // 存储 UTF - 16 编码的字符 '你'
char32_t c32 = U'\U00004f60'; // 存储 UTF - 32 编码的字符 '你'
std::cout << "char16_t size: " << sizeof(c16) << std::endl;
std::cout << "char32_t size: " << sizeof(c32) << std::endl;
return 0;
}C++11 新增了三种前缀来定义不同编码的字符或字符串:
u8:表示 UTF - 8 编码。u:表示 UTF - 16 编码。U:表示 UTF - 32 编码。加上之前 C++98 中使用的前缀 L 表示 wchar_t 类型的宽字符或字符串,以及普通字符串字面常量,C++ 中共有 5 种定义字符或字符串的方式。
示例代码如下:
#include <iostream>
int main() {
const char* s0 = "Hello"; // 普通字符串
const wchar_t* s1 = L"Hello"; // 宽字符串
const char16_t* s2 = u"Hello"; // UTF - 16 字符串
const char32_t* s3 = U"Hello"; // UTF - 32 字符串
const char* s4 = u8"你好"; // UTF - 8 字符串
return 0;
}可以直接使用新的前缀来创建不同编码的字符串字面量。
#include <iostream>
int main() {
const char* utf8_str = u8"这是一个 UTF - 8 字符串";
const char16_t* utf16_str = u"这是一个 UTF - 16 字符串";
const char32_t* utf32_str = U"这是一个 UTF - 32 字符串";
std::cout << "UTF - 8 字符串: " << utf8_str << std::endl;
// 对于 UTF - 16 和 UTF - 32 字符串,需要使用相应的输出方式,这里省略
return 0;
}当创建 Unicode 字符串字面值时,可以直接在字符串内插入 Unicode codepoints。C++11 提供了以下的语法:在 \u 之后跟 16 个比特的十六进制数值(不需要 0x 前缀),代表一个 16 位的 Unicode codepoint;如果要输入 32 位的 codepoint,使用 \U 和 32 个比特的十六进制数值。
示例代码如下:
#include <iostream>
int main() {
const char* utf8_str = u8"这是一个 Unicode 字符: \u2018";
const char16_t* utf16_str = u"这是一个更大的 Unicode 字符: \u2018";
const char32_t* utf32_str = U"这是一个 Unicode 字符: \U00002018";
std::cout << "UTF - 8 字符串: " << utf8_str << std::endl;
// 对于 UTF - 16 和 UTF - 32 字符串,需要使用相应的输出方式,这里省略
return 0;
}C++11 还引入了原生字符串字面量,即“所见即所得”,不需要在字符串中添加转义字符或其他的格式控制字符调整字符串的格式。原生字符串可以与 Unicode 字符串结合使用,定义 UTF - 8、UTF - 16 和 UTF - 32 的原生字符串。
语法格式如下:
u8R"delimiter(字符串)delimiter":UTF - 8 原生字符串。uR"delimiter(字符串)delimiter":UTF - 16 原生字符串。UR"delimiter(字符串)delimiter":UTF - 32 原生字符串。示例代码如下:
#include <iostream>
int main() {
const char* utf8_raw_str = u8R"XXX(I'm a "raw UTF - 8" string.)XXX";
const char16_t* utf16_raw_str = uR"*@(This is a "raw UTF - 16" string.)*@";
const char32_t* utf32_raw_str = UR"(This is a "raw UTF - 32" string.)";
std::cout << "UTF - 8 原生字符串: " << utf8_raw_str << std::endl;
// 对于 UTF - 16 和 UTF - 32 原生字符串,需要使用相应的输出方式,这里省略
return 0;
}在使用不同方式定义不同编码的字符串时,需要注意影响字符串处理和显示的几个因素:编辑器、编译器和输出环境。
代码编辑器采用何种编码方式决定了字符串最初的编码。比如编辑器如果采用 GBK,那么代码文件中的所有字符都是以 GBK 编码存储。当编译器处理字符串时,可以通过前缀来判断字符串的编码类型,如果目标编码与原编码不同,则编译器会进行转换。
示例代码及问题分析:
// 代码文件为 GBK 编码
#include <iomanip>
#include <iostream>
using namespace std;
int main() {
const char* sTest = u8"你好";
for (int i = 0; sTest[i] != 0; ++i) {
cout << setiosflags(ios::uppercase) << hex << (uint32_t)(uint8_t)sTest[i] << " ";
}
return 0;
}
// 编译选项:g++ -std=c++0x -finput-charset=utf - 8 test.cpp如果使用上述编译选项,编译器会认为代码文件是 UTF - 8 编码,但实际上是 GBK 编码,可能会导致编译器出现错误的认知,输出的码值可能是 GBK 的码值。正确的做法是使用 -finput-charset=gbk,让编译器按照 GBK 编码去处理代码文件。
编译器负责将代码文件中的字符串按照指定的编码进行处理。在 GCC 中,可以使用 -finput-charset=charset 设置输入字符集,用于将输入文件的字符集翻译为 GCC 使用的源字符集;使用 -fexec-charset=charset 设置执行字符集;使用 -fwide-exec-charset=charset 设置宽执行字符集。
字符串的正确显示依赖于输出环境。C++ 输出流对象 cout 能够保证的是将数据以二进制输出到输出设备,但输出设备(比如 Linux Shell 或者 Windows console)是否能够支持特定的编码类型的输出,则取决于输出环境。比如 Linux 虚拟终端 XShell,配置终端编码类型为 GBK,则无法显示 UTF - 8 编码的字符串。
C++11 在标准库中增加了一些 Unicode 编码转换的函数,开发人员可以使用这些函数来完成各种 Unicode 编码间的转换。
#include <iostream>
#include <cwchar>
#include <clocale>
#include <cstdlib>
#include <string>
int main() {
std::setlocale(LC_ALL, "");
const char* mbstr = "你好";
char16_t wc[10];
std::mbstate_t state = std::mbstate_t();
std::size_t len = std::mbrtoc16(wc, mbstr, sizeof(wc), &state);
if (len != static_cast<std::size_t>(-1)) {
std::cout << "转换成功" << std::endl;
}
return 0;
}#include <iostream>
#include <cwchar>
#include <clocale>
#include <cstdlib>
#include <string>
int main() {
std::setlocale(LC_ALL, "");
char16_t wc = u'你';
char mb[10];
std::mbstate_t state = std::mbstate_t();
std::size_t len = std::c16rtomb(mb, wc, &state);
if (len != static_cast<std::size_t>(-1)) {
std::cout << "转换成功" << std::endl;
}
return 0;
}#include <iostream>
#include <cwchar>
#include <clocale>
#include <cstdlib>
#include <string>
int main() {
std::setlocale(LC_ALL, "");
const char* mbstr = "你好";
char32_t wc[10];
std::mbstate_t state = std::mbstate_t();
std::size_t len = std::mbrtoc32(wc, mbstr, sizeof(wc), &state);
if (len != static_cast<std::size_t>(-1)) {
std::cout << "转换成功" << std::endl;
}
return 0;
}在实际项目中,使用第三方库如 ICU(International Components for Unicode)可以大大简化 Unicode 字符串的处理。ICU 提供了一套完整的 Unicode 支持,包括字符串处理、格式化、排序等功能。
示例代码如下:
#include <unicode/unistr.h>
#include <unicode/ustream.h>
#include <iostream>
int main() {
icu::UnicodeString unicodeStr = UNICODE_STRING_SIMPLE("Hello, 世界!");
// 输出 UnicodeString
std::cout << unicodeStr << std::endl;
// 转换为 UTF - 8 编码的 std::string
std::string utf8Str;
unicodeStr.toUTF8String(utf8Str);
// 输出 UTF - 8 字符串
std::cout << utf8Str << std::endl;
return 0;
}C++11 引入的 Unicode string literals 为开发者提供了更强大的 Unicode 支持,通过新的字符类型和字符串前缀,能够方便地处理不同编码的字符串。同时,需要注意编辑器、编译器和输出环境对字符串处理的影响,以及掌握 Unicode 编码转换的方法。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。