char16_t 与 char32_t
定义与作用
char16_t 和 char32_t 是 C++11 引入的两种新字符类型,分别用于明确表示 UTF-16 编码单元(至少 16 位)和 UTF-32 编码单元(至少 32 位),解决了 wchar_t 在不同平台上宽度不一致的跨平台痛点。
char16_t ch16 = u'飞'; // UTF-16 编码单元
char32_t ch32 = U'翔'; // UTF-32 编码单元
std::u16string s16 = u"飞翔科技";
std::u32string s32 = U"FEIXIANG";
| 字符类型 | 宽度 | 编码 | 字面量前缀 | 字符串类型 |
|---|---|---|---|---|
char | ≥ 8 位 | 实现定义(通常 ASCII/UTF-8) | 无 / u8(C++17) | std::string |
char16_t | ≥ 16 位 | UTF-16 | u | std::u16string |
char32_t | ≥ 32 位 | UTF-32 | U | std::u32string |
wchar_t | 实现定义 | 实现定义 | L | std::wstring |
核心原理
为什么需要 char16_t / char32_t
Unicode 编码单元与码点
类型关系
char16_t 和 char32_t 是独立的基础类型,不是 typedef。这意味着:
- 它们与
char/wchar_t不共享重载决议 - 指针类型不兼容(
char16_t*不能隐式转为char*) - 需要显式转换
// char16_t 和 char32_t 是独立类型
static_assert(!std::is_same<char16_t, unsigned short>::value
|| sizeof(wchar_t) != sizeof(char16_t),
"char16_t 不是 typedef");
// 指针类型互不兼容
char16_t* p16;
// char* p = p16; // 编译错误:类型不兼容
完整示例
示例一:多语言用户信息处理
场景说明:黄俪为飞翔科技设计国际化用户界面,需要正确处理中英日韩等多种语言字符。
#include <iostream>
#include <string>
#include <codecvt>
#include <locale>
#include <iomanip>
void showCharInfo(const std::string& label, const char16_t* s16) {
std::cout << label << ": ";
while (*s16) {
std::cout << std::hex << std::setfill('0')
<< std::setw(4) << static_cast<unsigned>(*s16) << " ";
++s16;
}
std::cout << std::dec << std::endl;
}
int main() {
std::cout << "===== 飞翔科技多语言支持 =====" << std::endl;
// ---- char16_t 与 UTF-16 ----
char16_t name16[] = u"孔蓝"; // UTF-16 字符串
std::u16string product16 = u"飞翔智能助手 v2.0";
std::cout << "\n[char16_t 数据]" << std::endl;
std::cout << "sizeof(char16_t) = " << sizeof(char16_t) << " 字节" << std::endl;
showCharInfo("孔蓝 (UTF-16)", name16);
// ---- char32_t 与 UTF-32 ----
char32_t name32[] = U"赵鸣"; // UTF-32 字符串
std::u32string slogan32 = U"愿你朝华相顾,愿你前程似锦。";
std::cout << "\n[char32_t 数据]" << std::endl;
std::cout << "sizeof(char32_t) = " << sizeof(char32_t) << " 字节" << std::endl;
for (const auto& ch : name32) {
if (ch == 0) break;
std::cout << "U+" << std::hex << std::setfill('0')
<< std::setw(8) << static_cast<uint32_t>(ch)
<< std::dec << " ";
}
std::cout << std::endl;
// ---- wchar_t 对比 ----
std::cout << "\n[平台对比]" << std::endl;
std::cout << "sizeof(char) = " << sizeof(char) << " 字节" << std::endl;
std::cout << "sizeof(wchar_t) = " << sizeof(wchar_t) << " 字节"
<< " (实现定义)" << std::endl;
std::cout << "sizeof(char16_t) = " << sizeof(char16_t) << " 字节"
<< " (始终16位)" << std::endl;
std::cout << "sizeof(char32_t) = " << sizeof(char32_t) << " 字节"
<< " (始终32位)" << std::endl;
std::cout << "\n[结论]" << std::endl;
if (sizeof(wchar_t) == 2) {
std::cout << " 当前平台 wchar_t 是 16 位 (如 Windows)" << std::endl;
std::cout << " wchar_t 宽度 == char16_t 宽度,但类型不同" << std::endl;
} else {
std::cout << " 当前平台 wchar_t 是 32 位 (如 Linux/macOS)" << std::endl;
std::cout << " wchar_t 宽度 == char32_t 宽度,但类型不同" << std::endl;
}
return 0;
}
预期输出(Windows):
===== 飞翔科技多语言支持 =====
[char16_t 数据]
sizeof(char16_t) = 2 字节
孔蓝 (UTF-16): 5b54 84dd
[char32_t 数据]
sizeof(char32_t) = 4 字节
U+00008d75 U+00009e23
[平台对比]
sizeof(char) = 1 字节
sizeof(wchar_t) = 2 字节 (实现定义)
sizeof(char16_t) = 2 字节 (始终16位)
sizeof(char32_t) = 4 字节 (始终32位)
[结论]
当前平台 wchar_t 是 16 位 (如 Windows)
wchar_t 宽度 == char16_t 宽度,但类型不同
逐段分析:
char16_t始终 16 位,char32_t始终 32 位——无论平台u"..."创建 UTF-16 字符串字面量,类型为const char16_t[N]U"..."创建 UTF-32 字符串字面量,类型为const char32_t[N]- Windows 上
wchar_t是 16 位(UTF-16),Linux/macOS 上是 32 位(UTF-32)——这就是引入char16_t/char32_t的根本原因 - 即使
wchar_t和char16_t宽度相同,它们仍是不同的独立类型,指针不能隐式互转
示例二:文本编码转换与字符计数
场景说明:李眉需要统计用户反馈中的实际字符数(码点数),区分 UTF-16 编码单元数和 Unicode 码点数。
#include <iostream>
#include <string>
#include <cstdint>
#include <vector>
// 统计 UTF-16 字符串中的 Unicode 码点数
// UTF-16 中,BMP 外字符用代理对表示(2 个 char16_t = 1 个码点)
std::size_t countCodePoints(const std::u16string& s) {
std::size_t count = 0;
for (std::size_t i = 0; i < s.size(); ++i) {
char16_t ch = s[i];
// 高代理项范围: 0xD800 - 0xDBFF
// 低代理项范围: 0xDC00 - 0xDFFF
if (ch >= 0xD800 && ch <= 0xDBFF) {
// 代理对的高位部分,跳过低位部分
++i; // 跳过下一个 char16_t(低代理项)
}
++count;
}
return count;
}
int main() {
std::cout << "===== 飞翔科技用户反馈分析 =====" << std::endl;
// 中文(均在 BMP 内)
std::u16string feedback = u"产品很好用,界面美观。";
std::cout << "\n[中文反馈]" << std::endl;
std::cout << "文本: 产品很好用,界面美观。" << std::endl;
std::cout << "char16_t 单元数: " << feedback.size() << std::endl;
std::cout << "Unicode 码点数: " << countCodePoints(feedback) << std::endl;
std::cout << "(中文在 BMP 内,每个字 = 1 个 char16_t)" << std::endl;
// 混合字符
std::u16string mixed = u"飞翔 FEIXIANG 2018";
std::cout << "\n[混合文本]" << std::endl;
std::cout << "文本: 飞翔 FEIXIANG 2018" << std::endl;
std::cout << "char16_t 单元数: " << mixed.size() << std::endl;
std::cout << "Unicode 码点数: " << countCodePoints(mixed) << std::endl;
// 显示字符与编码
std::cout << "\n[字符编码明细]" << std::endl;
char16_t special[] = u"飞";
std::cout << "'飞' 的 UTF-16: U+"
<< std::hex << static_cast<uint16_t>(special[0])
<< std::dec << std::endl;
// char32_t —— 每个字符恰好一个单元
std::u32string feedback32 = U"产品很好用,界面美观。";
std::cout << "\n[使用 char32_t 计数]" << std::endl;
std::cout << "char32_t 单元数 = Unicode 码点数 = "
<< feedback32.size() << std::endl;
std::cout << "(char32_t 定长编码,1 单元 == 1 码点)" << std::endl;
return 0;
}
预期输出:
===== 飞翔科技用户反馈分析 =====
[中文反馈]
文本: 产品很好用,界面美观。
char16_t 单元数: 11
Unicode 码点数: 11
(中文在 BMP 内,每个字 = 1 个 char16_t)
[混合文本]
文本: 飞翔 FEIXIANG 2018
char16_t 单元数: 16
Unicode 码点数: 16
[字符编码明细]
'飞' 的 UTF-16: U+98de
[使用 char32_t 计数]
char32_t 单元数 = Unicode 码点数 = 11
(char32_t 定长编码,1 单元 == 1 码点)
逐段分析:
- UTF-16 是变长编码:BMP(基本多语言平面)内的字符用 1 个
char16_t,BMP 外的字符用 2 个char16_t(代理对) - 中文常用汉字均位于 BMP 内,每个汉字恰好 1 个
char16_t std::u16string::size()返回的是char16_t单元数量,不是 Unicode 码点数量- 代理对检测:高位 0xD800–0xDBFF,低位 0xDC00–0xDFFF
char32_t是定长编码,每个char32_t恰好对应一个 Unicode 码点——适合需要码点级操作的场景
易错场景与面试考点
易错场景
| 场景 | 错误表现 | 正确做法 |
|---|---|---|
假设 char16_t 和 wchar_t 兼容 | 指针隐式转换失败 | 两者是独立类型,需显式转换或使用对应 API |
用 u16string::size() 当字符数 | BMP 外字符被计为 2 | 遍历时检测代理对,或用 char32_t |
遗漏字面量前缀 u / U | 编译错误,或类型不匹配 | char16_t* 必须用 u"...",char32_t* 必须用 U"..." |
混用 char 和 char16_t | 编码不一致导致乱码 | 统一编码策略,在边界处显式转换 |
codecvt 转换已弃用(C++17) | 使用了标记为 deprecated 的设施 | C++17 后考虑第三方库(如 ICU) |
常见面试问题
为什么 C++11 要引入
char16_t和char32_t?——wchar_t在不同平台宽度不同(Windows 16 位、Linux 32 位),导致跨平台行为不一致。char16_t和char32_t提供明确的宽度和编码语义。char16_t是uint16_t的 typedef 吗?——不是。char16_t是独立的基础类型,与uint16_t不共享重载决议,指针不兼容。UTF-16 中如何正确处理代理对(surrogate pair)?——检测当前
char16_t是否在高代理项范围(0xD800–0xDBFF),若是则与下一个char16_t(低代理项 0xDC00–0xDFFF)组合为一个码点。u8"..."字面量是什么?——C++17 引入的 UTF-8 字符串字面量,类型为const char[N](C++20 前)或const char8_t[N](C++20 起)。
小结
char16_t(UTF-16)和char32_t(UTF-32)提供跨平台一致的 Unicode 编码单元类型- 它们是独立基础类型,不是 typedef,与
wchar_t/char互不兼容 u"..."/U"..."是配套字面量前缀,分别生成const char16_t[N]和const char32_t[N]char32_t是定长编码(1 单元 = 1 码点),适合需要精确码点操作的场景char16_t是变长编码(BMP 外需代理对),适合与 Windows API 等 UTF-16 生态对接