C标准库<ctype.h>实现

简介:

1.背景知识

ctype.h是C标准函数库中的头文件,定义了一批C语言字符分类函数(C character classification functions),用于测试字符是否属于特定的字符类别,如字母字符、控制字符等

我们经常将字符排序并分成不同的类别,为了识别一个字母,可以编写:

if('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z')
    ...... 

当执行字符集是ASCII码的时候,可以得到正确的结果,但是这种惯用用法不适合其他字符集

同样,为了判断一个数字,可以这样编写:

if('0' <= c && c <= '9')
    ......

判断空白,可以编写代码:

if(c == ' ' || c == '\t' || c == '\n')
    ......

但是问题来了,我们很快就会厌倦代码中充斥着类似这样的判断语句而变长,最容易联想的解决办法是引入函数来替代这些判断语句,于是将出现如下的代码:

if(isalpha(c))
    ...
if(isdigit(c))
    ...
if(isspace(c))
    ...

貌似问题得到了解决,但是考虑一个典型的文本处理程序对输入流中的每一个字符会平均调用3次这样的函数,就会严重影响程序的执行效率

于是想到进一步的改进,考虑使用宏来替代这些函数

#define isdigit(x) ((x) >= '0' && (x) <= '9')

这会产生问题,如宏参数x具有副作用;例如,如果调用isdigit(x++)isdigit(run_some_program()),可能不是很显然,isdigit的参数将被求值两次。早期版本的Linux就使用了这种潜在犯错的方法。关于宏的缺点,本文就不赘述。

为保障安全和代码紧凑,进一步的改进,使用一个或多个转换表的宏集合,每个宏有如下形式:

#define _XXXMASK 0x...
#define isXXX(c) (_Ctytable[c] & _XXXMASK)

字符c编入以_Ctytable命名的转换表索引中,每个表项的不同位以索引字符为特征。如果任何一个和掩码_XXXXMASK相对应的位被设置了,那个字符就要在测试类别中,对所有正确的参数,宏展开成一个紧凑的非零表达式。

这种方法的弊端:当宏的参数不在它的定义域内,就会访问转换表外的存储空间

2.<ctype.h>的内容

<ctype.h>定义的宏如下表所示:

isalnum 是否为字母数字
isalpha 是否为字母
islower 受否为小写字母
isupper 是否为大写字母
isdigit 是否为数字
isxdigit 是否为16进制数字
iscntrl 是否为控制字符
isgraph 是否为图形字符(例如,空格、控制字符都不是)
isspace 是否为空格字符(包括制表符、回车符、换行符等)
isblank 是否为空白字符 (C99/C++11新增)(包括水平制表符)
isprint 是否为可打印字符
ispunct 是否为标点
tolower 转换为小写
toupper 转换为大写

下图来自Plauger和Brodie的Standard C:

3.<ctype.h>的实现

P.J.Plauger版本C标准库 Ctype 中判断字符是否属于某个类型,主要是通过转换表来实现的

以判断是否为小写字母为例:

/* ctype.h */
#ifndef _CTYPE
#define _CTYPE
 
/* _Ctype 转换位 */
#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' - 'F', 'a' - 'f' */
 
/* 声明外部的 _Ctype 转换表 */
extern const short *_Ctype;
/* 判断是否为小写字母的带参数宏 islower */
#define islower(c) (_Ctype[(int)(c)] & _LO)
 
// 其余省略 ...
#endif

_Ctype 转换表:

/* xctype.c _Ctype 转换表 -- ASCII 版 */
#include <limits.h>
#include <stdio.h>
#include "ctype.h"
#if EOF != -1 || UCHAR_MAX != 255
#error WRONG CTYPE table
#endif
/* 组合位 */
#define XDI (_DI|_XD)
#define XLO (_LO|_XD)
#define XUP (_UP|_XD)
 
