CppCon 2014 学习:Unicode in C++

在 Unicode 出现之前,计算机使用各种 单字节编码(Single-Byte Encodings) 来表示文本。理解这些编码对于学习字符集的演变过程很有帮助,以下是关键点的解释:

什么是单字节编码(Single-Byte Encoding)?

单字节编码是一种用 1 个字节(8 位)来表示一个字符的字符编码方案。由于一个字节最多可以表示 2⁸ = 256 个不同的数值,这就意味着:

  • 单字节编码最多支持 256 个字符
  • 这对于英文和一些西欧语言来说足够,但对于汉语、日语、韩语等字符数量庞大的语言来说远远不够。

常见的单字节编码示例

编码名覆盖语言/地区特点
ASCII英文最早的标准,只有 128 个字符(0-127)
ISO 8859-1 (Latin-1)西欧扩展 ASCII,支持法语、西班牙语等
Windows-1252西欧(Windows 上)类似 ISO 8859-1,但包含额外符号
KOI8-R俄语支持西里尔字母
MacRomanMac 系统上使用支持英文和西欧语言

问题在哪里?

单字节编码的最大问题是缺乏统一性和兼容性

  1. 同一个字节值在不同编码中可能代表不同字符
    • 例如,0xA0 在 ISO 8859-1 是不间断空格,但在 KOI8-R 中是俄文字母。
  2. 多语言支持困难
    • 只能支持一种语言或有限的语言集,无法在同一文档中混用多种语言。

Unicode 出现之前的过渡方案

为了解决多语言问题,一些区域开始使用多字节编码(如 Shift-JIS、GB2312、Big5),但这些也不兼容,依然存在歧义。

为什么后来采用 Unicode?

Unicode 的目标是:

  • 为全世界的每一个字符分配一个唯一编号(code point)
  • 支持几乎所有语言、符号和表情符号
  • 消除单字节编码之间的混乱

小结

对比项单字节编码Unicode
编码大小1 字节可变(UTF-8: 1–4 字节)
支持字符最多 256 个超过 140,000 个
多语言支持优秀
兼容性

ASCII 编码(American Standard Code for Information Interchange) 的一个结构化理解。我们来逐条解析和总结:

ASCII 是什么?

ASCII 是一种 7 位字符编码,即每个字符用 7 个比特位(bit) 表示,最多能表示 2⁷ = 128 个字符

ASCII 字符表组成

ASCII 一共定义了 128 个字符(编号 0–127),主要分为:

类型数量范围举例
控制字符32 个0–31 以及 127换行(LF, 10),回车(CR, 13),删除(DEL, 127)等
可打印字符95 个32–126英文大小写字母、数字、标点、符号等

