字符串的底层:char、char32 与 UTF-8 编码
官方文档淡淡的一句「string 是 []char 的别名,char 是 UTF-8 code unit」,背后藏着一整套文本编码知识。为什么 'e' 是 char 而 'é' 是 char32?为什么中文字符串的 Length 和肉眼字数对不上?这一页带你从「会用字符串」升级到「理解字符串」。
一、两种字符:char 与 char32
Verse 有两个字符类型,这在编程语言里不算常见:
▢ char——一个 UTF-8 编码单元(code unit),可以理解成「编码后的一个字节格子」。
◇ char32——一个完整的 Unicode 码点(code point),即「人类眼中的一个字符」在 Unicode 字符表里的编号。
字符字面量(就是直接用引号写出来的字符)都用单引号,但类型由内容决定:'e' 是 ASCII 字符,UTF-8 里恰好占一个编码单元,所以它是 char;而 'é' 这种带附加符号的字符,UTF-8 编码要占多个编码单元,单个 char 装不下,于是它的字面量类型是 char32——用完整码点才存得下。
# ASCII 字符:一个 UTF-8 编码单元,类型是 char
Letter:char = 'e'
# 'é' 需要多个 UTF-8 编码单元,字面量是 char32
# Accent:char32 = 'é'
char 和 char32 的能力被刻意限制在最小集合:比较(可失败的 = 和 <>)和持有值,没有加减、没有大小写转换。想对字符做更多事,一般先借插值 "{Ch}" 把它升格成 string 再处理——第 6 课拓展页的 Split 实现用的正是这一招。
char 和 char32 的本事被刻意压到最小:只能比较(用「可能不成立」的 = 和 <>,所以要放进 Branch/if 里)和存一个值,不能加减、不能转大小写。想对字符多做点事,一般先用插值 "{Ch}" 把它升格成 string 再处理——第 6 课拓展页的 Split 实现用的正是这一招。
二、UTF-8:变长的「字符打包术」
Unicode 给世界上每个字符发了一个编号(码点):拉丁字母、汉字、假名、emoji,人人有号。但「编号」怎么存进内存?这就是编码方案的活儿,而 UTF-8 是当今的绝对主流。它的核心思想是变长打包:
| 字符类型 | 例子 | UTF-8 编码单元数(典型) |
|---|---|---|
| ASCII 字母 / 数字 / 半角标点 | e、7、! | 1 |
| 带附加符号的拉丁字母 | é、ü | 2 |
| 中日韩文字 | 你、剑 | 3 |
| emoji 等高位码点 | 各类表情符号 | 4 |
越常用的字符编码越短——这让纯英文文本极省空间,同时又能表示全部 Unicode 字符。代价是:「字符数」和「编码单元数」从此不再是一回事。
这直接解释了第 6 课埋的伏笔:string 是 []char,即 UTF-8 编码单元的数组,所以 .Length 数的是编码单元,不是肉眼字符。按 UTF-8 规则推算,"你好" 的 Length 很可能是 6 而不是 2——给玩家名、聊天文本做长度校验的中文创作者,千万别拿 Length 当「字数」用。动手验证只要两行:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
utf8_lab_device := class(creative_device):
OnBegin<override>()<suspends>:void =
English := "Verse"
Chinese := "你好"
Print("English 长度:{English.Length}")
# 亲手跑一遍:下面这个数字很可能不是 2
# 具体计数以编辑器实测为准
Print("Chinese 长度:{Chinese.Length}")
读图翻译:开局(Event BeginPlay)记下两个字符串,再用两个 Print String 分别打出它们的 .Length——英文那行会是 5,中文那行很可能不是 2,亲眼看看编码单元和字数的差别。
同理,失败上下文里的下标取值 Chinese[0] 拿到的是第一个编码单元,而不是「你」这个完整汉字——对多字节文本做逐字符处理时,这是必须心里有数的边界。
同理,在 Branch(if)里按下标取 Chinese[0],拿到的是第一个编码单元,而不是「你」这个完整汉字——对多字节文本做逐字符处理时,这是必须心里有数的边界。
三、字符串比较:逐码点、分大小写
string 的相等比较 = 和不等比较 <> 都是可失败表达式(所以住在 if 里)string 的相等比较 = 和不等比较 <> 都是「可能不成立」的判断,所以得放进 Branch(if)里,规则简单粗暴:按码点逐个比对,大小写敏感。"Apple" = "apple" 会失败,因为 A 和 a 是两个不同的码点;不存在「忽略大小写比较」的内置开关,而且标准库也没有 ToUpper / ToLower 帮你先统一大小写——需要时得自己写映射(或者在设计上规避,比如玩家输入统一转成选项而非自由文本)。
把这一页和第 6 课正文连起来,你就集齐了 Verse 字符串的完整心智模型:字面量是台词,插值是填空,string 是 UTF-8 编码单元的数组,char / char32 是它的最小零件。会用,也知道底下是什么——处理中文创作场景时,这层理解就是你比别人少踩坑的底气。
四、随堂小测 ★
字面量 'e' 和 'é' 的类型分别是什么?
用 .Length 校验中文玩家名的「字数」,为什么不靠谱?
来源与延伸
本文整理自 Epic 官方文档,并参考了社区手册 Book of Verse 的字符类型章节(社区资料,细节以官方文档与编辑器实测为准):
▢ 官方文档:string in Verse ↗
◇ 社区手册:Book of Verse — Primitives ↗