重活一世,我终于把 C++ 编码与 std::string 的关系扒透了

张开发
2026/6/10 10:13:19 15 分钟阅读
重活一世,我终于把 C++ 编码与 std::string 的关系扒透了
目录一、编码的官方定义二、编码的分类三、不同编码字符串的表示与打印规则3.1 字符串表示规则3.2 字符串打印规则四、字符编码与存储规则4.1 UTF-8的存储规则4.2 编码设计的连续性一、编码的官方定义编码是按照预定规则将信息、数据或符号映射为另一种表示形式的系统化规则体系。在计算机领域通常特指字符编码将字符集中的每一个字符映射为唯一二进制序列的标准规则。二、编码的分类早期计算机仅支持 ASCII 编码它是一套仅用于映射英文字母、数字与基础符号的基础字符集。随着计算机在全球范围的普及仅能覆盖英文字母、数字与基础符号的 ASCII 编码已无法满足世界各国语言文字的存储、显示与跨系统交互需求Unicode统一码也译作万国码应运而生。Unicode 的核心设计目标是为全球所有语言的每一个字符分配唯一、跨平台通用的码点Code Point从根源上解决不同编码体系带来的字符乱码、映射冲突问题。我们常说的UTF-8、UTF-16、UTF-32并非独立的字符集而是基于 Unicode 字符集设计的不同二进制编码实现规则核心作用是将 Unicode 字符的码点映射为计算机可存储、可传输的字节序列。从全球编码的实际使用占比来看UTF-8 长期占据绝对主导地位。据全球网站技术统计机构 W3Techs 的最新监测数据全球超 98% 的网站均采用 UTF-8 编码它也是本文讲解 std::string 字符处理逻辑时的核心编码基准。不同的字符编码不仅映射规则不同其底层依赖的字符存储单元类型也存在本质差异 —— 从单字节的 char适配 ASCII、UTF-8 等到多字节的wchar_t适配 Windows UTF-16 等各类字符类型的存储大小、编码解析逻辑完全不同。为了系统性解决这一问题C标准库设计了模板类 std::basic_string它以 “字符类型” 为核心模板参数通过实例化生成针对不同字符类型的字符串容器同时为了简化日常开发标准库将这些实例化后的类通过typedef定义为简洁的常用别名形成了下表所示的标准字符串类体系。编码类型字符串类原型UTF-8stringtypedef basic_stringchar string;UTF-16C11新增u16stringtypedef basic_stringchar16_t u16string;UTF-32C11新增u32stringtypedef basic_stringchar32_t u32string;宽字符wstringtypedef basic_stringwchar_t wstring;三、不同编码字符串的表示与打印规则针对不同字符类型的字符串C 规定了专用前缀用于区分编码与存储格式同时受标准库限制非 char 类型的字符串无法直接打印必须转换为 UTF-8 格式才能正常输出。3.1 字符串表示规则C 针对不同字符存储类型规定了对应的字符串字面量前缀用于明确编码与底层存储格式char 类型字符串无特殊前缀默认适配 UTF-8 / ASCII 编码char16_t 类型字符串前缀 u用于表示 UTF-16 编码字符串char32_t 类型字符串前缀 U用于表示 UTF-32 编码字符串wchar_t 宽字符字符串前缀 L用于表示平台相关的宽字符串。char16_t b[] u哈基米; char32_t c[] U哈基米; wchar_t d[] L哈基米;3.2 字符串打印规则char/UTF-8 字符串可以直接打印char16_t、char32_t、wchar_t 字符串不能直接用cout打印原因C 标准流cout没有提供对应的 operator 重载无法识别 UTF-16、UTF-32、宽字符格式。正确做法必须在程序内部将UTF-16/UTF-32/宽字符转换为 UTF-8格式再进行输出。由于标准输出流cout只支持char类型的字节流UTF-16、UTF-32、宽字符无法直接打印。正确的做法是在程序内部先将它们统一转换为 UTF-8 编码再输出。C 标准库提供了codecvt编码转换工具我们可以借助它完成各类编码到 UTF-8 的转换char16_tUTF-16→ 使用 codecvt_utf8char16_tchar32_tUTF-32→ 使用 codecvt_utf8char32_twchar_t宽字符→ 使用 codecvt_utf8wchar_t转换后得到普通 std::stringUTF-8 格式即可通过 cout 正常打印。#include iostream #include string #include locale #include codecvt using namespace std; int main() { // 定义不同编码的字符串 const char16_t* str16 u哈基米; // UTF-16 const char32_t* str32 U哈基米; // UTF-32 const wchar_t* wstr L哈基米; // 宽字符 // 定义转换器转为 UTF-8 wstring_convertcodecvt_utf8char16_t, char16_t cv16; wstring_convertcodecvt_utf8char32_t, char32_t cv32; wstring_convertcodecvt_utf8wchar_t, wchar_t cvw; // 转为 UTF-8 字符串char 序列 string u8_from_16 cv16.to_bytes(str16); string u8_from_32 cv32.to_bytes(str32); string u8_from_w cvw.to_bytes(wstr); cout UTF-16 转 UTF-8 u8_from_16 endl; cout UTF-32 转 UTF-8 u8_from_32 endl; cout 宽字符 转 UTF-8 u8_from_w endl; return 0; }四、字符编码与存储规则4.1 UTF-8的存储规则UTF-8是变长字符编码也是目前全球使用最广泛的Unicode编码实现方案。它会根据字符对应的 Unicode码点大小动态使用1~4 个字节存储单个字符。占用字节数编码二进制格式有效编码位对应 Unicode 码点范围1字节0xxxxxxx70 ~ 1272字节110xxxxx 10xxxxxx11128 ~ 20473字节1110xxxx 10xxxxxx 10xxxxxx162048 ~ 655354字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx2165536 ~ 2097151码点范围 0~127 的字符使用单字节存储最高位固定为 0与 ASCII 编码完全一致。所有 ASCII 文本无需转换即可直接用 UTF-8 解析这是 UTF-8 能快速普及的核心优势。码点超过 127 的字符按码点区间分配多字节存储码点 128 ~ 2047使用 2 字节编码覆盖拉丁文、希腊文等拼音文字码点 2048 ~ 65535使用 3 字节编码绝大多数常用中文、日文、韩文等东亚表意文字均落在该区间码点 65536 ~ 1114111使用 4 字节编码覆盖生僻字、emoji 表情、罕见符号等特殊字符。多字节结构中首字节标明长度后续字节均以 10 开头存储剩余编码位。这样既保证了前向兼容也避免与 ASCII 冲突。char[]存放的是 UTF-8 编码。一个汉字在 UTF-8 中通常占 2个字节所以你看到的是若干负数因为最高位为 1。char16_t[]存放 UTF-16。一个 BMP 区汉字占2 字节因此每个元素是一个 16 位的 Unicode 码点。char32_t[]和wchar_t[]在大多数 Linux 上等价于 32 位都按 UTF-32 存直接保存 Unicode 码点.4.2 编码设计的连续性char a[] 你好;cout a endl; a[1];cout a ; a[3]; cout a endl; a[1];cout a ; a[3]; cout a endl; a[1];cout a ; a[3]; cout a endl;你好匿好 匿耗腻耗 腻号逆号 逆浩如上我们将a的两个编码的值反复并打印得到这样一串文字。我们发现在UTF-8中每个汉字的存储根据发音具有连续性。

更多文章