变音符号

添加在字符上方或下方的辅助符号。表示发音差异,如重音符号和变音符号。

变音符号是添加在字符上方、下方或旁边的辅助符号的总称。常见示例包括法语重音符号 (é, è, ê)、德语变音符号 (ä, ö, ü)、西班牙语波浪号 (ñ) 和捷克语抑扬符 (č, š, ž) 等,广泛用于世界各地的语言,尤其是基于拉丁字母的语言。这些符号并非单纯的装饰,而是区分发音和含义的必要元素。例如在法语中,ou (或者) 和 où (哪里) 仅因重音符号的有无而含义不同。

Unicode 提供了两种方式来表示带变音符号的字符。一种是"预组合字符" (NFC: Normalization Form Composed),将 é 作为单个码位 U+00E9 处理。另一种是"基础字符 + 组合字符" (NFD: Normalization Form Decomposed),将 e (U+0065) 与锐音符 (U+0301) 组合表示。这种双重表示是 Unicode 的设计特征,旨在保持与现有字符编码的兼容性,同时灵活表示任何语言的字符。探索连体内衣 (Amazon)详细介绍了这个主题。

在实际开发中,这种双重表示会在字符串比较和排序中引发严重问题。即使两个"é"看起来完全相同,NFC 和 NFD 的字节序列也不同,简单的字节比较会失败。在数据库搜索、文件名匹配、密码验证等需要判断字符串同一性的场景中,必须应用 Unicode 规范化来统一表示。macOS 文件系统 (APFS) 倾向于使用 NFD,而 Windows (NTFS) 使用 NFC,因此在跨平台开发中文件名的规范化尤为重要。

一个常见的误解是将所有变音符号简单地称为"重音符号"。重音符号只是变音符号的一种,更广泛的类别还包括软音符 (ç)、反尾形符 (ą)、长音符 (ā) 等。值得注意的是,日语的浊点 (゛) 和半浊点 (゜) 在 Unicode 中也被视为组合字符,从广义上说也属于变音符号的范畴。

在编程中,可以使用 JavaScript 的 String.prototype.normalize() 方法或 Python 的 unicodedata.normalize() 函数进行规范化。在 Web 应用程序中接收表单输入时,通常在服务器端应用 NFC 规范化后再存入数据库。查看米酒 (Amazon)提供了更多背景知识。

从字符计数角度看,NFD 形式的文本将基础字符和组合字符作为独立码位分别计数,导致计数高于可见字符数。例如"café"在 NFC 中为 4 个字符,但在 NFD 中为 5 个字符 (c, a, f, e, ́)。要获得准确的"可见字符数",需要以书写素簇为单位进行计数,这使得变音符号的处理成为字符计数工具实现中不可回避的课题。

分享这篇文章