回上级页面

UTF-8

2023 年 07 月 25 日


问题

使用正则表达式对文本进行分割通常是多快好省之举,但是在遇到一个有着多个模式的字符串,必须构造多个相应的正则表达式对其重复匹配,遇到该情况,我倾向于自行编写专用的解析程序。假设字符串 s,其值可能为空或不少于 1 个空格,也可能为前后可能存在空格的 +^+。编写一个程序,识别 s 的值对应的模式。

字符串遍历

Rust 的 &strString 类型皆有 chars 方法,该方法返回字符串迭代器。例如,

fn main() {
    let s = "你好!";
    for c in s.chars() {
        println!("{}", c);
    }
}

fn main() {
    let s = String::from("你好!");
    for c in s.chars() {
        println!("{}", c);
    }
}

输出皆为

你
好
!

需要注意的是,Rust 以 UTF-8 作为字符串编码,上述程序表明 Rust 语言能够正确处理以多个字节表示单个字符的中文编码。

状态机

在遍历字符串的过程中,可以构造一个状态机,用它判断字符串对应的形式。状态机的所有状态可基于枚举类型予以定义,例如

enum Status {
    Init, // 初始状态
    Empty, // 为空或不少于 1 个空格
    Append, // 前后可能存在空格的 +
    Prepend, // 前后可能存在空格的 ^+
    MaybePrepend // 可能是 prepend 状态
}

基于 Rust 的 match 语法可实现状态的迁移:

let s = "    ^+ ";
let mut m = Status::Init;
for c in s.chars() {
    match m {
        Status::Init => {
            match c {
                ' ' => m = Status::Empty,
                _ => panic!("Unknown text!")
            }
        },
        Status::Empty => {
            match c {
                ' ' => {},
                '+' => m = Status::Append,
                '^' => m = Status::MaybePrepend,
                _ => panic!("Unknown text!")
            }
        },
        Status::Append => {
            match c {
                ' ' => {},
                _ => panic!("Unknown text!")
            }
        },
        Status::MaybePrepend => {
            match c {
                '+' => m = Status::Prepend,
                _ => panic!("Unknown text!")
            }
        },
        Status::Prepend => {
            match c {
                ' ' => {},
                _ => panic!("Unknown text!")
            }
        }
    }
}
println!("{:?}", m);

上述代码输出为 Prepend,即 m 的值为 Status::Prepend

C 版本

上述用 Rust 编写的状态机,用 C 的 switch...case 语法亦可实现等价版本,并无困难之处,不再赘述。真正的难点在于,C 语言及其标准库不支持 UTF-8 编码,如何编写可对 UTF-8 编码的字符串以字符为单位的遍历程序?

GLib 提供了 UTF-8 编码的文本处理功能。我曾写过一篇文章对此有所介绍,详见「在 C 程序中处理 UTF-8 文本」。

GNU 项目的 libunistring 库亦能让 C 程序支持 UTF-8 编码的字符串,我也曾为此写过一篇文章,详见「C 程序严重的 Unicode」。

小结

Rust 标准库对 UTF-8 编码的字符串提供的支持,顺应了 UTF-8 一统天下的潮流,C 语言用户则可通过一些第三方库解决该问题,但相较之下,Rust 更为简便。