• 2006-07-17

    P.J.Plauger版本C标准库实现分析之'ctype.h' - [语言探索]

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://bigwhite.blogbus.com/logs/2853205.html

    如果在你的源代码中经常见到如下代码:
    /* To Identify a letter */
    if ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z'))

    /* To Identify a digit */
    if ( i >= '0' && i <= '9')

    这说明你对头文件理解的不是很好,而也恰恰是为了减少代码中重复出现的各种'字符分类'代码而设置的。

    中的接口常用来进行数据的校验和分类,如在我们的项目中它常被用来校验原始数据的'符合性'。比如说一个11位的手机号码就必须是一个全数字的字符串,我们可以选择'isdigit'来进行测试,如果返回失败,则说明原始数据不符合要求,校验失败。

    首先这里有两件事不会提及,首先是对中各个接口的说明,你可以参见'ANSI C标准'文档,也可以参考各种C手册来找到你的答案;另外一点就是暂不考虑locale对中各种接口行为的影响问题,我们仅仅在'C' locale的范围内考虑问题。

    Ok,有了上面两个前提,我们就可以考虑如何实现了。传统的方案,同时也是P.J.Plauger实现方案之一,那就是使用'Translation Table'和宏。宏的好处大家都很明了,不外乎可读性好+性能优越,它也是C程序员一直偏爱的工具,尽管现在很多人对之嗤之以鼻,我们依然在很多的源代码中大量的见到它的身影。

    P.J.Plauger的实现方案有三个值得注意的地方:
    首先我们来看看他声明的(摘录其中一部分)

    /* ctype.h */
    #ifndef _CTYPE
    #define _CTYPE

    /* _Ctype code bits */
    #define _XA 0x200 /* extra alphabetic */
    #define _XS 0x100 /* extra space */
    #define _BB 0x80 /* BEL, BS, etc */
    #define _CN 0x40 /* CR, FF, HT, NL, VT */
    #define _DI 0x20 /* '0' ~ '9' */
    #define _LO 0x10 /* 'a' ~ 'z' */
    #define _PU 0x08 /* punctuation */
    #define _SP 0x04 /* space */
    #define _UP 0x02 /* 'A' ~ 'Z' */
    #define _XD 0x01 /* '0' ~ '9', 'a' ~ 'z', 'A' ~ 'Z' */

    int isdigit(int);
    extern const short *_Ctype;
    ....

    #define isdigit(c) (_Ctype[(int)(c)] & _DI)

    #endif

    /* isdigit.c */
    #include

    #define XDI (_DI|_XD)
    ... ...

    int (isdigit)(int c)
    {
     return (_Ctype[c] & _DI);
    }

    中有一处奇怪的地方,那就是每个接口函数都有一个同名的宏与之对应。再看看isdigit.c中isdigit接口的实现是int (isdigit)(int c),而不是int isdigit(int c),如果是后者,编译都会有问题。不是很了解P.J.Plauger为什么要这么做,Maybe是为了提供多种字符处理的方案,你可以这样来使用宏:
    int a = 0;
    a = isdigit(5);

    同样你也可以这样来选择使用函数接口:
    int  b = 0;
    b = (isdigit)(5);

    第二个值得注意的地方就是'Translation Table'转换的原理了,以检查digit为例,先看看ctype_tab表是什么样子的:
    static const short ctype_tab[257] = { 0, /* EOF */
    ..., ..., ...,
    ..., ..., ...,
    ...
    ...
    ...
    ..., XDI, ...,
    ...
    };
    const short * _Ctype = &ctype_tab[1];

    注意这个表支持另外一个额外的值'EOF'宏,这样表的大小就是257,而非256了。

    当我们调用isdigit的时候,如:
    c = '5';
    if (isdigit(c)) {
     printf("c is a digit\n");
    } else {
     printf("c is not a digit\n");
    }

    如上面isdigit实现,它把参数作为index在转换表中找到相应表项,然后与_DI宏做'与'操作。如c = '5',其在ASCII码表中的index为53,我们在转换表中找出index为53的那个表项是XDI,然后XDI & _DI,结果为真。当然转换表中的表项都是事先按照ASCII码标安排好的。

    第三个值得注意的地方就是ctype_tab数组类型为short。按照P.J.Plauger的说法他之所以选择short而非unsigned char类型是因为他觉得这样的实现拥有最大的portability,易于以后支持其他各种locale。当然如果你能完全排除支持其他locale的念头,你大可使用unsigned char,而且这样可以更好的节省空间。

    中还提供toupper和tolower两个接口,这两个接口的实现也分别各需要一个转换表。这里就不详细叙述了。

    附录
    P.J.Plauger版本C标准库实现分析之'assert.h'


    收藏到:Del.icio.us




    引用地址:

    评论

  • 请问你有没有

    P.J.Plauger C标准库 实现这本书的电子版?
    Tony Bai回复grantguo说:
    有djvu文件格式的版本,你在www.itpub.net/forum61.html上可以搜索到或者到csdn下载也能找到。
    2008-09-02 21:33:40
  • Good!



    btw:能不能把这本书的电子版发给我一份?呵呵,谢谢哈~
    Tony Bai回复Landy说:
    已经给你发过去了:)
    2006-07-18 12:57:31