/* 转换表 */
static const short ctype_tab[257] = { 0, /* EOF */
    _BB, _BB, _BB, _BB, _BB, _BB, _BB, _BB,
    _BB, _CN, _CN, _CN, _CN, _CN, _BB, _BB,
    _BB, _BB, _BB, _BB, _BB, _BB, _BB, _BB,
    _BB, _BB, _BB, _BB, _BB, _BB, _BB, _BB,
    _SP, _PU, _PU, _PU, _PU, _PU, _PU, _PU,
    _PU, _PU, _PU, _PU, _PU, _PU, _PU, _PU,
    XDI, XDI, XDI, XDI, XDI, XDI, XDI, XDI,
    XDI, XDI, _PU, _PU, _PU, _PU, _PU, _PU,
    _PU, XUP, XUP, XUP, XUP, XUP, XUP, _UP,
    _UP, _UP, _UP, _UP, _UP, _UP, _UP, _UP,
    _UP, _UP, _UP, _UP, _UP, _UP, _UP, _UP,
    _UP, _UP, _UP, _PU, _PU, _PU, _PU, _PU,
    _PU, XLO, XLO, XLO, XLO, XLO, XLO, _LO,
    _LO, _LO, _LO, _LO, _LO, _LO, _LO, _LO,
    _LO, _LO, _LO, _LO, _LO, _LO, _LO, _LO,
    _LO, _LO, _LO, _PU, _PU, _PU, _PU, _BB,
};
const short *_Ctype = &ctype_tab[1];

举一个例子来说明:

当判断‘a’是否为小写字母的时候,使用宏islower,通过宏替换,也即执行(_Ctype[(int)(c)] & _LO)

预处理之后,假设当前的c是'a'那么变成了: (_Ctype[(int)('a')] & _LO)

字符'a'的值为97所以接下来便是: (_Ctype[97] & _LO)

_Ctype[97]的转换宏是 _LO ,通过查_Ctype 转换表, _LO 的值又是 0x10,所以最后是:

(_LO & _LO)   ---->    0x10 & 0x10    ---->    1, 说明当前字符为小写字母

其他的字符的判断都可以通过类似的替换与‘&’得到,不一一赘述。

附上linux内核中的ctype.h实现,基本原理相似

#ifndef _LINUX_CTYPE_H
#define _LINUX_CTYPE_H

/*
 * NOTE! This ctype does not handle EOF like the standard C
 * library is required to.
 */

#define _U    0x01    /* upper */
#define _L    0x02    /* lower */
#define _D    0x04    /* digit */
#define _C    0x08    /* cntrl */
#define _P    0x10    /* punct */
#define _S    0x20    /* white space (space/lf/tab) */
#define _X    0x40    /* hex digit */
#define _SP    0x80    /* hard space (0x20) */

extern const unsigned char _ctype[];

#define __ismask(x) (_ctype[(int)(unsigned char)(x)])

#define isalnum(c)    ((__ismask(c)&(_U|_L|_D)) != 0)
#define isalpha(c)    ((__ismask(c)&(_U|_L)) != 0)
#define iscntrl(c)    ((__ismask(c)&(_C)) != 0)
#define isdigit(c)    ((__ismask(c)&(_D)) != 0)
#define isgraph(c)    ((__ismask(c)&(_P|_U|_L|_D)) != 0)
#define islower(c)    ((__ismask(c)&(_L)) != 0)
#define isprint(c)    ((__ismask(c)&(_P|_U|_L|_D|_SP)) != 0)
#define ispunct(c)    ((__ismask(c)&(_P)) != 0)
/* Note: isspace() must return false for %NUL-terminator */
#define isspace(c)    ((__ismask(c)&(_S)) != 0)
#define isupper(c)    ((__ismask(c)&(_U)) != 0)
#define isxdigit(c)    ((__ismask(c)&(_D|_X)) != 0)

#define isascii(c) (((unsigned char)(c))<=0x7f)
#define toascii(c) (((unsigned char)(c))&0x7f)

static inline unsigned char __tolower(unsigned char c)
{
    if (isupper(c))
        c -= 'A'-'a';
    return c;
}

static inline unsigned char __toupper(unsigned char c)
{
    if (islower(c))
        c -= 'a'-'A';
    return c;
}

#define tolower(c) __tolower(c)
#define toupper(c) __toupper(c)

/*
 * Fast implementation of tolower() for internal usage. Do not use in your
 * code.
 */
static inline char _tolower(const char c)
{
    return c | 0x20;
}

#endif
/*
 *  linux/lib/ctype.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 */

