字符编码科谱:在MUD开发中怎么判断一个字符是不是汉字?

在现有的GBK编码的MUD开发中我们都习惯直接用 is_chinese() 判断用户输入的字符是不是汉字,也都知道这是一个模拟外部函数,相关代码如下:

int is_chinese(string str)
{
    int i;

    if( strlen(str) < 2 ) return 0;

    for( i= 0; i < strlen(str); i++)
    {
        if( str[i] < 161 || str[i] == 255 ) return 0;
        if( !(i%2) && (str[i] < 176 || str[i] >= 248) ) return 0;
    }

    return 1;
}

但是你知道为什么这样可以判断一个字符是不是汉字吗?能看懂这段代码的意思吗?

现在进入知识科谱时间,虽然你不学不影响使用,但学习掌握后不只是在MUD中有用,所以最好了解一下,这里涉及到文字编码相关知识,英文、阿拉伯数据和控制字符在计算机中使用的是ASCII编码,而汉字使用的是GB2312、GBK等编码。

ASCII编码

ASCII ((American Standard Code for Information Interchange): 美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符

标准ASCII码也叫基础ASCII码,使用7位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符。其中:

0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8、9、10 和13 分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响。

32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。

GB2312编码

因为ASCII编码只能存128个字符,而汉字有上万个,常用的也有数千个,怎么办?GB2312-80编码为汉字而生。

GB2312-80 是 1980 年制定的中国汉字编码国家标准。共收录 7445 个字符,其中汉字 6763 个。GB2312 兼容标准 ASCII码,采用扩展 ASCII 码的编码空间进行编码,一个汉字占用两个字节,每个字节的最高位为 1。具体办法是:收集了 7445 个字符组成 94 * 94 的方阵,每一行称为一个“区”,每一列称为一个“位”,区号位号的范围均为 01-94,区号和位号组成的代码称为“区位码”。区位输入法就是通过输入区位码实现汉字输入的。将区号和位号分别加上 20H,得到的 4 位十六进制整数称为国标码,编码范围为 0x2121~0x7E7E。为了兼容标准 ASCII 码,给国标码的每个字节加 80H,形成的编码称为机内码,简称内码,是汉字在机器中实际的存储代码GB2312-80 标准的内码范围是 0xA1A1~0xFEFE

上面介绍这么多,关键就是二点:

  1. 一个汉字占用两个字节,每个字节的最高位为 1
  2. 编码范围是 0xA1A1~0xFEFE

16进制A1为十进制161,16进制FE为十进制254,简单的判断方法就出来了,GB2312编码下如果获取的字符的编码小于161或大于254就肯定不是中文了。关键代码:

if( str[i] < 161 || str[i] == 255 ) return 0;

但是,GB2312编码中并不全是汉字,7445个字符只有6763个汉字,具体汉字编码范围是0xB0A1~0xF7FE,如果一个字符的首字节编码小于0xB0(十进制176)或大于等于F8(十进制248)就不是汉字。关键代码:

if( !(i%2) && (str[i] < 176 || str[i] >= 248) ) return 0;

再回过头看看这个函数代码:

int is_chinese(string str)
{
    int i;

    if( strlen(str) < 2 ) return 0;

    for( i= 0; i < strlen(str); i++)
    {
        if( str[i] < 161 || str[i] == 255 ) return 0;
        if( !(i%2) && (str[i] < 176 || str[i] >= 248) ) return 0;
    }

    return 1;
}

已经明白了吗?因为汉字是2个字节,驱动提供的外部函数strlen对中文字符返回长度是2,而LPC中字符串也可以当字符数组操作,通过str[i]即可取得字符编码进行判断。

GBK编码

因为GB2312编码收录的汉字并不全,后来国家又发明了GBK编码,GBK编码是对GB2312编码的扩展,因此完全兼容GB2312-80标准。GBK编码依然采用双字节编码方案,其编码范围:8140-FEFE,剔除xx7F码位,共23940个码位。共收录汉字和图形符号21886个,其中汉字(包括部首和构件)21003个,图形符号883个。GBK编码支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。GBK编码方案于1995年12月15日正式发布,这一版的GBK规范为1.0版。

GBK编码汉字增加了非常多,包括繁体字等,而且兼容GB2312编码,如果你想让你的MUD接受玩家输入繁体字、生僻字,那可以参考GBK编码中汉字的编码范围改造is_chinese模拟外部函数。只是因为在我们游戏中用GB2312编码判断汉字就足够了,不需要允许玩家用各种冷门的文字,没有折腾的必要。

UTF-8编码

GBK编码是中国用的,不是世界统一的,如果别人用的不是GBK编码,那就出现乱码,比如用GBK编码打开非GBK编码的内容可能出现“烫烫烫”和“屯屯屯”。为了世界大统一,就出现了Unicode万国码,这是一种把全世界的字符都收录了的一套字符编码方案,每个字符都有唯一的编号,此方案的字符编号兼容ASCII编码,在Unicode中规定了中文基本汉字范围为4E00-9FA5,其这每个编号对应哪个汉字我们可以看这里:https://www.unicode.org/charts/PDF/U4E00.pdf

但是Unicode只是字符集,不是具体编码方案,UTF-8编码是此方案的最广泛使用的具体实现。UTF-8编码规则:如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的字节数,其余各字节均以10开头。

从Unicode字符集中规定0x4E00代表汉字4E00的十进制是19968,二进制是100 1110 0000 0000,根据UTF-8的编码规则,需要三个字节才能存储,转化结果为11100100 10111000 10000000,其十六进制为0xE4 0xB8 0x80

关于 mudos 、 fluffos v2017 和 fluffos v2019 的编码说明

MUDOS和FluffOS v2017都不支持UTF8编码,FluffOS v2019支持UTF-8编码。

mudos 和 fluffos v2017 的字符编码支持是一致的,都是使用系统内码ANSI(简体中文系统下是GBK),英文字符占1个字节,中文字符占2个字节,strlenstrwidth返回一致(英文1,中文2),使用str[0]str[1]可以取一个汉字的二个字节编码。

但是在 fluffos v2019 中所有文字统一为utf-8编码,对strlenstrwidth做了调整,strlen返回的是字符个数,strwidth返回字符宽度,要注意的是使用str[0]直接返回这个字符的unicode编码,如果要取得汉字的GBK(2字节)或utf-8(3字节)编码,需要使用string_encode转化为buffer后操作。如下代码:

int main(object me, string arg)
{
    if (!arg)
    {
        arg = "一";
    }

    printf("Unicode: %O\n", arg[0]);
    printf("GBK: %O\n", string_encode(arg, "GBK"));
    printf("UTF-8: %O\n", string_encode(arg, "utf-8"));

    return 1;
}

输出结果为:

Unicode: 19968
GBK: BUFFER ( /* sizeof() == 2 */
  0xd2,
  0xbb
)
UTF-8: BUFFER ( /* sizeof() == 3 */
  0xe4,
  0xb8,
  0x80
)

因为可以直接用str[0]取得字符的Unicode编码,所以,在fluffos v2019下,判断汉字的方法可以用如下代码:

int is_chinese(string str)
{

    if (strwidth(str) < 2)
        return 0;

    for (int i = 0; i < strlen(str); i++)
    {
        if (str[i] < 19968 || str[i] > 40869)
            return 0;
    }

    return 1;
}

不过,在v2019中判断中文有更简单的方式,可以直接使用pcre正则表达式判断中文,不需要自己通过字符编码范围判断。代码如下:

int is_chinese(string str)
{
    if (!str)
        return 0;

    return pcre_match(str, "^\\p{Han}+$");
}

参考网址:

京ICP备13031296号-4