Linux 中的小小编程技巧(一) 字符类别判断
做驱动开发已经有一段时间了,每日只是关注内核及驱动方面的一些内容,很少查看诸如string.c等类文件的内容。某日看了些这些代码后,颇有感触,感叹其设计方法和技巧,遂做下笔记,留着学习。
今天要说的是lib/ctype.c和include/linux/ctype.h两个文件内的部分内容,内容时关于如何判断一个字符是何种类 型的,比如是否为控制字符,是否为大写,是否为写等。
一般初学情况下判断一个字符为大写的方法用if 语句来判断,比如
If (c>=’A’&&c<=’Z’) return true;
小写的方法与之类似
因为字符A-Z,a-z在ASCII上是连续的,所以一个范围判断就可以了,假设某些字符在ASCII上是不连续的,比如十六进制数,0-9,A-H,那么if语句里面就需要两个范围判断了
If((c>=’0’&&c<=’9’)||(c>=’A’&&c<=’H’)) return true;
再者假设有更多的范围,那么if的条件部分就越长,判断条件长了,效率自然就低了。
Linux中使用空间换时间的方法来做,基本方法为:
建立一个256个元素的unsigned char _ctypes[]全局数组,每一个数组元素的值代表其下标所对应的ASCII码的类别,比如大写字母A,其ASCII码为65,那么_ctypes[65]中的8个位代表了其所属类别,8个位最多有8中并列的属性。
在Linux中定义了8种属性,其宏为:
#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) */
Linux中将_ctypes初始化为:
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 */
然后Linux定义了一个宏:
#define __ismask(x)(_ctype[(int)(unsigned char)(x)])
此宏获得一个字符所对应数组中的标志值,
Linux中又定义了一些宏用以辅助判断一个字符类别:
#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) /* punct */
/* Note: isspace() must return false for %NUL-terminator */
#define isspace(c) ((__ismask(c)&(_S)) != 0) /*(space/lf/tab) */
#define isupper(c) ((__ismask(c)&(_U)) != 0) //是否大写
#define isxdigit(c) ((__ismask(c)&(_D|_X)) != 0) //是否十六进制数
#define isascii(c) (((unsigned char)(c))<=0x7f) //是否ASCII
#define toascii(c) (((unsigned char)(c))&0x7f) //转为ASCII
上述宏在程序中可以直接使用了,比如想判断某个字符是否为大写
If (isupper(‘A’)) return true;
If (isxdigit(‘B’)) return true;
比如:
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;
}
Linux中方法已经描述完了,下面对比优劣。
普通方法:
优势:简单易懂,不额外占用数据空间
劣势:效率较第二种低,需要很多条件判断指令
Linux方法:
优势:速度快,一次性位操作即可完成任务,优雅。
劣势:额外的占用了空间。
综合:
如果自己随便写个小小程序的话,第一种方法够用了。如果是一个真实的项目的,推荐第二种方法。