你写的字符表展示:

 !"#$%&'()*+,-./0123456789:;<=>?
 @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
 `abcdefghijklmnopqrstuvwxyz{|}~

这是 ASCII 中的可打印字符,从代码 32 到 126,对应十六进制为 0x200x7E

“Hello!” 在 ASCII 中的编码过程

你提供的流程描述了字符串 "Hello!" 的 ASCII 编码转换过程:

字符十六进制十进制
H0x4872
e0x65101
l0x6C108
l0x6C108
o0x6F111
!0x2133
\00x000

\0 是字符串结束符(null terminator),在 C 语言等系统级语言中常用来标记字符串的结尾。

ASCII 的特点总结

你这段也很好地总结了 ASCII 的关键特点:

  • A 7-bit character encoding
    → 只使用 7 位,适合老式硬件和英语使用场景。
  • 32 control characters
    → 不可打印的,用于控制设备或格式(如换行、退格等)。
  • 95 printable characters
    → 英文字符、数字、标点和符号。
  • Near-ubiquitous, but often with substitutions or extensions
    → 在全世界广泛使用,但经常有各种扩展版本(如 ISO 8859-1、Windows-1252)来支持更多字符。
  • Great for English; not so great for most other languages
    → 适合英语,不适合中文、日文、阿拉伯文等。

小结一句话:

ASCII 是一种为英文设计的早期字符编码标准,它简单、高效、广泛,但无法满足多语言时代的需求。

Extended ASCII(扩展 ASCII) 是对原始 7 位 ASCII 的一个重要扩展和演变阶段。

什么是 Extended ASCII?

Extended ASCII 是在原始 7-bit ASCII(0–127) 基础上,使用整个 8-bit(0–255)范围,扩展出 额外 128 个字符(128–255)

  • 原始 ASCII(0–127)仍保留
  • 新增了更多的符号、语言字符、图形字符等

为什么需要扩展?

7 位 ASCII 无法满足其他语言(如法语、德语、西班牙语,甚至图形字符)的需求。不同厂商和标准组织为了各自地区的需求,定义了不兼容的扩展版本

常见的 Extended ASCII 编码集

你列出的项目都是 Extended ASCII 的不同实现:

1. IBM Code Pages(代码页)

IBM 使用了多个所谓的“代码页(Code Page)”,为不同语言或平台定义字符集。

  • CP437(Code Page 437)
    • 最早的 IBM PC 编码
    • 名称:“Latin US”
    • 含有 ASCII + 图形符号 + 拉丁字符(如 ç, ü)
    • 常用于 MS-DOS 控制台字符
  • CP737
    • 为希腊语设计
      IBM 的代码页都保留 ASCII 前 128 项,但扩展部分不同。

Mac OS Roman

  • 苹果公司用于经典 Mac OS 的编码
  • 包含西欧语言的特殊字符(如 ñ、é、ä 等)
  • 与 Windows 编码不兼容

3. DEC Multinational Character Set

  • 数字设备公司(DEC)开发的字符集
  • 用于 VT220 终端
  • 比 CP437 更接近 ISO 标准

4. ISO/IEC 8859 系列

  • 由国际标准组织制定的标准化编码
  • 系列包括多个子集,每个针对一种语言区域
    | 编号 | 名称 | 支持语言 |
    | ----------- | -------- | -------------- |
    | ISO 8859-1 | Latin-1 | 西欧(法语、德语、西班牙语) |
    | ISO 8859-5 | Cyrillic | 俄语等 |
    | ISO 8859-6 | Arabic | 阿拉伯语 |
    | ISO 8859-7 | Greek | 希腊语 |
    | ISO 8859-15 | Latin-9 | 拉丁语变体,支持 € 符号 |

Extended ASCII 的问题

  • 不兼容:不同编码使用相同的 128–255 范围表示不同字符
  • 容易乱码:用错误的编码打开文件就会显示错误字符
  • 无法统一多语言:一次只能支持一个区域的语言

这就是 Unicode 要解决的问题!

Unicode 统一了所有字符编码,让你不用再猜“这 0xA0 是 ç 还是 Ё 还是 Ά?”

总结一句话

Extended ASCII 是多个非统一的 8-bit 编码方案,它们试图通过添加 128 个字符来支持更多语言,但缺乏标准化,最终被 Unicode 取代。

你展示的是 Code Page 437(CP437)扩展字符部分(代码 128–255)。这是 IBM 在 1981 年为初代 IBM PC 所设计的 第一个扩展 ASCII 编码,也被称作 “OEM-US” 或 “DOS Latin US”

理解 Code Page 437 的结构

范围划分:

范围(十进制)内容类型说明
0–31控制字符与 ASCII 控制字符一致(如回车、换行)
32–126可打印字符标准 ASCII(如英文字母、数字、标点)
127DEL删除控制符
128–255扩展字符如你展示的字符集,包括拉丁扩展字母、图形符号、数学符号等

示例分类(你展示的内容的结构)

这些字符大致可以分成以下几类:

拉丁字母扩展和语言符号(多语言支持)

Ç ü é â ä à å ç ê ë è ï î ì Ä Å É æ Æ ô ö ò û ù ÿ Ö Ü
á í ó ú ñ Ñ ª º ¿

用于西欧语言,如法语、德语、西班牙语、葡萄牙语、北欧语等。

货币符号和其他文字符号

¢ £ ¥ ₧ ƒ
  • 美分、英镑、日元、西班牙比塞塔、函数符号(ƒ)

░图形块符号(用于绘制图形界面)

░ ▒ ▓ │ ┤ ╡ ╢ ╖ ╕ ╣ ║ ╗ ╝ ╜ ╛ ┐
└ ┴ ┬ ├ ─ ┼ ╞ ╟ ╚ ╔ ╩ ╦ ╠ ═ ╬
╧ ╨ ╤ ╥ ╙ ╘ ╒ ╓ ╫ ╪ ┘ ┌ █ ▄ ▌ ▐ ▀

这些字符被用于 DOS 程序中绘制“图形界面”,比如边框、阴影、框架等。

数学和希腊字符

α ß Γ π Σ σ µ τ Φ Θ Ω δ ∞ φ ε ∩ ≡ ± ≥ ≤ ⌠ ⌡ ÷ ≈ ° ∙ · √ ⁿ ² ■

这些字符可用于简单的科学或数学表达式,常用于教育、科学程序。

特点总结:

  • 兼容 ASCII 前 128 项
  • 适合英文、部分西欧语言、数学符号、图形界面绘制
  • 不支持东亚文字(如中文、日文)
  • 不兼容其他编码(比如 ISO-8859 系列)

为什么重要?

  • DOS 系统 中广泛使用
  • 是最早期的“字符图形 UI”的基础(图形界面在没有图像支持的情况下)
  • 历史意义重大,是许多后续编码(如 Windows-1252)的基础

一句话总结:

Code Page 437 是 IBM PC 的默认字符集,它扩展了 ASCII,加入图形符号和西欧语言支持,是早期计算机文本界面的基础。

ISO/IEC 8859 编码系列 的各个变体,这是在 Unicode 出现前,最系统性地支持多种语言字符的一套 单字节字符集标准

什么是 ISO/IEC 8859?

ISO/IEC 8859 是一套 8-bit 单字节字符编码标准,由国际标准化组织(ISO)和国际电工委员会(IEC)联合制定。

  • 前 128 个字符(0–127)与 ASCII 完全相同
  • 后 128 个字符(128–255)用于特定语言或区域的扩展字符
    每一个变体称为一个“部分(Part)”,分别用于不同语言或文化区域。

各种 ISO 8859 编码变体的解释

编码名名称支持语言/区域
ISO 8859-1Latin-1西欧语言(法语、德语、西班牙语、葡萄牙语等)
ISO 8859-2Latin-2中欧语言(波兰语、捷克语、斯洛伐克语、匈牙利语等)
ISO 8859-3Latin-3南欧语言(马耳他语、土耳其语部分)
ISO 8859-4Latin-4北欧语言(波罗的语族,早期的爱沙尼亚语等)
ISO 8859-5Latin/Cyrillic斯拉夫语言(俄语、乌克兰语、保加利亚语等)
ISO 8859-6Latin/Arabic阿拉伯语
ISO 8859-7Latin/Greek希腊语
ISO 8859-8Latin/Hebrew希伯来语
ISO 8859-9Latin-5土耳其语(Latin-1 的土耳其变体)
ISO 8859-10Latin-6北欧语言(更现代、更准确的支持)
ISO 8859-11Latin/Thai泰语(很少使用,通常用 TIS-620 替代)
ISO 8859-13Latin-7波罗的海国家(拉脱维亚、立陶宛、爱沙尼亚)
ISO 8859-14Latin-8凯尔特语言(威尔士语、布列塔尼语等)
ISO 8859-15Latin-9Latin-1 的修订版,加入 € 符号等
ISO 8859-16Latin-10东南欧语言(罗马尼亚语、克罗地亚语等)

注意:ISO 8859-12 没有定义,编号被跳过。

为什么要这么多变体?

因为每种语言都有一些特别的字符(例如:ş、č、ł、ß、€ 等),而单字节只能编码 256 个字符,所以:

  • 每一套编码只支持 一组相关语言
  • 无法像 Unicode 一样支持所有语言同时使用

局限性

问题描述
不兼容同一个编码点在不同变体中含义不同
无法混用多语言只能支持有限一组语言
不能编码亚洲语言不支持中文、日文、韩文等大型字符集

小结一句话:

ISO/IEC 8859 是一套为欧洲、中东和部分亚洲语言设计的单字节编码标准系列,它弥补了 ASCII 的不足,但因容量限制无法实现全世界语言的统一编码。

ISO/IEC 8859-1(简称 Latin-1)中 128–255(0x80–0xFF) 范围内的字符。这是 Unicode 出现之前,最广泛使用的 西欧语言单字节编码标准

什么是 ISO/IEC 8859-1(Latin-1)?

  • 一种 单字节字符编码(8-bit)
  • 与 ASCII 前 128 个字符(0–127)完全一致
  • 编号 128–255 用于西欧语言的扩展字符
  • 适用于西欧大部分语言,如英语、法语、德语、西班牙语、葡萄牙语、荷兰语等

你提供的 Latin-1 扩展字符分类解析

特殊符号和标点(0xA1–0xBF)

¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ® ¯
° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿
  • 货币符号:¢(美分)、£(英镑)、¤(一般货币)、¥(日元)
  • 排版标记:©(版权)、®(注册商标)、« »(法语书名号)
  • 数学符号:±(正负)、² ³(上标 2、3)
  • 分数:¼ ½ ¾
  • 特殊标点:¡ ¿(西班牙语倒问号和感叹号)

大写拉丁字母扩展(0xC0–0xDF)

À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï
Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß
  • 有重音的元音字母(法语、西班牙语、葡萄牙语等常见)
  • Æ、Ø、Þ、ß:来自北欧语言(如挪威语、德语)

小写拉丁字母扩展(0xE0–0xFF)

à á â ã ä å æ ç è é ê ë ì í î ï
ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ
  • 小写的重音元音与上面的大写对应
  • ñ(西语)、ç(法语/葡语)、ø(挪威语)、ý þ ÿ 等用于多种西欧语言

特别注意:

  • × 是乘号(不是小写 x)
  • ÷ 是除号
  • µ 是微符号,常用于“微米”、“微秒”
  • ß 是德语“Eszett”(尖音 s),发音近似“ss”

ISO 8859-1 与 Unicode 的关系

  • ISO 8859-1 中的字符 直接映射 到 Unicode 的前 256 个码位(U+0000 到 U+00FF)
  • 但是 Unicode 后来补充了更多字符,比如 €(欧元符号),这在 ISO 8859-1 中 是缺失的

为了解决这个问题,后来诞生了:

ISO 8859-15(Latin-9):一个 ISO 8859-1 的修订版,加入了欧元符号(€)等现代需求字符

一句话总结:

ISO 8859-1 是支持西欧语言的最广泛的单字节字符集,它扩展了 ASCII,包含重音字母、货币符号和数学标记,是现代 UTF-8 等编码的基础之一。

你列出的是 ISO/IEC 8859-5,也称为 Latin/Cyrillic,它是 ISO 8859 系列中专门为支持 斯拉夫语言(使用西里尔字母) 而设计的编码变体。

什么是 ISO/IEC 8859-5?

  • 一种 8-bit 单字节字符编码
  • 前 128 个字符(0–127)与 ASCII 完全相同
  • 后 128 个字符(128–255)用于 西里尔字母扩展
  • 主要用于:俄语、乌克兰语、保加利亚语、塞尔维亚语、马其顿语

你列出的字符解释

这部分是 ISO 8859-5 中 0xA0–0xFF(160–255) 的字符,按字形或语义分类如下:

🇷🇺 西里尔大写字母(А–Я)和变体

АБВГДЕЖЗИЙКЛМНОП
РСТУФХЦЧШЩЪЫЬЭЮЯ
  • 这些是俄语的标准大写西里尔字母

🇷🇺 西里尔小写字母(а–я)

абвгдежзийклмноп
рстуфхцчшщъыьэюя
  • 小写形式,对应上面的字母

特殊西里尔扩展字符(用于乌克兰语、马其顿语、塞尔维亚语)

Ё Ђ Ѓ Є Ѕ І Ї Ј Љ Њ Ћ Ў Џ
ё ђ ѓ є ѕ і ї ј љ њ ћ ў џ
  • 这些不是标准俄语中常用的字母,而是用于:
    • Ё, ё:俄语中的变音符号
    • Є, Ї, І:乌克兰语
    • Ј, Љ, Њ, Ћ, Ѓ:塞尔维亚语和马其顿语
    • Ў, ў:白俄罗斯语

其他符号

№ §
  • :俄语中的“编号”符号,常用于文档和编号,例如“№ 5”
  • §:段落符号,通常用于法律或文档引用

与 Unicode 对应关系

ISO 8859-5 中的字符在 Unicode 中都有直接映射。例如:

字符ISO 8859-5 编码(十六进制)Unicode 编码
А0xC0U+0410
я0xFFU+044F
0xB9U+2116

局限与取代

  • 不支持所有斯拉夫语变体(如非标准变音)
  • 不支持混合语言文档
  • 已被 Unicode(尤其是 UTF-8)取代,现在更推荐使用 Unicode 编码来表示西里尔字母

总结一句话:

ISO 8859-5 是专为使用西里尔字母的语言(如俄语、乌克兰语、塞尔维亚语)设计的单字节字符集,能够表示基本和扩展的西里尔字符,但已被 Unicode 所取代。

在使用 ISO/IEC 8859-5(Latin/Cyrillic) 或其他单字节编码时,如果不小心处理编码转换,相同的字节值可能会在不同编码中代表完全不同的字符。这正是“But You Have to Be Careful…”的警告核心。

一步一步理解你列出的内容:

示例 1:ISO/IEC 8859-5 编码中的字符

А → B0  
л → DB  
о → DE  
\0 → 00

这是指:

  • ISO 8859-5 中:

    • А(西里尔大写 A)= 0xB0
    • л(西里尔小写 l)= 0xDB
    • о(西里尔小写 o)= 0xDE
    • 字符串结束符 \0 = 0x00

组合起来:

"Ало\0" → B0 DB DE 00

这就是 “Ало”(“Hello” 的俄语变体)在 ISO 8859-5 编码下的字节序列。

但是小心!

如果用错误的编码方式(比如 ISO 8859-1 或 UTF-8)来解释这些字节,就会出错。

示例 2:同样的字节序列在其他编码中含义不同

B0 → °   (在 Latin-1 中)
DB DE → Ϋ ή(在 UTF-8 中)
➤ 在 Latin-1 中:
  • 0xB0°(度数符号)
➤ 在 UTF-8 中:
  • 0xDB 0xDE 是 UTF-8 编码中一个非英语字符:
    • 0xDBDE → U+03AB → Ϋ(希腊语:大写带变音的 upsilon)
    • 或者错误地读取成多个字符,变成乱码
      所以:
      如果你把 ISO 8859-5 的 “Ало” 误用 UTF-8 或 Latin-1 解码,就会显示成乱码或完全不同的字符!

Why You Have to Be Careful…

字节ISO 8859-5ISO 8859-1UTF-8 解码尝试
B0А°非法或乱码
DBл×开始多字节符号
DEоÞ非法或 Ϋ

结论总结:

在使用 ISO 8859-5(或任何单字节编码)时,一定要清楚使用的是哪种编码方式,否则在解码或显示时可能会误解成完全不同的字符,甚至出现乱码。

单字节编码(Single-Byte Encodings) 的优缺点。我们逐条解析,帮助你完全理解这段内容的意思。

单字节编码的核心特点解析

优点:为什么单字节编码“曾经很好用”

  1. Each character is the same size(每个字符大小相同)

    • 每个字符固定为 1 字节(8 位),便于处理。
    • 不像 UTF-8 那样,一个字符可能占用 1 到 4 个字节。
  2. The encodings are compact(编码很紧凑)

    • 占用空间小:每个字符仅 1 字节。
    • 非常适合英文和一些本地语言环境下的数据存储。
  3. String operations are generally straightforward(字符串操作简单)

    • 因为字符大小一致,可以直接计算字符串长度、定位字符、截取子串等。
    • 不需要处理“多字节字符”或“变长字符”的复杂逻辑。

缺点:为什么单字节编码后来被淘汰

  1. There aren’t enough code points to represent all characters

    • 1 字节 = 8 位 ⇒ 最多只有 256 个可能的编码
    • 前 128 个被 ASCII 占用,剩下的 128 个根本 无法覆盖所有语言和符号
    • 无法容纳:中文、日文、韩文、阿拉伯语、表情符号、特殊符号等
  2. Different encodings can be used for different sets of characters

    • 为了解决这个问题,各地区各语言设计了自己的编码(如 ISO 8859-1、8859-5、CP437、Shift-JIS、Big5)
    • 每个编码能支持某一种语言或字符集,但不通用
  3. …but this doesn’t work for all languages

    • 很多语言字符太多,1 字节根本不够用(例如:汉字有几千个)
    • 一些语言(如印地语、缅甸语)完全无法用单字节编码表示
  4. …and it makes text interchange difficult

    • 如果发邮件、存文件时不知道使用了哪种编码,接收方会看到乱码
    • 没有“统一标准”会导致跨系统、跨国家间的文字通信变得困难

总结一句话:

单字节编码结构简单、占用小、效率高,但它无法表示世界上所有的字符,使用多个不兼容的编码方案也让跨语言交流变得复杂——这就是后来 Unicode 和 UTF-8 诞生的根本原因。

Variable-Length Encodings(可变长度编码),这是现代字符编码(比如 UTF-8)非常重要的一个概念。我们一步步来解释清楚:

什么是 Variable-Length Encodings(可变长度编码)?

它是一种字符编码方式,其中不同的字符使用 不同数量的字节 来表示。
对比:

  • 单字节编码:每个字符固定使用 1 字节(如 ASCII、ISO 8859 系列)
  • 可变长度编码:每个字符可能使用 1、2、3,甚至更多字节(比如 UTF-8:1–4 字节)

优点(为什么使用可变长度编码)

  1. 高效存储英文字符

    • 常见字符(比如英文)只用 1 字节,节省空间
  2. 支持全球所有字符

    • 需要时能扩展到更多字节,用来编码中文、阿拉伯文、表情符号等
    • 例如 UTF-8 可以编码整个 Unicode 字符集(超过 140,000 个字符)
  3. 兼容 ASCII

    • 以 UTF-8 为例,它对 0x00–0x7F 范围的字符完全兼容 ASCII

缺点(需要注意的地方)

  1. 字符大小不固定,操作复杂

    • 不能用简单的数组索引来定位第 N 个字符
    • 需要解析整段字节流来识别字符边界
  2. 字符串长度 ≠ 字节数

    • 比如 "你" 是一个字符,但在 UTF-8 中占用 3 个字节(E4 BD A0
  3. 处理效率较低

    • 要判断一个字符用了多少字节,涉及查表或位运算

示例:UTF-8 是最常见的可变长度编码

字符范围字节数UTF-8 示例
U+0000–U+007F1 字节A0x41
U+0080–U+07FF2 字节é0xC3A9
U+0800–U+FFFF3 字节0xE4BDA0
U+10000+4 字节🧠0xF09FA6A0

和定长编码(如 UTF-32)对比

编码方式每字符占用空间效率操作复杂度
UTF-81–4 字节高(英文)
UTF-162 或 4 字节
UTF-324 字节简单

总结一句话:

可变长度编码(如 UTF-8)是一种用更少空间表示常见字符、同时又能兼容全世界字符的编码方式。它更灵活,但也需要更复杂的处理逻辑。

Shift-JIS 编码的总结,Shift-JIS 是日本常用的字符编码之一,用于表示日文文本。我们来详细解析理解它:

Shift-JIS 解析

1. 单字节和双字节混合编码

  • 有些字符用 单字节 表示(比如标准 ASCII 字符,或者一些日文假名)
  • 有些字符用 双字节 表示,两个字节连起来才能表示一个字符

2. 双字节字符的结构

  • 双字节字符由:
    • 首字节(lead byte)
    • 尾字节(trail byte) 组成
  • 首字节 的最高位(最高位就是最高的那一位二进制)总是设置为 1(即值≥128)
  • 尾字节 可以是任何值(不一定高位是 1)

3. 起始于7位 ASCII

  • 基础是标准的 7-bit ASCII(0–127)
  • 有一些替换:
    • \(反斜杠)被替换成 ¥(日元符号)
    • ~ 被替换成 (上标波浪线)

4. 编码结构复杂

  • 由于单字节和双字节混合
  • 以及不同字符范围重叠(Lead byte 范围和 Trail byte 范围不整齐)
  • 解析时必须严格按照规则判定当前字节是单字节还是首字节
  • 这导致编码的复杂度比较高,不像 ASCII 或 UTF-8 那样直观

总结

  • Shift-JIS 是日本特有的字符编码,能兼顾 ASCII 和日文字符
  • 它用单字节表示普通英文字符,用双字节表示汉字等复杂字符
  • 由于双字节和单字节混合,使得解析更复杂
  • 对系统兼容性要求较高,不同环境处理时要特别注意
 44D (Latin Capital D)
 84 44 → Д (Cyrillic Capital De)
 84 84 → т (Cyrillic Capital Te)

1. Shift-JIS 的单字节示例

  • 44(十六进制)在 ASCII 和 Shift-JIS 中都代表大写字母 D
    • 这部分是兼容 ASCII 的。

2. Shift-JIS 里的双字节字符

  • Shift-JIS 用双字节编码来表示日文汉字(Kanji)和假名,但它不支持表示西里尔字母(如 “Д”, “т”)。
  • 你给出的:
    84 44 → Д (Cyrillic Capital De)
    84 84 → т (Cyrillic Capital Te)
    
    这不属于 Shift-JIS 标准。
    这更像是某种 双字节编码里的示例(例如某些 EBCDIC 或其他编码),但不适用于 Shift-JIS。

3. 为什么会混淆?

  • Shift-JIS 是日本编码,不包含西里尔字母。
  • 西里尔字母 出现在像 ISO 8859-5、Windows-1251、KOI8-R 或 Unicode 里。
  • 两种编码体系根本不同,字节组合也不一样。

4. Shift-JIS 双字节编码的结构

  • 例如:
    • 0x81 0x40 代表一个日文汉字
    • 0x82 0xA0 代表假名等
      它不会用 84 4484 84 来表示西里尔字母。

总结

  • 你举的例子有点儿混淆了不同编码的字符。
  • Shift-JIS 中,44 是 ASCII D
  • 双字节编码不会表示西里尔字母。
  • 如果你看到 84 44 表示“Д”,那不是 Shift-JIS,可能是别的编码。

可变长度编码(Variable-Length Encodings) 的特点和优缺点,我们一步步拆解,帮助你更深入理解:

可变长度编码的特点理解

1. 扩展了字符空间

  • 单字节编码最多支持 256 个字符(2^8)
  • 两字节编码理论上能表示多达 32,896 个字符(为什么是这个数字,稍后讲)

    为什么不是 2^16 = 65,536?
    因为可变长度编码的设计通常不是简单地用所有两个字节组合,而是预留了一部分位作为控制信息(比如标记字符开始、长度等),所以实际可用码点数比纯粹的 2 字节数目少。

  • 实际编码标准(如 UTF-8、UTF-16)往往限制了可用字符数,但比单字节编码大得多,能支持全球几乎所有文字。

2. 某些基于字节的字符串操作还能继续使用

  • 例如 C 语言的 strcpy 之类的复制函数,如果只是复制字节流,是可以直接用的,不需要关注字符边界。
  • 但它们不理解“字符”的概念,只复制了“字节”,所以对于多字节字符来说,可能会把一个字符拆开。

3. 缺点:解析更复杂

  • 由于一个字符不再固定大小,要知道从哪个字节开始,读多少字节才能完整表示一个字符,必须解析字节内容
  • 有的编码(如 UTF-8)通过字节的高位模式来区分字符边界;有的编码(如 UTF-16)通过代理项对(surrogate pairs)处理扩展字符。

4. 许多常用字符串操作变得困难

  • 比如:
    • 计算“字符数”时,不能直接用字节数除以固定数字
    • 截取子串必须确保不切断多字节字符
    • 字符定位(第 N 个字符)可能需要从头扫描,逐个判断字符长度
  • 所以通常需要 专门的字符串库或者 API 来正确处理可变长度编码的字符串。

5. 编码标准多样化问题依然存在

  • 虽然 Unicode 统一了字符集,但实际编码格式有多种:
    • UTF-8(广泛使用)
    • UTF-16(Windows 内部常用)
    • UTF-32(固定长度,空间浪费大)
  • 应用、平台和语言支持不一,仍需注意编码转换和兼容性问题。

总结

方面优点缺点
字符数支持大幅度增加,可支持全球文字实际支持字符数受编码规范限制
操作便利性某些基于字节的操作仍可用许多常见操作(计数、截断)需要额外逻辑
实现复杂度灵活,节省空间(常用字符占用小字节数)解析复杂,性能可能受影响
标准多样性支持多种编码格式编码多样化导致互通困难

Unicode 1.0 的设计目标和基本特性,我们来逐条拆解,帮你理解:

Unicode 1.0 设计目标理解

1. One Character Encoding to Rule Them All…

  • 目标是设计一个 统一的字符编码,能够取代过去各种不同的编码方案,解决多编码互不兼容的问题。
  • 就像“魔戒”中“唯一统治所有戒指”的意思,Unicode 想成为“唯一编码标准”。

2. Universal(通用)

  • 必须能表示世界上所有常用的文字和符号。
  • 这就意味着不仅是拉丁字母,还包括汉字、阿拉伯字母、日文假名、符号、标点等等。

3. Efficient(高效)

  • 编码应方便解析,处理纯文本时不复杂。
  • 例如:避免过于复杂的字节结构,便于编程和实现。

4. Unambiguous(无歧义)

  • 每个 Unicode 码点(code point)唯一对应一个字符。
  • 不会出现同一个码点代表多个字符的情况,保证文本交换的准确性。

5. Characters, Not Glyphs(关注字符,不是字形)

  • Unicode 关注的是字符本身(抽象的文字单元),而不是具体的“显示形态”或“字形”。
  • 同一个字符可能有多种字体或样式,这不在 Unicode 的编码范围内。

Unicode 1.0 的具体特性

1. 16-bit code points

  • 每个字符用 16 位(2 字节) 编码。
  • 这样理论上可以表示最多 65,536 个字符(2^16)。
  • 实际字符数当时少于这个最大值。

2. 兼容 ISO/IEC 8859-1(Latin-1)

  • Unicode 中的前 256 个码点和 ISO-8859-1 完全对应,方便过渡。

3. UCS-2 编码

  • Unicode 1.0 使用 UCS-2(Universal Character Set 2-byte)编码方式。
  • 每个字符对应一个 16 位单元。
  • UCS-2 是固定长度编码,不支持扩展码点(即不支持超过 16 位的字符)。

总结一句话

Unicode 1.0 试图用固定 16 位长度编码来统一全球所有字符,简单、无歧义,兼容旧标准,但字符总数有限,未来需扩展。

你给出的这两组示例很好地展示了 Unicode 1.0 如何用统一的码点来表示不同的字符,包括英文字母和各种语言文字符号。我们来详细理解:

示例 1:英文 “Hello!”

  H       e      l      l     o      !     \0
  ↓       ↓      ↓      ↓     ↓      ↓      ↓
U+0048 U+0065 U+006C U+006C U+006F U+0021 U+0000
  • 每个字符都映射到一个 唯一的 Unicode 码点
  • 这里都是基本的拉丁字母和标点,码点在 0x0000 到 0x007F 范围内,跟 ASCII 完全一致。
  • \0 是字符串结束符,Unicode 里也用 U+0000 表示。

示例 2:多语言字符

X      Δ       Ж       ヸ     ᠼ      ☃       ڳ
↓      ↓       ↓       ↓      ↓      ↓       ↓
U+0058 U+0394 U+0436 U+30F8 U+183C U+2603 U+06B3
  • X:拉丁大写字母 X,码点 U+0058。
  • Δ (Delta):希腊字母,码点 U+0394。
  • Ж (Zhe):西里尔字母,码点 U+0436。
  • :日文片假名,码点 U+30F8。
  • :蒙古文字符,码点 U+183C。
  • ☃ (雪人):符号字符,码点 U+2603。
  • ڳ:阿拉伯扩展字符,码点 U+06B3。

说明

  • Unicode 1.0 使用统一的 16 位码点来表示所有字符,不管是哪种语言。
  • 这解决了过去单字节编码对多语言支持不足的问题。
  • 每个码点唯一对应一个字符,保证了无歧义。
  • 码点用 U+ 加上 4 位十六进制数字表示(Unicode 1.0 时为 16 位)。

总结

  • Unicode 1.0 实现了跨语言的字符统一编码
  • 英文、希腊、西里尔、日文、蒙古、符号、阿拉伯等字符都能用单一标准表示。
  • 这为全球文本交换奠定了基础。
    如果你想了解更多后续版本如何支持更多码点(超出 16 位)和新的编码方式(如 UTF-8, UTF-16),我也可以帮你讲解!

这段展示了 UCS-2 编码的一个简单例子:

UCS-2 编码简介

  • UCS-2 是 Unicode 1.0 中使用的编码方式。
  • 它用 固定的 2 字节(16 位) 表示一个字符。
  • 每个字符对应一个 16 位的码点,范围是 U+0000 到 U+FFFF。

你给的示例:

00 48  00 65  00 6C  00 6C  00 6F  00 21  00 00
 ↓      ↓      ↓      ↓      ↓      ↓      ↓
U+0048 U+0065 U+006C U+006C U+006F U+0021 U+0000
 ↓      ↓      ↓      ↓      ↓      ↓      ↓
 H      e      l      l      o      !      \0

解析:

  • 每个字符用两个字节表示,高字节是 00,低字节是 ASCII 码的值。
  • 例如:
    • 00 48 表示 U+0048,也就是大写字母 H
    • 00 65 表示 U+0065,英文小写 e
  • 这样,整个字符串 “Hello!” 被编码为一串 UCS-2 编码单元。
  • 末尾的 00 00 是字符串结束符(null terminator)。

关键点

  • UCS-2 固定宽度,每个字符占用 2 字节
  • 对于常用拉丁字母,前面高字节是 00,实际数据在低字节。
  • 简单易解析,但限制在 16 位范围内,不能表示超出 U+FFFF 的字符(比如 Emoji 或部分汉字扩展)。

总结

  • UCS-2 是固定宽度 2 字节编码,Unicode 1.0 的基础。
  • 优点:解析简单,每个字符直接对应 2 字节。
  • 缺点:字符集大小受限,不能支持所有 Unicode 字符。

经典的“字节序(endianness)”导致的误读问题,理解它对掌握编码很重要。:

正确解读 UCS-2 编码的关键:字节序

你给的字节序列(假设每两个字节是一组):

00 48  00 65  00 6C  00 6C  00 6F  00 21  00 00

1. 大端序(Big-Endian) vs 小端序(Little-Endian)

  • 大端序(Big-Endian):高位字节放前面,低位字节放后面。
    • 例如,00 48 按大端就是 0x0048,对应 Unicode U+0048(字母 ‘H’)
  • 小端序(Little-Endian):低位字节放前面,高位字节放后面。
    • 例如,00 48 按小端是 0x4800,对应 Unicode U+4800(𒀀,一种罕见的象形字符)

2. 写的这组字节被当作 小端序 解释时:

  • 00 48 变成了 0x4800 → 字符 “䠀” (U+4800)
  • 00 65 变成了 0x6500 → 字符 “攀” (U+6500)
  • 00 6C 变成了 0x6C00 → 字符 “氀” (U+6C00)
  • 00 6F 变成了 0x6F00 → 字符 “漀” (U+6F00)
  • 00 21 变成了 0x2100 → 字符 “℀” (U+2100)
  • 00 00 → 正常的 null 终止符

3. 但正确的 UCS-2 大端序 解释应该是:

  • 00 480x0048 → ‘H’
  • 00 650x0065 → ‘e’
  • 00 6C0x006C → ‘l’
  • 00 6C0x006C → ‘l’
  • 00 6F0x006F → ‘o’
  • 00 210x0021 → ‘!’
  • 00 00 → null terminator

小结:

解释方式码点字符
大端序(正确)U+0048, U+0065, …Hello!
小端序(错误)U+4800, U+6500, …䠀攀氀氀漀℀…

为什么重要?

  • 不同平台和文件可能用不同字节序存储 UCS-2/UTF-16 数据。
  • 如果读写时字节序错误,文本就会被误解成乱码或者错误字符。
  • 通常会用 BOM(Byte Order Mark,字节顺序标记) 来表明编码的字节序,帮助正确解析。

如果你需要,我可以帮你演示:

  • 如何判断文件是大端序还是小端序的 UCS-2;
  • UTF-16 是怎么解决这个字节序问题的。

UCS-2 的优缺点:

UCS-2 的优点(Advantages)

  1. 支持大量字符
    UCS-2 用 16 位编码,理论上最多支持 65,536 个字符,涵盖了很多语言的字符集。

  2. 多语言混合方便
    不同语言的字符都能放在同一个字符串里,支持多种文字混合。

  3. 编码简单
    每个字符对应一个固定的 2 字节单元,编码和解码都很直接,没有变长编码的复杂性。

UCS-2 的缺点(Disadvantages)

  1. 字节序问题
    不同机器可能采用不同的字节序(大端或小端),所以交换 UCS-2 文本时需要用 字节顺序标记(BOM) 来指明编码方式,否则会导致乱码。

  2. 空间占用大
    每个字符都占 2 字节,相比 ASCII(1 字节)或某些单字节编码,文本文件体积更大。对只用拉丁字母的文本来说,存储空间是 ASCII 的两倍。

  3. 不兼容传统字符串函数
    传统的 C 语言字符串函数(如 strcpystrlen)是基于单字节字符设计的,不能直接用来处理 UCS-2 编码的字符串,需要专门的宽字符函数或新的 API。

总结一句话

UCS-2 是简单且支持多语言的固定宽度 2 字节编码,但因字节序问题和存储效率,实际应用中有一定局限,需要后续更灵活的编码方式(比如 UTF-16)来改进。

在这里插入图片描述

“Code Space Usage”(码点空间使用)是指字符编码系统中,分配给字符的编码范围(码点空间)被如何使用的情况。理解它对掌握编码设计和效率很重要。

Code Space Usage 的核心点

  1. 码点空间大小

    • Unicode 最初(Unicode 1.0)使用 16 位码点,即最多可表示 65,536(2^16)个字符。
    • 但实际上,很多码点是保留的、未使用的或留作将来扩展。
  2. 实际使用的码点分布

    • 很多字符集中在几个特定区块(Block)里,如拉丁字母、汉字、希腊字母等。
    • 大量码点未被分配,尤其是在早期版本,码点使用效率不一定高。
  3. 编码空间分配策略

    • 保留区(Reserved Areas)用于未来添加字符。
    • 特殊区如私有使用区(PUA)允许用户自定义字符。
    • 扩展区允许 Unicode 通过多平面支持超过 65,536 个字符(后来的 UTF-16 通过代理对实现)。
  4. 实际需求 vs 码点空间

    • 大多数文字使用的字符数量远低于理论最大值。
    • 但为了支持全球所有语言、历史文字、符号等,Unicode 保留了大块空间。

为什么重要?

  • 效率与兼容性:合理分配码点空间可以提升编码效率和未来扩展能力。
  • 存储和传输:码点越多,编码设计越复杂,影响存储和传输效率。
  • 软件设计:程序需要处理不连续的码点范围,影响字符串处理复杂度。

几个术语 —— Code Space Usage, General Scripts, Symbols, CJK, Auxiliary, Private Use, 和 Compatibility —— 都是 Unicode 中码点空间(Code Space)分配的不同区域。这些区域体现了 Unicode 如何组织全球各种文字和符号,以高效、可扩展的方式编码。

下面是对这些术语的解释和它们在 Unicode 中所占用“码点空间”的总结:

Code Space Usage 概览(Unicode Code Space)

Unicode 的码点范围是:

U+0000 to U+10FFFF (共 1,114,112 个码点)

Unicode 将这片“码点空间”分为 17 个 Plane(平面),每个平面有 65,536 个码点(即 2^16)。

各类用途分类说明:

1. General Scripts(通用文字系统)

  • 包括世界各地常用的字母系统,如:
    • 拉丁(Latin)
    • 希腊(Greek)
    • 西里尔(Cyrillic)
    • 阿拉伯(Arabic)
    • 印地语(Devanagari)等
      主要位于:Plane 0(即 Basic Multilingual Plane,BMP),例如:
U+0000 – U+07FF、U+0900 – U+1FFF

2. Symbols(符号)

  • 包括:
    • 数学符号(+, =, √, ∑)
    • 货币符号(€, ¥, ₿)
    • 技术符号(⚙, ⏯)
    • 表情符号 Emoji(😂, ❤️)
      分布广泛,但许多集中在以下范围:
U+2000 – U+2BFF(符号和图形)
U+1F300 – U+1F9FF(Emoji 等)

3. CJK Ideographs(中日韩表意文字)

  • 大量汉字(含简体、繁体、日文汉字、韩文汉字)
  • 因数量庞大,Unicode 分配了多个区块和扩展块
    主要位于
U+4E00 – U+9FFF(CJK Unified Ideographs)
U+3400 – U+4DBF(扩展 A)
U+20000 – U+2A6DF(扩展 B)
U+2A700 – 等等,直到扩展 H

4. Auxiliary Areas(辅助区域)

  • 包括:
    • 标点符号
    • 标记(Combining marks)
    • 控制字符
    • 书写方向符号
      这些字符分散在多个区块内,常见于:
U+0000 – U+00FF(基本拉丁)
U+2000 – U+206F(通用标点)

5. Private Use Areas(PUA) 私有用途区

  • 用户或厂商可以定义自己的字符(比如企业内部编码)
  • 这些码点不会在 Unicode 官方分配具体字符
    位置如下:
U+E000 – U+F8FF(BMP 内 PUA)
U+F0000 – U+FFFFD(补充 PUA-A)
U+100000 – U+10FFFD(补充 PUA-B)

6. Compatibility Characters(兼容字符)

  • 为了兼容旧编码(如 GB2312、Big5、JIS、MacRoman 等)而保留的字符
  • 有些是视觉相似但逻辑上不同的字符(例如全角 vs 半角)
    常见位置:
U+F900 – U+FAFF(CJK Compatibility Ideographs)
U+2F800 – U+2FA1F(CJK Compatibility Ideographs Supplement)

总结表格(简化)

类别代表内容主要位置范围
General Scripts拉丁、希腊、阿拉伯等Plane 0(U+0000 – U+1FFF)
Symbols数学、技术、Emoji等符号U+2000 – U+2BFF, U+1F300+
CJK Ideographs中文汉字、日文、韩文汉字U+4E00+、U+3400+、U+20000+
Auxiliary控制字符、标点、格式控制等分散分布
Private Use自定义字符U+E000+、U+F0000+
Compatibility为兼容旧系统编码而设计的字符U+F900+、U+2F800+

这句话出自 Unicode 1.0 的介绍文档,它说:

(“Unicode 字符编码中保留了超过 30,000 个尚未分配的位置,为可预见的未来扩展提供了充足的空间。”)

1. 背景:Unicode 1.0 限制在 16 位空间(UCS-2)
  • Unicode 1.0 中,字符用 16 位码点 表示(UCS-2 编码),所以最大可支持 65,536(即 2^16) 个字符。
  • 在当时,已分配的字符 大约 34,000 多个。
  • 还有 30,000+ 的空位,这就是“unallocated character positions”。
2. 意图:强调“未来扩展够用了”
  • Unicode 团队希望说明:虽然现在还没用完所有空间,但这些未分配码点可以支持今后几十年新增的字符,比如:
    • 罕见古文字
    • 各国新增符号
    • 技术图形
    • 表情符号(Emoji 当时还没出现)
3. 实际发展:UCS-2 不够用了!
  • 到了后来的 Unicode 3.1(2001年),字符数就突破了 65,536 个限制。
  • 所以 Unicode 引入了 UTF-16(使用代理对),并扩展到了 21 位码点空间(最多支持 1,114,112 个字符)。
  • 也就是说,原来的 16 位设计已经不够用了。

总结

Unicode 1.0 虽然只支持最多 65,536 个码点,但当时只用了大约一半,其余 30,000+ 是保留空间,用于未来需要。它们相信这对“可预见的未来”是足够的。
不过,随着语言、技术和文化的不断演进,Unicode 后来扩展到了多平面(UTF-16 和 UTF-32),来适应更大的字符集。

Unicode 从 1.0 到 2.0 版本的重大扩展 的总结。

我们最终发现需要编码超过 65,536 个字符。

  • 原先 Unicode 1.0 使用 16 位(UCS-2),理论上支持 2¹⁶ = 65,536 个字符。
  • 但实际需求远远超过了这个数字,包括:
    • 世界上更多语言的字符(如:历史文字、亚洲文字变体)
    • 表情符号(emoji)
    • 技术、数学、音乐等领域的特殊符号

在 Unicode 2.0 中,码点空间被扩展了。

  • 解决方案不是继续塞进 16 位里,而是将码点范围扩展到 21 位
  • 新的码点范围:U+0000U+10FFFF(十六进制)

「Total code points: 1,112,064」

  • 计算方式:

    0x10FFFF - 0x0000 + 1 = 1,114,112
    再减去 2,048 个保留码点(例如 UTF-16 的代理对区域 D800–DFFF)
    → 实际可分配字符数:1,112,064 个码点
    
  • 减掉一些不能表示字符的特殊保留区域(比如 surrogate pair 区段)后,最终最多可以表示 1,111,998 个字符

16 位无法表示这些所有的码点,编码方式也必须随之改变。

  • 16 位的限制是最大只能表示 2¹⁶ = 65,536 种不同数值。
  • 于是,Unicode 2.0 引入了新方法来支持更多字符 —— 特别是 变长编码方式(Variable-Length Encoding)

如果 16 位不够用,那用 32 位可以吗?

  • 当然可以,这种编码方式称为 UCS-4,它用固定的 32 位(4 字节)表示每一个字符。
  • 不过,UCS-4 太浪费内存,尤其是在处理英文或 ASCII 字符时。
  • 所以,更常用的方案是:
    • UTF-16: 用两个 16 位单元(即代理对 surrogate pair)来编码大于 U+FFFF 的字符。
    • UTF-8: 可变长度编码,兼容 ASCII,最节省空间,最常用于网络传输和文件系统。

总结思维图

Unicode 1.0 → UCS-2 → 16 位 → 最多 65,536 个字符
     ↓
Unicode 2.0 → 扩展码点到 U+10FFFF → 总数 1,112,064
     ↓
新编码方式:
- UTF-8:1–4 字节,兼容 ASCII,广泛使用
- UTF-16:1–2 个 16 位单元(使用代理对)
- UCS-4:固定 32 位,太浪费,不常用

关于 UTF-32 编码方式 的示例,包括它的字节序、字符对应关系和 Unicode 码点的解释。以下是逐段详解,帮助你真正理解这些内容。

什么是 UTF-32?

  • UTF-32 是 Unicode 的一种编码方式。
  • 每个字符固定使用 4 个字节(32 位)来表示一个 Unicode 码点
  • 因为固定宽度,它的解析是最简单的,但也是最浪费空间的(尤其是处理英文文本时)。

示例拆解与理解

示例 1:

00 00 00 48   00 00 00 69   00 00 00 21
↓             ↓             ↓
U+0048        U+0069        U+0021
↓             ↓             ↓
H             i             !
解读:
  • 每 4 个字节表示一个 Unicode 码点(code point):
    • 0x00000048 = U+0048 → 字符 'H'
    • 0x00000069 = U+0069 → 字符 'i'
    • 0x00000021 = U+0021 → 字符 '!'

示例 2:加了 BOM(Byte Order Mark)

00 00 FE FF   00 00 00 48   00 00 00 69   00 00 00 21
↓                  ↓             ↓             ↓      
U+FEFF (BOM)     U+0048        U+0069        U+0021           
                   ↓             ↓             ↓   
                   H             i             !         

解读:
  • 前 4 个字节:00 00 FE FF 是 UTF-32 的 BOM(Big Endian 顺序)
  • 后续三个字符依然是 H、i、!,含义不变

示例 3:Little Endian 顺序的 UTF-32

FF FE 00 00   48 00 00 00   69 00 00 00   21 00 00 00
↓                  ↓             ↓             ↓     
U+FEFF (BOM)     U+0048        U+0069        U+0021              
                   ↓             ↓             ↓    
                   H             i             !             
解读:
  • 前 4 个字节 FF FE 00 00UTF-32 Little Endian 的 BOM
  • 每个字符的字节顺序反了,因为小端(LE):最低有效字节在前
    • 48 00 00 00 → 0x00000048 → 'H'

示例 4:包含补充字符(超出 BMP 范围)

FF FE 00 00   48 00 00 00   69 00 00 00   30 F4 01 00
↓
U+FEFF        U+0048        U+0069        U+1F430
↓             ↓             ↓             ↓
(BOM)         H             i             🐰(兔子)
解读:
  • 30 F4 01 00 → 小端 → 字节反转成 0x0001F430
  • 这是字符 🐰 的 Unicode 编码:U+1F430(兔子 emoji)

总结:UTF-32 特点

特点描述
每个字符占 4 字节编码、解码简单
固定宽度字符位置可随机访问
存储效率低英文文本会浪费空间
BOM 可选用于表示字节顺序(大端/小端)
表示范围广可覆盖完整的 Unicode 范围(U+0000 到 U+10FFFF)

UTF-32 编码的优缺点总结

UTF-32 的优点(Advantages)

1. 可表示非常多的字符

“A huge number of characters are representable”

  • UTF-32 使用固定的 32 位(4 字节)来表示一个字符,可以覆盖整个 Unicode 范围(超过 100 万个码点)。
  • 所以几乎所有人类书写系统的字符都能被表示。

2. 每个字符都是一个代码单元

“Each code point is representable using a single code unit”

  • 与 UTF-8 或 UTF-16 不同,UTF-32 不使用变长编码
  • 每个字符始终占用 4 个字节,这样可以直接通过偏移量定位第 N 个字符,不需要遍历整个字符串。

3. 但这在实践中不太有用

“…this isn’t all that useful in most practical string usage”

  • 虽然编码逻辑上很简单,但:
    • 大多数文本其实用不到几十万个字符。
    • 浪费空间大,内存占用多,尤其对英语等拉丁语系文本非常不划算。
  • 所以它在实际中不是最优解,仅在少数特定应用中才使用(如内部处理、大型字典表等)。

UTF-32 的缺点(Disadvantages)

1. 需要 BOM 来区分字节序

“Two possible byte orderings, so byte order mark (BOM) is required”

  • UTF-32 可用 大端(Big Endian)或小端(Little Endian) 存储。
  • 必须通过 BOM(如 00 00 FE FFFF FE 00 00)来指示使用哪种顺序。
  • 否则读取时可能出现乱码。

2. 字符太大,浪费空间

“Every character requires four bytes; wasting at least 11 bits per code point”

  • Unicode 实际只使用了大约 21 位(0x10FFFF 以内)。
  • UTF-32 每个字符却用了完整的 32 位(4 字节),所以每个字符多浪费了 至少 11 位
  • 对于大量英文或常见文字,这种浪费在大文本中会非常明显。

3. 不兼容 byte 操作

“None of the byte-oriented string functions (like strcpy) work with UTF-32 strings”

  • UTF-32 字符占 4 字节,常见的 strcpystrlen 等 C 语言函数是为 1 字节的字符串设计的(如 ASCII、UTF-8)。
  • 所以这些函数不能正确处理 UTF-32,必须使用特定的 Unicode 字符处理函数。

4. 与 UCS-2 不兼容

“Nothing that was written to work with UCS-2 works with UTF-32”

  • UCS-2 是早期 Unicode 方案,使用 固定的 2 字节编码
  • UTF-32 是 4 字节,因此:
    • UCS-2 的数据结构、函数、接口不能直接用在 UTF-32 上。
    • 必须完全重写或适配处理逻辑。

总结一句话:

UTF-32 优点是结构简单、编码直观;缺点是浪费内存、不兼容常用操作函数,实际中用途有限。

UTF-8 编码格式的结构说明表

UTF-8 编码基本原理:

UTF-8 是一种 可变长度编码(Variable-Length Encoding),它能将一个 Unicode 码点编码成 1~4 个字节,具体取决于该字符的码点范围。

表格详解:

Code Point 位数Unicode 范围字节数每个字节的格式
7 位U+0000 ~ U+007F1 字节0xxxxxxx
11 位U+0080 ~ U+07FF2 字节110xxxxx 10xxxxxx
16 位U+0800 ~ U+FFFF3 字节1110xxxx 10xxxxxx 10xxxxxx
21 位U+10000 ~ U+10FFFF4 字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

分步骤解释每种情况:

1 字节编码(ASCII 字符)
  • 码点范围U+0000U+007F(0 到 127)
  • 格式0xxxxxxx
  • 说明:兼容 ASCII(比如 'A' = 0x41 = 01000001
2 字节编码(拉丁扩展字符等)
  • 码点范围U+0080U+07FF(128 到 2047)
  • 格式110xxxxx 10xxxxxx
  • 举例:字符 éU+00E9,编码为 11000011 10101001(C3 A9)
3 字节编码(常用汉字、日文、韩文等)
  • 码点范围U+0800U+FFFF(2048 到 65535)
  • 格式1110xxxx 10xxxxxx 10xxxxxx
  • 举例:汉字 U+4F60,编码为 E4 BD A0
4 字节编码(Emoji 等补充字符)
  • 码点范围U+10000U+10FFFF
  • 格式11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  • 举例:Emoji 😂 是 U+1F602,编码为 F0 9F 98 82

UTF-8 的设计优势

  1. 兼容 ASCII:前 128 个字符编码相同。
  2. 节省空间:英文内容基本都是 1 字节。
  3. 无需 BOM:不像 UTF-16 或 UTF-32,UTF-8 的编码没有字节序问题。
  4. 自同步性强:容易在字节流中找出字符边界。
  5. 广泛使用:是网页、数据库、编程语言中的默认字符编码。

总结:

UTF-8 使用变长编码方式,根据字符 Unicode 范围使用 1~4 个字节编码。越常用的字符,所用字节数越少,空间效率高,兼容性好,是现代最流行的字符编码。

你这两段是用 UTF-8 编码表示字符串的例子

第一段 UTF-8 例子

48      65     6C      6C     6F     21     00
↓       ↓      ↓       ↓      ↓      ↓      ↓
U+0048 U+0065 U+006C U+006C U+006F U+0021 U+0000
 H      e      l       l      o      !      \0
  • 这是英文字符串 "Hello!" 的 UTF-8 编码。
  • 每个字符的 Unicode 码点都在 U+0000 ~ U+007F 范围内,所以每个字符对应一个字节,和 ASCII 完全相同。
  • 最后的 00 是字符串结束的空字符(null terminator)。
  • 这是 UTF-8 的基本用法:ASCII 码字符直接一字节表示。

第二段 UTF-8 例子

58       CE 94     D0 B6   E3 83 B8   E1 A0 BC   F0 9F 90 B0   00
↓          ↓         ↓        ↓           ↓          ↓          ↓       
U+0058    U+0394   U+0436   U+30F8      U+183C    U+1F430     U+0000
 X         Δ        Ж        ヸ          ᠼ           🐰         \0

详细拆解:

UTF-8 字节序列码点字符字节数说明
58U+0058X1ASCII 直接表示
CE 94U+0394Δ (希腊大写Delta)2两字节编码,11位码点(0x394)
D0 B6U+0436Ж (西里尔大写Zhe)2两字节编码
E3 83 B8U+30F8ヸ (日文片假名)3三字节编码,16位码点
E1 A0 BCU+183Cᠼ (蒙古字母)3三字节编码
F0 9F 90 B0U+1F430🐰 (兔子表情符号)4四字节编码,超出基本多语言平面(BMP)的码点

总结

  • UTF-8 的编码长度不固定:字符编码长度由码点大小决定,ASCII字符1字节,常见欧洲字符2字节,亚洲字符3字节,Emoji这类特殊字符4字节。
  • 每个字节的格式根据编码规则不同,比如:
    • 1字节:0xxxxxxx
    • 2字节:110xxxxx 10xxxxxx
    • 3字节:1110xxxx 10xxxxxx 10xxxxxx
    • 4字节:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  • 结尾的 00 是字符串结束符,和 C 语言的习惯一致。

UTF-8 优点

  • ASCII 兼容:ASCII 字符在 UTF-8 中编码完全相同,1字节表示,方便旧系统兼容。
  • 无需强制BOM:不强制要求文件头部有字节顺序标记(BOM),虽然可以用一个可选的 BOM EF BB BF
  • 支持大部分字节操作函数:像 strcpystrcatstrlen 等传统处理字符串的函数仍然能在字节层面操作 UTF-8 字符串(当然这些函数按字节操作,不是按字符)。
  • 存储效率高:对于大多数语言(尤其是英文及西欧语言),UTF-8 比 16 位或 32 位编码节省空间。

UTF-8 缺点

  • 可变长度编码:一个字符编码长度从1到4个字节不等,解析和处理时需要额外逻辑,比如计算字符长度需要遍历字节。
  • 少数语言效率低:某些语言(比如大量使用亚洲文字的语言)用 UTF-8 存储时,比 16 位编码(如 UCS-2/UTF-16)占用更多字节。

UTF-16 编码超出基本多语言平面(BMP,即 U+0000 到 U+FFFF 范围)以外的字符(即 U+10000 到 U+10FFFF)的方法,也就是 代理对(surrogate pairs) 的原理:

UTF-16 编码 U+10000 到 U+10FFFF 的字符

  1. 减去 0x10000

    • 先从目标码点减去 0x10000,得到一个 20 位的数字(范围是 0 到 0xFFFFF)。
  2. 拆分成两个 10 位部分

    • 将这个 20 位数字拆成两部分:
      • 高 10 位(上半部分)
      • 低 10 位(下半部分)
  3. 生成高位代理(lead surrogate)

    • 把高 10 位加到 0xD800,得到高位代理代码单元。
  4. 生成低位代理(trail surrogate)

    • 把低 10 位加到 0xDC00,得到低位代理代码单元。

举例说明(伪代码)

code_point = U+1F430  // 兔子 emoji

// 1. 减去 0x10000
U' = code_point - 0x10000  // 得到 0xF430

// 2. 拆分为两部分
high_10 = (U' >> 10) & 0x3FF  // 取高10位
low_10  = U' & 0x3FF          // 取低10位

// 3. 计算代理对
lead_surrogate = 0xD800 + high_10
trail_surrogate = 0xDC00 + low_10

// 结果是两个 16-bit 编码单元

这样就把一个超出 BMP 的 Unicode 码点,编码成了两个 16 位单元,存储在 UTF-16 中。

在这里插入图片描述

UTF-16 编码 U+1F378(🍸)的步骤

  1. 码点减去 0x10000
0x1F378 - 0x10000 = 0x0F378
  1. 将 0x0F378 转换成二进制(20位以内)
0x0F378 = 0b00001111001101111000
             (20位二进制数)
  1. 拆成高10位和低10位
  • 高10位(取高10位):0b0000001111 = 0x003C
  • 低10位(取低10位):0b001101111000 = 0x0378
  1. 加上代理区间起始码点
  • 高位代理 = 0xD800 + 0x003C = 0xD83C
  • 低位代理 = 0xDC00 + 0x0378 = 0xDF78

最终编码:

UTF-16 编码的 U+1F378 是:0xD83C 0xDF78

这样就正确地将 U+1F378 (🍸) 转成了 UTF-16 代理对!

UTF-16 优点

  • 与 UCS-2 以及基于 16 位编码的系统有一定兼容性。
  • 对某些语言(如汉字、日本假名等)文本,UTF-16 通常比 UTF-8 更节省空间。

UTF-16 缺点

  • 每个字符至少占 2 字节,代理对时占 4 字节。
  • 存在多种字节序,文件交换时需要使用 BOM(字节顺序标记)来标识字节顺序。
  • 传统的基于字节的字符串函数(如 strcpy)不能直接适用于 UTF-16。
  • UTF-16 是可变宽编码,但很多系统错误地将其当成固定宽编码处理。
  • 用二进制方式比较 UTF-16 字符串的结果,与 UTF-8 或 UTF-32 的比较结果不一致。

总结一下 UTF-8、UTF-16、UTF-32 的特点对比吧:

UTF-8

  • 优点
    • 通常比 UTF-16 和 UTF-32 更节省空间(尤其是英文和大部分西文文本)
    • 变长编码,灵活高效
    • 互联网和存储中最常用的编码格式
  • 缺点
    • 变长编码导致处理稍微复杂一些(解析、字符串操作需注意)

UTF-16

  • 优点
    • 对部分语言(如东亚语言)通常比 UTF-8 更节省空间
    • 与 UCS-2 兼容,有一定历史遗留优势
  • 缺点
    • 变长编码,且对代理对处理复杂
    • 需要 BOM 标识字节序

UTF-32

  • 优点
    • 固定宽编码,处理简单,随机访问方便
  • 缺点
    • 空间利用率低,每个字符占4字节,浪费大量存储

总体来说,UTF-8 是目前最主流和最实用的编码方式。

语言代码UTF-8大小UTF-16大小UTF-16占UTF-8比例语言名称
pnb14431204119.85%Western Panjabi
kor6752255332122.03%Korean
mal7659060468126.66%Malayalam
tel26422080127.02%Telugu
hin455546349438130.37%Hindi
mar13325091008604132.11%Marathi
npi460348132.18%Nepali
ben2773320802133.32%Bengali
san14651094133.91%Sanskrit
kat2997022344134.13%Georgian
cmn15636631055924148.08%Mandarin Chinese
yue11548877874148.30%Yue Chinese
wuu150945101588148.59%Wu Chinese
tha1874512610148.65%Thai
lzh7328548982149.62%Literary Chinese
bod778743925149.69%Tibetan
jpn97786436524720149.87%Japanese
ain384142142.19%Ainu (Japan)
lao10842622145.39%Lao
khm2267615348147.75%Central Khmer

这段数据展示了不同语言文本在 UTF-8 和 UTF-16 编码下的大小对比,数字后面的百分比表示 UTF-16 相比 UTF-8 的大小比例,比如:

  • pnb (Western Panjabi):UTF-16 文本大小是 UTF-8 的 119.85%(比 UTF-8 大约 20%)
  • kor (Korean):UTF-16 是 UTF-8 的 122.03%
  • hin (Hindi):UTF-16 是 UTF-8 的 130.37%
  • cmn (Mandarin Chinese):UTF-16 是 UTF-8 的 148.08%
  • jpn (Japanese):UTF-16 是 UTF-8 的 149.87%

总结:

  • 对大多数东亚语言(汉语、日语、韩语等)来说,UTF-8 会比 UTF-16 更节省空间。
  • 对少数语言(尤其是西方语言和某些印度语言),UTF-16 有时会比 UTF-8 更紧凑,但整体差距不大。
  • 百分比超过 100% 表示 UTF-16 文件比 UTF-8 文件更大。

“Dynamic Composition”(动态组合)在文本编码和Unicode里,通常指的是字符不是单独固定的码点,而是可以通过多个码点动态组合形成一个“字符”或“视觉单元”的机制。简单来说,就是用多个代码点动态“拼合”成一个完整的字符或者字形。

1. Unicode 合成字符(Combining Characters)

  • 比如字母 “a” 加上重音符号 ´(U+0301,COMBINING ACUTE ACCENT)
    实际显示的是带重音的 á,但在内存里是两个码点:
    U+0061 (a) + U+0301 (´)
  • 这个过程就是动态组合,字符显示成一个整体,但编码是多个码点组成。

2. 表情符号(Emoji)组合

  • Emoji 可以通过“零宽连接符”(ZWJ, U+200D)组合成更复杂的表情,比如👨‍👩‍👧‍👦
    实际是多个emoji码点用 ZWJ 连接在一起,动态组合成一个“家庭”表情。

3. 汉字部首组合(理论)

  • 汉字虽然大多是单个码点,但理论上如果用部首或偏旁组合,属于动态组合思想的应用。

动态组合的好处

  • 节省编码空间,不用给每个复合字符单独编码
  • 允许灵活扩展和组合,支持更多语言和符号
  • 保持编码统一性,同时能展示丰富的视觉效果

动态组合的挑战

  • 字符串处理复杂,不能简单按码点切割
  • 排版和渲染需要更智能的支持
  • 搜索和长度计算变得更复杂

动态组合的几个具体示例,展示了字符是如何通过多个码点动态组合形成复杂字符的。:

动态组合示例解析

示例 1
A
À Á Â Ã Ä Å
  • 这些带重音符的字母都是基于字母 A,但它们可以是:
    • 预合成字符(precomposed),即单独一个码点表示(例如 À 是 U+00C0)
    • 也可以通过 动态组合表示(字母 A + 组合重音符)
示例 2
A (U+0041)
+ ̈ (U+0308, COMBINING DIAERESIS)
→ Ä
  • 字符 Ä 可以用单一的预合成码点表示(U+00C4),
  • 也可以用两个码点表示:
    • U+0041 是大写字母 A
    • U+0308 是组合分音符¨(diaeresis)
  • 这两个码点动态组合显示为 Ä
示例 3
e + ̃ + ̽ + ̪ → U+0065 U+0303 U+033D U+032A

在这里插入图片描述

  • 这是更复杂的动态组合:
    • e (U+0065)
    • ̃ 组合波浪(U+0303)
    • ̽ 组合下中横(U+033D)
    • ̪ 组合下钩(U+032A)
  • 这几个码点串联起来,动态组成一个带多重附加符号的字母(例如某些音标或拼写)

总结

动态组合就是用基本字符 + 多个组合码点,动态生成一个视觉上完整的复杂字符,而不是用一个固定的单码点来表示。
这样做的优点是灵活、省编码空间,但在字符串处理时要注意:

  • 字符串长度不一定等于码点数
  • 显示需要渲染引擎支持组合字符的正确叠加

C++ 中关于 Unicode 的类型。这里给你梳理一下常用的 Unicode 相关类型及其特点:

Unicode Types in C++

1. char

  • 传统的 8 位类型,用于 ASCII 或单字节编码(如 Latin-1)
  • 不适合 Unicode(尤其是多字节字符)

2. wchar_t

  • 宽字符类型,大小依平台而异(通常 Windows 是 16 位,Linux 是 32 位)
  • 可用于存储 UCS-2 或 UTF-16(Windows)或 UCS-4/UTF-32(Linux)
  • 缺点:平台不统一,跨平台编码支持不好

3. char16_t

  • C++11 新增,专门表示 UTF-16 编码的单个代码单元(16 位)
  • 适合处理 UTF-16 编码字符串
  • char 不兼容,需要专门的库或函数处理

4. char32_t

  • C++11 新增,专门表示 UTF-32 编码的单个代码单元(32 位)
  • 每个值对应一个 Unicode 码点(不含代理对)
  • 用于处理完整的 Unicode 码点集合

对应字符串类型

类型描述例子
std::string字节字符串,通常用于 UTF-8"Hello"
std::wstring宽字符串,使用 wchar_tL"Hello"
std::u16stringUTF-16 字符串,使用 char16_tu"Hello"
std::u32stringUTF-32 字符串,使用 char32_tU"Hello"

小结

  • UTF-8 通常用 std::string 存储,兼容传统字节字符串
  • UTF-16std::u16stringchar16_t
  • UTF-32std::u32stringchar32_t
  • 宽字符 wchar_t 依平台不同,不建议新代码中单独依赖
编码类型代码单元类型(Code Unit Type)
UTF-8char
UTF-16char16_t
UTF-32char32_t
  • UTF-8 用一个字节(char)表示一个代码单元,长度可变(1~4 字节)
  • UTF-16 用两个字节(char16_t)表示一个代码单元,长度1或2个单元(代理对)
  • UTF-32 用四个字节(char32_t)表示一个代码单元,固定长度(1个单元对应1个码点)

表达不同的 Unicode 字符串字面量以及它们在 C++ 中的大小(sizeof)对应关系。

char const a[] {u8"Hello, \u2603!"};
char const b[] {u8"Hello, ☃!"};
// sizeof(a) == sizeof(b) == 12

char16_t const c[] {u"Hello, \u2603!"};
char16_t const d[] {u"Hello, ☃!"};
// sizeof(c) == sizeof(d) == 20

char32_t const e[] {U"Hello, \u2603!"};
char32_t const f[] {U"Hello, ☃!"};
// sizeof(e) == sizeof(f) == 40

解释

  • UTF-8 字符串 ab

    • "Hello, " 是 7 个 ASCII 字符,每个 1 字节。
    • (U+2603) 用 UTF-8 编码是 3 字节(E2 98 83)。
    • ! 是 1 字节。
    • \0 结束符 1 字节。
    • 总计:7 + 3 + 1 + 1 = 12 字节。
  • UTF-16 字符串 cd

    • "Hello, " 是 7 个字符,每个 2 字节,共 14 字节。
    • 在 UTF-16 是一个 16 位单元(2 字节)。
    • ! 2 字节。
    • \0 2 字节。
    • 总计:7*2 + 2 + 2 + 2 = 20 字节。
  • UTF-32 字符串 ef

    • "Hello, " 是 7 个字符,每个 4 字节,共 28 字节。
    • 4 字节。
    • ! 4 字节。
    • \0 4 字节。
    • 总计:7*4 + 4 + 4 + 4 = 40 字节。

总结

  • 用 Unicode 转义(\u2603)和直接写字符 在字符串字面量里是等价的,结果大小一样。
  • UTF-8 最紧凑,UTF-16 是中等大小,UTF-32 最大但最简单。

你这段代码是关于 Unicode 字符字面量 在 C++ 中的合法性和限制的例子,尤其是不同宽度的字符类型 char, char16_t, char32_t 对单字符的支持情况。

说明和分析

  1. char 类型:
char a{'X'};              // 合法,ASCII 字符
char b{'\u2603'};         // 错误,'\u2603' 是雪人符号,超出 char 范围(一般是 1 字节)
char c{'\U0001F378'};     // 错误,超出 char 范围,Unicode 码点太大
  • char 一般是 1 字节,表示范围仅限 0~255,无法容纳大于 0xFF 的 Unicode 码点。
  • 因此,\u2603(U+2603 雪人符号)和 \U0001F378(U+1F378 鸡尾酒杯)都不能用 char 字符字面量。
  1. char16_t 类型:
char16_t d{u'X'};           // 合法,ASCII 范围字符
char16_t e{u'\u2603'};      // 合法,U+2603 能被 16 位表示
char16_t f{u'\U0001F378'};  // 错误,超出 16 位范围,需要代理对表示
  • char16_t 是 16 位无符号整数,能直接表示 U+0000 ~ U+FFFF 区间内的字符(BMP 基本多文种平面)。
  • U+2603 属于 BMP 内部,可以用单个 char16_t 字符字面量表示。
  • U+1F378 超出 16 位范围,属于辅助平面,必须用 UTF-16 代理对表示,不能用单个 char16_t 字符字面量。
  1. char32_t 类型:
char32_t g{U'X'};            // 合法,ASCII 范围字符
char32_t h{U'\u2603'};       // 合法,U+2603 在 BMP 内
char32_t i{U'\U0001F378'};   // 合法,U+1F378 可被 32 位表示
  • char32_t 是 32 位无符号整数,可以表示所有 Unicode 码点。
  • 因此,所有示例中的字符字面量都合法。
  1. 使用 \u\U 转义时注意点:
  • \u 后面必须跟 4 个十六进制数字,表示一个 16 位 Unicode 码点。
  • \U 后面必须跟 8 个十六进制数字,表示一个 32 位 Unicode 码点。

总结

类型支持的字符范围合法示例非法示例
char0x00 ~ 0xFF'X', '\u0078''\u2603', '\U0001F378'
char16_t0x0000 ~ 0xFFFF (BMP)u'X', u'\u2603', u'\u0078'u'\U0001F378'
char32_t0x00000000 ~ 0x10FFFF (全Unicode)U'X', U'\u2603', U'\U0001F378'

是 C++ 标准库中针对不同 Unicode 编码的 std::basic_string 的特化类型别名(typedef):

编码类型Code Unit 类型字符串类型别名
UTF-8charstd::string
UTF-16char16_tstd::u16string
UTF-32char32_tstd::u32string
  • std::basic_string 是模板类,模板参数为字符类型(Code Unit 类型)。
  • 通过 typedef,标准库定义了对应的字符串类型别名以方便使用。
  • 所以,如果你要操作 UTF-8 字符串,用 std::string;操作 UTF-16 字符串,用 std::u16string;操作 UTF-32 字符串,用 std::u32string

用 C++11 以后支持的 Unicode 字符串字面量初始化不同编码的 std::string 类型:

std::string a{u8"Hello, \u2603!"};      // UTF-8 编码,char 类型
std::string b{u8"Hello, ☃!"};          

std::u16string c{u"Hello, \u2603!"};    // UTF-16 编码,char16_t 类型
std::u16string d{u"Hello, ☃!"};         

std::u32string e{U"Hello, \u2603!"};    // UTF-32 编码,char32_t 类型
std::u32string f{U"Hello, ☃!"};         
  • u8"" 是 UTF-8 字符串字面量,存储为 char 字符串。
  • u"" 是 UTF-16 字符串字面量,存储为 char16_t 字符串。
  • U"" 是 UTF-32 字符串字面量,存储为 char32_t 字符串。
    这种写法保证了字符串内部的编码和类型匹配,是 C++ 中处理多种 Unicode 编码的标准方法。

std::basic_string 是对底层代码单元(code units)的简单封装:

  • 它储存的是代码单元序列,而不是语义上的字符(code points)、文本元素(text elements)或更高级的字符组合(grapheme clusters)。
  • 使用 begin()end() 遍历时,是逐个访问代码单元,不是字符。
  • size() 返回的是代码单元的数量,而不是 Unicode 字符的数量。
  • front(), back(), operator[], push_back(), find(CharT) 这些操作都是针对单个代码单元进行的。
    所以,直接用 std::basic_string 做 Unicode 字符串操作时要注意,它并不理解多字节或代理对(surrogate pairs)等复杂编码结构,往往需要额外的库或自己实现字符处理逻辑。
std::string a{"Hello, "};

// Wrong:
a.push_back('\u2603'); // U+2603

// Right:
char const snowman[] {u8"☃"}; // U+2603
a.insert(a.end(), begin(snowman), end(snowman));

这段代码说明了为什么不能直接用 push_back('\u2603') 来添加 Unicode 字符(比如雪人☃ U+2603)到 std::string 中:

  • '\u2603' 是一个 Unicode 码点,但 char 类型只能存一个字节(8-bit),'\u2603' 超过了 char 的范围,所以 push_back 不能正确存储这个字符,结果是错误的。
    正确的方法是:
  • 先用 UTF-8 编码的字符串数组存储该字符,比如 char const snowman[] {u8"☃"};,这里 snowman 是一个多字节的 UTF-8 编码字符串。
  • 然后通过 a.insert(a.end(), begin(snowman), end(snowman)); 把整个 UTF-8 编码的字节序列插入到 a 的末尾。
    这样就保证了 Unicode 字符能被正确地以 UTF-8 形式存储在 std::string 里。
std::u16string a{u"A glass: "};

// Wrong:
a.push_back(u'🍸'); // 错误:单个 char16_t 不能表示 U+1F378

// Right:
char16_t const glass[] {u"🍸"}; // U+1F378
a.insert(a.end(), begin(glass), end(glass));

问题点

  • u'🍸' 是单个 UTF-16 字符字面量,但 U+1F378 超出单个 char16_t 可表示范围(超过 0xFFFF),所以这个写法是不合法的。
  • 要表示 U+1F378,需要用 UTF-16 的字符串字面量(u"🍸"),而不是字符字面量(u'🍸')。
    正确写法是
std::u16string a{u"A glass: "};

char16_t const glass[]{u"🍸"}; // 用 UTF-16 字符串字面量
a.insert(a.end(), std::begin(glass), std::end(glass) );

解释

  • u"🍸" 是 UTF-16 编码的字符串字面量,包含两个 char16_t 代码单元(代理对)。
  • insert 把这两个代码单元插入到字符串末尾。
    如果想用 push_back,只能针对 BMP 范围内的字符(<= U+FFFF),比如:
a.push_back(u'Ä'); // 合法,Ä 在 BMP 里

而代理对字符(辅助平面)只能用字符串插入方式。

std::u32string a{U"A glass: "};
// Right:
a.push_back(U'🍸'); // U+1F378

完全正确!这段代码用 std::u32string 存储字符串,直接用 push_back(U'🍸') 插入 Unicode 码点 U+1F378(🍸)是合法且简洁的写法。
总结:

  • std::u32string 的元素是 char32_t,每个元素可以完整表示一个 Unicode 码点。
  • 所以可以用 U'🍸' 字面量直接表示单个字符,方便插入。

字符串 "1 Ä 🍸" 在各种编码格式下的详细对照表:

编码类型1A组合分音符 ̈🍸
Unicode 码点U+0031U+0041U+0308U+1F378
UTF-83141CC 88F0 9F 8D B8
UTF-16003100410308D83C DF78 (代理对)
UTF-320000003100000041000003080001F378
编码类型1A组合分音符 ̈🍸字符串长度:字节数字符串长度:编码单元数字符串长度:码点数字符串长度:文本元素数
Unicode 码点U+0031U+0041U+0308U+1F378
UTF-83141CC 88F0 9F 8D B88843
UTF-16003100410308D83C DF78 (代理对)10543
UTF-320000003100000041000003080001F37816443
  • 字符串长度的四种含义
    1. 字节数(Number of bytes)
    2. 编码单元数(Number of code units)
    3. 码点数(Number of code points)
    4. 文本元素数(Number of text elements)
  • 码点数和文本元素数在不同编码下保持一致,因为它们描述的是字符的语义层面,而不是存储方式。
  • UTF-8的字节数和编码单元数相同,因为它的编码单元就是1字节。
  • UTF-32的编码单元长度固定为4字节,所以编码单元数和码点数相同。
#include <iostream>
#include <string>

// 计算码点数(非续字节计数)
std::size_t count_code_points(const std::string& s) {
    std::size_t count = 0;
    for (unsigned char c : s) {
        if ((c & 0xC0) != 0x80) {  // 非10xxxxxx的字节是起始字节
            ++count;
        }
    }
    return count;
}

// 简单演示(文本元素需要ICU或其他库,下面仅示意)
int main() {
    std::string s{u8"1Ä🍸"};
    std::cout << "Number of Bytes: " << s.size() << "\n";
    std::cout << "Number of Code Units (UTF-8): " << s.size() << "\n";
    std::cout << "Number of Code Points: " << count_code_points(s) << "\n";

    // 文本元素(grapheme clusters)需要专业库才能准确计算,暂时用码点数-1演示
    std::cout << "Number of Text Elements (approximate): " << count_code_points(s) - 1 << "\n";

    return 0;
}
Number of Bytes: 8
Number of Code Units (UTF-8): 8
Number of Code Points: 4
Number of Text Elements (approximate): 3

字符串“相等性”(Equality)时区分“表示上的相等”和“语义上的相等”吧?我帮你整理一下你给的内容并补全示例代码,方便理解:

表示上的相等(Representational Equality)

  • 比较两个 std::string,它们的编码单位序列完全相同才认为相等。
  • 例子:
#include <iostream>
#include <string>
int main() {
    std::string a{"Hello, Bellevue!"};
    std::string b{"Hello, Bellevue!"};
    if (a == b)
        std::cout << "The strings are equal.\n";
    else
        std::cout << "The strings are NOT equal.\n";

    return 0;
}

输出:

The strings are equal.

语义上的相等(Semantic Equality)

  • 即使字符串的编码表示不同,但它们语义上表示的是同一个文本。例如:
    • 一个字符串用预组合字符(Ä,U+00C4)
    • 另一个字符串用“组合字符”(A + 组合分音符 ̈,U+0041 U+0308)
  • 直接用 std::string 比较时,它们不相等,因为字节序列不同。
  • 例子:
#include <iostream>
#include <string>
int main() {
    std::string a{u8"1\u00C4\u2603"};      // 1 + Ä (预组合) + 🍸
    std::string b{u8"1A\u0308\u2603"};     // 1 + A + ̈(组合分音符) + 🍸
    if (a == b)
        std::cout << "The strings are equal.\n";
    else
        std::cout << "The strings are NOT equal.\n";
    return 0;
}

输出:

The strings are NOT equal.

解释

  • 虽然语义上ÄA + ̈ 是同一个字符(Unicode 规范里两者等价),但是它们的UTF-8编码不一样,导致字符串比较不相等。
#include <iostream>
#include <string>
int main() {
    std::string a{u8"1Ä🍸"};
    std::string b{u8"1Ä🍸"};
    if (a == b)
        std::cout << "The strings are equal.\n";
    else
        std::cout << "The strings are NOT equal.\n";
    return 0;
}

输出:

The strings are equal.

你列出的内容是 Unicode 中“多个表示方式”(Multiple Representations) 的经典例子,这关系到:

“语义相同、表示不同”的字符组合

同一个视觉/语义字符,可以有多个 Unicode 表示方法

核心概念理解:

1. 预组合字符(Precomposed Character) vs 组合序列(Combining Sequence)
表示方式Unicode码点含义
ÄU+00C4预组合字符(单个码点)
A + ¨U+0041 + U+0308基字符 + 组合音符
视觉上都显示为 Ä,但编码不同。
2. 全角字符与半角字符
字符Unicode含义
AU+0041Latin Capital Letter A
U+FF21Fullwidth Latin Capital A
它们是不同的码点,在宽度、字体渲染中表现不同。
3. 连字(Ligatures)
字符Unicode表示
f + iU+0066 + U+0069分别的两个字母
U+FB01连字形式(单个码点)
现代排版系统可能会自动将 f + i 显示为 ,但编码上是不同的。
4. 罗马数字的两种表示
字符Unicode序列表示
U+216B罗马数字 12(单一符号)
XIIU+0058 U+0049 U+0049组成形式(普通拉丁字母)
5. 组合顺序的多种合法排列
组合Unicode序列注释
A + + ^U+0041 U+0347 U+0302正常组合序列
A + ^ + U+0041 U+0302 U+0347顺序不同但视觉上几乎无区别
这说明组合字符的位置顺序也可能不同,但视觉结果一样。

重点总结

概念说明
多重表示同一个字符或词语可以用不同的 Unicode 序列表达
比较时问题std::string == std::string 是字节级比较,多个表示方式将判定为“不相等”
解决方法使用 Unicode 归一化(Normalization),例如 ICU 中的 NFC / NFD 格式

Normalization(规范化) 是 Unicode 中一个非常关键的机制,用于解决“多个字符表示同一文本内容”的问题。

理解 Unicode 的四种规范化形式

规范化形式名称含义简述
NFCCanonical Composed标准组合形式(预组合),如 A + ̈ → Ä
NFDCanonical Decomposed标准拆分形式,如 Ä → A + ̈
NFKCCompatibility Composed兼容性组合,会简化兼容字符(如全角、连字)
NFKDCompatibility Decomposed兼容性拆分,也会拆分字符为最基本单位

举个例子:字符 Ä

表达方式Unicode编码说明
ÄU+00C4单个预组合字符
A + ̈U+0041 + U+0308基字符加组合符号
NFC 中:会转换为 U+00C4
NFD 中:会转换为 U+0041 + U+0308

为什么需要规范化?

在字符串比较、搜索、排序中,两个视觉上相同的字符串(如 ÄA + ̈)如果不规范化,它们将被视为“不同”。

附加说明:组合字符顺序排序

规范化还会对 多个组合字符的顺序 进行一致化,比如:

  • A + ́ + ̨(U+0041 U+0301 U+0328)
  • A + ̨ + ́(U+0041 U+0328 U+0301)
    两者视觉上相同,但字节顺序不同,规范化后可统一顺序。
    如果你想用 C++ 验证这些规范化行为,推荐使用:
  • ICU Library(国际组件 Unicode)
  • Boost.Text(支持 Unicode 规范化)
  • 或 C++23 中的 std::text(未来标准)

你这部分是在理解 字符串排序(Ordering),尤其是使用 std::string 进行排序时的行为 —— 这是按字节(code unit)顺序排序,不是按“字典顺序”或“视觉顺序”

核心理解

1. std::string 排序是按 字节值(Code Unit) 比较:
std::string a{"a"};
std::string b{"b"};
if (a < b) {
    std::cout << "a is ordered before b.\n";
}

这个是对的,因为 'a' 的 ASCII 值是 97,小于 'b' 的 98。

🐻🧜‍♂️ 2. Emoji 也一样,只不过是 UTF-8 多字节编码
std::string bear{u8"🐻"};   // U+1F43B → UTF-8: F0 9F 90 BB
std::string whale{u8"🐳"};  // U+1F433 → UTF-8: F0 9F 90 B3
if (bear < whale) {
    std::cout << "the bear is ordered before the whale.\n";
}

这个也成立,因为:

  • 🐻 的 UTF-8 最后一个字节是 0xBB
  • 🐳 的 UTF-8 最后一个字节是 0xB3
  • 0xBB > 0xB3 → 所以 🐻 > 🐳 → bear < whalefalse,不会输出
    但 UTF-8 顺序不等于视觉或语言顺序!
3. 排序示例
std::vector<std::string> v {"c", "X", "b", "Y", "a", "Z"};
std::sort(v.begin(), v.end());
for (const auto& s : v)
    std::cout << s << ' ';

输出是:

X Y Z a b c

为什么?

  • 大写字母(AZ):ASCII 值 6590
  • 小写字母(az):ASCII 值 97122
    所以 X Y Z 会排在 a b c 前面。

小结

概念排序依据
std::string字节值排序(Code Units,按字典码点大小)
排序行为与人类“字母顺序”可能不一致,特别是 Unicode/Emoji
排序稳定性区分大小写,大写排在小写前面
国际排序(如字典顺序)需要用 ICU、locale-aware 比较器(如 std::collate

String Collation(字符串排序规则),尤其是不同语言区域(locale)对字符的排序行为不同。这很重要,因为视觉上相似的字符,如 äaz,在不同文化中排序是不同的。

核心概念理解

什么是 Collation?
  • Collation 是字符串排序的规则,它考虑了语言习惯,而不是单纯的 Unicode 或 ASCII 值比较。
  • 使用 C++ 的 std::locale + std::collate 可以根据语言规则对字符串排序。

不同语言区域的排序行为

🇺🇸 English (en_US)
std::vector<std::string> v{"c", "X", "b", "Y", "a", "Z"};
std::locale en_us("en_US.UTF-8");
std::sort(v.begin(), v.end(), [&en_us](const std::string& a, const std::string& b) {
    return std::use_facet<std::collate<char>>(en_us).compare(
        a.data(), a.data() + a.size(),
        b.data(), b.data() + b.size()) < 0;
});

输出:

a b c X Y Z

英语环境中是区分大小写但排序更像字典顺序。

🇩🇪 German (de_DE)
std::vector<std::string> v{"z", "ä", "b", "a"};
std::locale de_de("de_DE.UTF-8");
std::sort(v.begin(), v.end(), [&de_de](const std::string& a, const std::string& b) {
    return std::use_facet<std::collate<char>>(de_de).compare(
        a.data(), a.data() + a.size(),
        b.data(), b.data() + b.size()) < 0;
});

输出:

a ä b z

在德语中,ä 会排在 a 后面,z 最后。

🇸🇪 Swedish (sv_SE)
std::vector<std::string> v{"z", "ä", "b", "a"};
std::locale sv_se("sv_SE.UTF-8");
std::sort(v.begin(), v.end(), [&sv_se](const std::string& a, const std::string& b) {
    return std::use_facet<std::collate<char>>(sv_se).compare(
        a.data(), a.data() + a.size(),
        b.data(), b.data() + b.size()) < 0;
});

输出:

a b z ä

瑞典语中,ä 是排在 z 后面的独立字母,和 a 没有“组合”关系。

小结表

区域设置排序示例输入排序输出
en_US.UTF-8a b c X Y Za b c X Y Z
de_DE.UTF-8a ä b za ä b z
sv_SE.UTF-8a ä b za b z ä

总结

  • std::string 默认按 字节值(binary) 排序。
  • 若要按语言规则排序,需使用:
    std::locale("xx_XX.UTF-8")
    std::use_facet<std::collate<char>>(...)
    
  • 不同语言对同一字符(如 ä)排序位置不同。
  • Collation 是国际化程序(i18n)中非常重要的功能。

你提到的内容涵盖了 Unicode 文本处理中的几个关键点,包括文本操作(大小写转换)、标准库函数的局限性、以及编码转换(如 UTF-8 ↔ UTF-32)与 I/O。下面是对这些内容的系统梳理与解释,确保你真正理解了其中的关键

一、文本操作(Text Manipulation)

std::toupper() 与 Unicode 的限制
std::locale loc("en_US.utf8");
char lowercase_a = 'a';
char uppercase_a = std::toupper(lowercase_a, loc); // 正确输出 'A'
  • 适用于 ASCII 范围
  • 不适用于多字节字符(如 Ä, ß)或复合字符

例如:

  • 'ß' 在德语中转换成 'SS'(两个字符)
  • toupper('ß') 仍然是 'ß'(不会变成 'SS')——标准库无法处理

二、字符分类函数的局限(std::isalpha, std::isspace

std::isalpha('A', loc); // 对 char 有效,但不支持 UTF-8 多字节字符
这些函数本质是 基于单个 code unit(通常是 char 的:
  • 不支持像 🍸Ä(U+0308)这样由多个 code units 构成的字符
  • 只有 char32_t(UTF-32)场景下,才能准确按码点判断字符类型

三、编码转换 <codecvt>(虽然已弃用,仍有参考价值)

示例:UTF-8 → UTF-32 转换
std::string utf8 = u8"1Ä🍸";
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
std::u32string utf32 = converter.from_bytes(utf8);
// utf32 将包含:U+0031, U+00C4, U+1F378
注意:
  • <codecvt> 已在 C++17 弃用,但仍可用于教学与实验
  • 替代方案:使用 ICU、Boost.Locale、C++20 std::text_encoding(仍在演进)

四、I/O 转换与 BOM 示例

写入 UTF-8 文件(带 BOM):
std::ofstream("text.txt") << "\xEF\xBB\xBF" << u8"1Ä🍸";
读取文件并跳过 BOM(使用 codecvt):
std::basic_ifstream<char32_t> f("text.txt");
f.imbue(std::locale(f.getloc(),
    new std::codecvt_utf8<char32_t, 0x10ffff, std::consume_header>));
for (char32_t c{}; f.get(c); ) {
    std::cout << std::hex << c << ' ';
}
// 输出: 31 41 308 1f378

五、总结理解点

概念含义注意事项
std::toupper只能处理单个字符(code unit)无法处理 ß→SS 或组合字符
std::isalpha检查单个 code unit不支持 emoji 或复合字符
<codecvt>编码转换工具(已弃用)C++17 开始被标记为不推荐使用
std::locale控制区域相关行为(大小写、排序)要求系统支持相应 locale,如 en_US.utf8
如果你今后要进行更复杂的 Unicode 操作(大小写转换、排序、比较、文本元素计数等),推荐使用:
  • ICU 库(跨平台、功能强大)
  • Boost.Locale
  • 或等待/采用 C++20 及以后提供的文本工具支持

关于 ICU(International Components for Unicode) 的要点是非常准确的,下面我为你进一步梳理和解释每一项,以便真正理解它在 Unicode 处理中的地位与特点:

ICU:国际 Unicode 支持组件

ICU 是一套功能非常强大的跨平台国际化库,提供了几乎所有你需要的 Unicode 操作:

1. 功能非常全面

  • 文本归一化(Normalization)
  • 区分大小写转换(Case Mapping)
  • 排序与比较(Collation)
  • 断词与换行(Boundary Analysis)
  • 正则表达式(支持 Unicode-aware)
  • 日历、时区、国际化数字与货币格式化等
    简言之:你能想到的 Unicode 需求它几乎都覆盖了

2. 非常成熟、广泛使用

  • 被用于 Google Chrome、Android、Windows、macOS、Firefox 等主流软件
  • 因此非常稳定可靠、经过实战验证

3. 跨平台 + 开源

  • 支持 Linux、Windows、macOS、AIX、Solaris、FreeBSD……
  • 许可证宽松:ICU 使用 Unicode 的自定义开源许可(类似 MIT)

使用 ICU 的挑战

1. ICU 有自己的字符串类型:UnicodeString

icu::UnicodeString str = UNICODE_STRING_SIMPLE("Äpfel");
  • 内部使用 UTF-16
  • 不兼容标准的 std::stringstd::u8string
  • 需要转换才能交互:
std::string utf8;
str.toUTF8String(utf8);

2. 接口风格不是“现代 C++”

  • 设计年代早,接口接近 Java 风格,例如:
    UnicodeString str("straße");
    str.toUpper(Locale("de"));  // -> "STRASSE"
    
  • 没有使用 STL 容器或智能指针
  • 很多 API 通过状态码、输出参数返回值,不支持 exceptions

示例对比

功能std::string / 标准库ICU 示例
大小写转换toupper()str.toUpper(Locale("en"))
排序(德语 ä)不准确ICU 排序器支持 locale-aware 排序
正规化 NFC/NFD无内建支持Normalizer::normalize(str, UNORM_NFC, status)
文本元素计数BreakIterator::createCharacterInstance(...)

你这个例子很好地展示了 ICU 和 C++ 中不同 Unicode 类型的用法,以下是详细解释,帮你理解:

UChar32UChar 区别

类型描述编码单元大小用途
UChar3232 位无符号整数4 字节(UTF-32 code unit)表示完整的 Unicode 码点(code point)
UChar16 位无符号整数2 字节(UTF-16 code unit)表示 UTF-16 编码单元(一个或半个码点)

代码示例讲解:

// UTF-32 码单位(完整 Unicode 码点)
UChar32 rook_utf32{static_cast<UChar32>(U'♖')};  // U+2656, 棋子“车”符号
UChar32 cake_utf32{static_cast<UChar32>(U'🍰')}; // U+1F370, 蛋糕符号

// UTF-16 码单位(16 位单元,可能是代理对)
UChar rook_utf16{u'♖'};             // U+2656,单个 UTF-16 单元
UChar cake_utf16[2]{0xD83C, 0xDF70}; // U+1F370 的 UTF-16 代理对(2 个单元)

// UTF-8 码单位(char 数组,长度可变)
char rook_utf8[4]{u8"♖"};           // UTF-8 编码,3 字节,包含结束符 '\0'
char cake_utf8[5]{u8"🍰"};           // UTF-8 编码,4 字节,包含结束符 '\0'

具体说明:

  • UChar32 是完整的 Unicode 码点,用一个 32 位整数表示,无论字符多复杂(即使是辅助平面的字符,也只用一个 UChar32 表示)。
    • U'♖'U'🍰' 是 C++11 字符字面量,类型是 char32_t,可以强制转换为 ICU 的 UChar32
  • UChar 是 UTF-16 的单元类型,通常是 16 位宽,处理 BMP(基本多文种面)的字符只需一个单元,如
    • 对于辅助平面(码点 > U+FFFF),如 🍰,需要两个 UChar(代理对)来表示:高位代理 0xD83C,低位代理 0xDF70
  • UTF-8 是变长编码,字符可能是 1 到 4 个字节。
    • char rook_utf8[4]char cake_utf8[5] 是数组,长度包括了末尾的 \0 空字符。

你可以理解为:

字符Unicode 码点UTF-32 (UChar32)UTF-16 (UChar 或 代理对数组)UTF-8 (char 数组)
U+26561 个 32 位单元1 个 16 位单元3 个字节 + \0
🍰U+1F3701 个 32 位单元2 个 16 位单元(代理对)4 个字节 + \0

你这段内容涉及 ICU 的 UnicodeString 类和相关的 Unicode 码点(UChar32)操作,帮你梳理和补全成更清晰的示例代码,方便理解和使用。

ICU UnicodeString 与 UChar32 示例

#include <unicode/unistr.h>   // ICU 的 UnicodeString
#include <unicode/ustream.h>  // 方便打印 UnicodeString
#include <iostream>
int main() {
    // UTF-16 代理对表示蛋糕符号 U+1F370
    UChar cake_utf16[2] = {0xD83C, 0xDF70};

    // 从 UTF-16 数组构造 UnicodeString
    icu::UnicodeString cake_utf16_str(cake_utf16, 2);

    // UTF-8 编码字符串
    const char8_t* cake_utf8 = u8"🍰";

    // 从 UTF-8 构造 UnicodeString
    icu::UnicodeString cake_utf8_str = icu::UnicodeString::fromUTF8(cake_utf8);

    // UTF-32 码点数组(单个码点 + 结尾0)
    UChar32 cake_utf32[] = {0x1F370, 0};

    // 从 UTF-32 构造 UnicodeString
    icu::UnicodeString cake_utf32_str = icu::UnicodeString::fromUTF32(cake_utf32, 1);

    // 输出结果
    std::cout << "UTF-16 string: " << cake_utf16_str << std::endl;
    std::cout << "UTF-8 string: " << cake_utf8_str << std::endl;
    std::cout << "UTF-32 string: " << cake_utf32_str << std::endl;

    // 使用 UnicodeString 的一些操作
    std::cout << "Length (UTF-16 code units): " << cake_utf16_str.length() << std::endl;
    std::cout << "Count of code points (chars): " << cake_utf16_str.countChar32() << std::endl;

    return 0;
}

关键点解析

  • UnicodeString 内部存储是 UTF-16 编码。
  • 可以通过 fromUTF8()fromUTF32() 等静态方法从不同编码构造 UnicodeString
  • length() 返回 UTF-16 编码单元数(即 UChar 个数)。
  • countChar32() 返回 Unicode 码点数,辅助平面字符算作一个。
  • 支持丰富操作:比较(comparecaseCompare),查找(indexOf),修改(appendreplace),转换(toUTF8StringtoUTF32)等。

理解

  • UChar32 是表示单个 Unicode 码点(32位整型)。
  • 复杂字符(如表情)在 UTF-16 中用代理对表示,需要两个 UChar
  • ICU 设计是基于 UTF-16 编码存储,方便处理 BMP 和辅助平面。
  • 通过 UnicodeString 你可以方便地做 Unicode 相关的字符串处理,比直接用 std::string 更准确。
UTF-16 string: 🍰
UTF-8 string: 🍰
UTF-32 string: 🍰
Length (UTF-16 code units): 2
Count of code points (chars): 1
# 设置 CMake 的最低版本要求
cmake_minimum_required(VERSION 3.10)

# 设置项目的名称和使用的编程语言
project(MyCppProject CXX)

set(CMAKE_CXX_STANDARD 26) 

find_package(ICU REQUIRED COMPONENTS uc io)

# 创建可执行文件
add_executable(test "test1.cpp")
target_link_libraries(test PRIVATE ${ICU_LIBRARIES})

你这段内容主要是讲 ICU 的 Unicode 规范化(Normalization),尤其是 NFC(Canonical Composition)形式的用法。下面帮你整理和补全一个更清晰的示例代码,展示如何用 ICU 的 Normalizer 类进行规范化:

ICU 规范化示例代码(NFC)

#include <unicode/unistr.h>
#include <unicode/normlzr.h>
#include <unicode/errorcode.h>
#include <iostream>

int main() {
    // UErrorCode 用于错误检测
    UErrorCode status = U_ZERO_ERROR;

    // 构造一个 UnicodeString,包含 'A' (U+0041) 和组合分音符 ̈ (U+0308)
    icu::UnicodeString source = icu::UnicodeString::fromUTF8("A\u0308"); // A + 组合分音符

    // 目标字符串用于保存规范化结果
    icu::UnicodeString result;

    // 使用Normalizer进行 NFC 规范化
    icu::Normalizer::normalize(source, UNORM_NFC, 0, result, status);

    if (U_FAILURE(status)) {
        std::cerr << "Normalization failed: " << u_errorName(status) << std::endl;
        return 1;
    }

    // 输出规范化前后的 UTF-8 字符串
    std::string source_utf8;
    source.toUTF8String(source_utf8);

    std::string result_utf8;
    result.toUTF8String(result_utf8);

    std::cout << "Source: " << source_utf8 << std::endl;  // 输出: A + 组合分音符
    std::cout << "Normalized (NFC): " << result_utf8 << std::endl;  // 输出: Ä (单个字符 U+00C4)

    return 0;
}

重点说明:

  • UnicodeString::fromUTF8("A\u0308") 这里的字符串是由拉丁字母 A 和组合分音符 ¨(U+0308)组成,两个码点。
  • 调用 Normalizer::normalize 用 NFC 规范化(UNORM_NFC),它会将组合字符合成单一预组合字符(U+00C4,即“Ä”)。
  • status 用于捕获 ICU 操作中的错误状态,必须检查。
  • 规范化后,result 会是单一码点“Ä”。
  • 通过 toUTF8String() 将 ICU 的 UnicodeString 转为标准 UTF-8 字符串方便打印。

额外说明

  • ICU 提供了四种规范化形式:UNORM_NFC, UNORM_NFD, UNORM_NFKC, UNORM_NFKD
  • NFC(Canonical Composition)是最常用的规范化形式,尽量合成预定义的组合字符。
  • NFD 是拆解所有组合字符为基础字符+组合字符序列。
  • 规范化还会对组合字符的顺序进行调整,确保统一。
Source:Normalized (NFC): Ä

代码片段核心在演示 ICU Collator 的用法,实现英文(en_US)环境下字符串的排序比较。

#include <iostream>
#include <memory>
#include <unicode/unistr.h>     // ICU UnicodeString
#include <unicode/locid.h>      // ICU Locale
#include <unicode/coll.h>       // ICU Collator
#include <unicode/uclean.h>     // ICU cleanup
#include <unicode/utypes.h>     // ICU UErrorCode

int main() {
    // 初始化 ICU 错误代码变量
    UErrorCode status = U_ZERO_ERROR;

    // 创建 en_US 的 Locale 对象
    icu::Locale locale("en", "US");

    // 创建对应 Locale 的 Collator 对象,返回 std::unique_ptr 管理
    std::unique_ptr<icu::Collator> collator(
        icu::Collator::createInstance(locale, status)
    );

    if (U_FAILURE(status)) {
        std::cerr << "Failed to create collator: " << u_errorName(status) << std::endl;
        return 1;
    }

    // 创建两个 UnicodeString 对象,分别是 "a" 和 "Z"
    icu::UnicodeString a = icu::UnicodeString::fromUTF8("a");
    icu::UnicodeString z = icu::UnicodeString::fromUTF8("Z");

    // 用 collator 比较两个字符串
    UCollationResult result = collator->compare(a, z, status);

    if (U_FAILURE(status)) {
        std::cerr << "Comparison failed: " << u_errorName(status) << std::endl;
        return 1;
    }

    // 输出比较结果
    if (result == UCOL_LESS) {
        std::cout << "\"a\" is ordered before \"Z\" in en_US locale." << std::endl;
    } else if (result == UCOL_GREATER) {
        std::cout << "\"a\" is ordered after \"Z\" in en_US locale." << std::endl;
    } else {
        std::cout << "\"a\" and \"Z\" are equal in en_US locale." << std::endl;
    }

    // 清理 ICU 资源(可选,但推荐)
    u_cleanup();

    return 0;
}
find_package(ICU REQUIRED COMPONENTS uc i18n io)
target_link_libraries(test PRIVATE
    ICU::uc
    ICU::io
    ICU::i18n
)
"a" is ordered before "Z" in en_US locale.

关键点总结:

  • UErrorCode 是 ICU 的错误码机制,所有 ICU 函数都用它来报告状态。
  • icu::Locale 用来指定区域语言,比如 "en", "US" 表示美式英语。
  • icu::Collator::createInstance(locale, status) 创建一个对应该语言环境的排序器。
  • icu::UnicodeString 是 ICU 自有的字符串类型,支持 UTF-16,最好用 fromUTF8() 来构造。
  • collator->compare(str1, str2, status) 返回排序结果,有三种:
    • UCOL_LESS: str1 排在 str2 前
    • UCOL_GREATER: str1 排在 str2 后
    • UCOL_EQUAL: 两者相等
  • u_cleanup() 释放 ICU 资源。

你这段代码是用 ICU 库里的正则表达式(RegexMatcher)做匹配的示例,关键点如下:

代码结构大概是:

UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::RegexMatcher> matcher = std::make_unique<icu::RegexMatcher>(
    icu::UnicodeString::fromUTF8(u8R"(\p{Number})"),  // 正则表达式,匹配所有数字字符
    0,
    status);

icu::UnicodeString text = icu::UnicodeString::fromUTF8(u8"Ⅻ⅝");  // 一段含有数字字符的Unicode文本

matcher->reset(text);  // 重置 matcher,准备对 text 进行匹配

while (matcher->find()) {
    // 在这里处理每次找到的匹配结果
}

重要点说明:

  • UErrorCode status:ICU所有函数调用都会传入这个状态变量,用来检查是否成功。
  • RegexMatcher:ICU中用于正则匹配的类,支持Unicode。
  • u8R"(...)":这是C++11的原始字符串字面量,用来写正则表达式,里面的\p{Number}是Unicode属性,表示所有数字字符。
  • fromUTF8:把UTF-8字符串转换成 ICU 的 UnicodeString 对象。
  • reset:绑定新的文本给 matcher。
  • find():查找下一个匹配,如果找到返回true。
    你这是典型的用法,用ICU正则来匹配Unicode文本中所有数字类字符。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值