在早期的计算机时代,字符编码相对简单,例如ASCII编码中,大写字母和小写字母之间的转换只需翻转一个比特位。例如,将ASCII字母"A"(0x41)转换为小写"a"(0x61),只需进行一次按位或操作(OR)与0x20。然而,随着多语言字符集的出现,大小写转换变得复杂起来。以名字"Neacșu"为例,在所有大写的情况下应该是"NEACȘU"。在Unicode中,"带音标的小写拉丁字母s"的编码是0x15E,而"带音标的大写拉丁字母S"的编码是0x15F。尽管它们之间只相差一个比特位,但这个比特位是不同的。
本文继续探讨Windows中的UTF-8系列,并展示了一个多语言环境下的tolower和toupper函数的实现。可以从GitHub网站下载最新版本的代码。
对于任何程序员来说,了解世界上所有语言的字母表中的大写和小写字母是一项艰巨的任务。幸运的是,Unicode联盟(负责管理Unicode的组织)在创造表情符号之余,也创建了一个长长的大小写折叠代码列表。可以从这个链接下载列表。该文档描述了四种类型的大小写折叠:常见、完整、简单和土耳其语。函数只实现了标准和简单的情况。
实现的基本思想相当简单。将Unicode文档转换为两个等大小的表,一个包含大写字母,另一个包含小写字母。大写表是排序的,以允许二分查找。如果在大写表中找到了一个代码,就用小写表中匹配的代码替换它。
以下是tolower函数的代码示例:
#include "uppertab.c"
std::string tolower(const std::string& str) {
u32string wstr = runes(str);
for (auto ptr = wstr.begin(); ptr < wstr.end(); ptr++) {
char32_t* f = lower_bound(begin(u2l), end(u2l), *ptr);
if (f != end(u2l) && *f == *ptr)
*ptr = lc[f - u2l];
}
return narrow(wstr);
}
"uppertab.c"是由一个小程序生成的,它读取Unicode大小写折叠文件并生成两个表u2l和lc。以下是它的一个简短示例:
//大写表
static char32_t u2l[] = {
0x00041, // LATIN CAPITAL LETTER A
0x00042, // LATIN CAPITAL LETTER B
0x00043, // LATIN CAPITAL LETTER C
...
};
//小写等价
static char32_t lc[] = {
0x00061,
0x00062,
0x00063,
...
};
输入字符串通过调用utf8::runes函数转换为UTF-32。然后,每个字符在u2l表中进行搜索,如果找到,则用lc表中匹配的字符替换。搜索使用lower_bound函数执行二分查找。结果字符串转换回UTF-8,就是这样。
toupper函数非常相似,只是它使用在"lowertab.c"文件中定义的表l2c和uc。
为了完整性,这两个函数也有一个就地变体:
void tolower(std::string& str);
void toupper(std::string& str);
另一个函数是utf8::icompare,它执行不区分大小写的字符串比较:
int icompare(const std::string& s1, const std::string& s2);
它的行为就像std::string::compare,如果s1(转换为小写)在s2(转换为小写)之前,返回一个负值,或者如果s1在s2之后,返回一个正值。如果两个字符串的小写版本相等,函数返回0。
表生成程序gen_casetab.cpp也很简单。它读取并解析大小写折叠文本文件,首先生成"uppertab.c"文件,然后生成"lowertab.c"文件。在此过程中,它必须重新排序表,该表最初是按大写代码排序的,以使其按小写代码排序。
所有函数都在utf8命名空间中。由于这些函数和这个命名空间中的许多其他函数与标准C/C++函数同名,建议是不要使用"using"指令。下面是一个简短的示例,展示了如何调用这些函数:
#include
...
string all_caps = utf8::toupper(u8"Neacșu");
// all_caps应该是"NEACȘU"
string greek {u8"αλφάβητο"};
utf8::toupper(greek);
//字符串应该是"ΑΛΦΆΒΗΤΟ"
一个有趣的点是,有多个大写代码折叠到同一个小写代码中。在实现中,第二个代码从表中被丢弃。这似乎可以正常工作,但整个想法有点令人不安:这意味着没有一种唯一的方法将小写字符串转换为其等效的大写形式。就个人而言,认为Unicode联盟的表中有一些奇怪的选择。例如,大写拉丁字母K和开尔文温度符号(K)都被折叠成小写(k)。