#include <linux/ctype.h>
#include <linux/compiler.h>
#include <linux/export.h>

const unsigned char _ctype[] = {
_C,_C,_C,_C,_C,_C,_C,_C,                /* 0-7 */
_C,_C|_S,_C|_S,_C|_S,_C|_S,_C|_S,_C,_C,            /* 8-15 */
_C,_C,_C,_C,_C,_C,_C,_C,                /* 16-23 */
_C,_C,_C,_C,_C,_C,_C,_C,                /* 24-31 */
_S|_SP,_P,_P,_P,_P,_P,_P,_P,                /* 32-39 */
_P,_P,_P,_P,_P,_P,_P,_P,                /* 40-47 */
_D,_D,_D,_D,_D,_D,_D,_D,                /* 48-55 */
_D,_D,_P,_P,_P,_P,_P,_P,                /* 56-63 */
_P,_U|_X,_U|_X,_U|_X,_U|_X,_U|_X,_U|_X,_U,        /* 64-71 */
_U,_U,_U,_U,_U,_U,_U,_U,                /* 72-79 */
_U,_U,_U,_U,_U,_U,_U,_U,                /* 80-87 */
_U,_U,_U,_P,_P,_P,_P,_P,                /* 88-95 */
_P,_L|_X,_L|_X,_L|_X,_L|_X,_L|_X,_L|_X,_L,        /* 96-103 */
_L,_L,_L,_L,_L,_L,_L,_L,                /* 104-111 */
_L,_L,_L,_L,_L,_L,_L,_L,                /* 112-119 */
_L,_L,_L,_P,_P,_P,_P,_C,                /* 120-127 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,            /* 128-143 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,            /* 144-159 */
_S|_SP,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,    /* 160-175 */
_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,_P,    /* 176-191 */
_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,_U,    /* 192-207 */
_U,_U,_U,_U,_U,_U,_U,_P,_U,_U,_U,_U,_U,_U,_U,_L,    /* 208-223 */
_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,_L,    /* 224-239 */
_L,_L,_L,_L,_L,_L,_L,_P,_L,_L,_L,_L,_L,_L,_L,_L};    /* 240-255 */

EXPORT_SYMBOL(_ctype);
目录
相关文章
|
3月前
|
C语言
C语言 字符串操作函数
本文档详细介绍了多个常用的字符串操作函数,包括 `strlen`、`strcpy`、`strncpy`、`strcat`、`strncat`、`strcmp`、`strncpy`、`sprintf`、`itoa`、`strchr`、`strspn`、`strcspn`、`strstr` 和 `strtok`。每个函数均提供了语法说明、参数解释、返回值描述及示例代码。此外,还给出了部分函数的自实现版本,帮助读者深入理解其工作原理。通过这些函数,可以轻松地进行字符串长度计算、复制、连接、比较等操作。
|
4月前
|
存储 安全 C语言
C语言中的字符串处理
C语言中的字符串处理
113 0
|
6月前
|
存储 C语言
c语言scanf函数用法
c语言scanf函数用法
|
7月前
|
安全 算法 网络安全
C语言在安全领域的应用
本文探讨了C语言在网络安全中的应用,包括密码学算法实现、网络安全工具开发和安全协议实现。C语言因其高效性、可控性和跨平台性,常用于实现AES、RSA等加密算法,开发网络扫描器和入侵检测系统,以及实现SSL/TLS、IPSec等安全协议。代码示例展示了C语言如何进行AES加密解密。尽管C语言在安全领域有显著优势,但面对不断演变的威胁,持续学习和研究新的安全技术至关重要。
|
7月前
|
存储 安全 编译器
【c语言】字符串常见函数 上
【c语言】字符串常见函数 上
45 1
|
7月前
|
程序员 C语言 开发者
C语言库函数 — 字符串函数(含模拟实现字符串函数)
C语言库函数 — 字符串函数(含模拟实现字符串函数)
70 0
|
存储 编译器 C语言
初识C语言(四)
初识C语言(四)
|
存储 程序员 编译器
初识C语言(4)
初识C语言(4)
87 0
|
C语言 C++
C语言常见字符串函数解析(上)
C语言常见字符串函数解析(上)
|
C语言
C语言假期作业 DAY 14
C语言假期作业 DAY 14