字符串、字符和字节
1. 字符串
1.1 字符串基础
字符串就是一串零个或多个字符,并且以一个位模式为全0的NUL字节结尾。NUL字节是字符串的终止符,但是它本身不是字符串的一部分,所以字符串的长度并不包括NUL字节。
头文件string.h包含了使用字符串函数所需的原型和声明。
1.2 字符串长度
字符串的长度就是它所包含的字符个数。strlen可求字符串长度,返回值是size_t类型,该类型在头文件stddef.h中定义,是一个无符号整数类型。在表达式中使用无符号数可能会导致不可预料的结果,例如,下面两个表达式看上去是相等的:
if (strlen(x) >= strlen(y))....
if (strlen(x) - strlen(y) >= 0)...
但事实上它们是不相等的,第1条语句按照预想的工作,但第2条语句的结果将永远是真。strlen的结果是个无符号数,所以操作符>=左边的表达式也将是无符号数,而无符号数绝不可能是负的。
表达式中如果同时 包含了有符号数和无符号数,可能会产生奇怪的结果。如果把strlen的返回值强制转换为int,就可以消除这个问题。
寻找一种更好的算法比改良一种差劲的算法更有效率,复用已经存在的软件比重新开发一个效率更高。
1.3 函数的返回值
strcpy和strcat都返回它们第一个参数的一份拷贝。就是一个指向目标字符数组的指针。由于它们返回这种类型的值,所以可以嵌套地调用这些函数,如:
strcat(strcpy(dst, a), b);
但是这种嵌套调用的风格较可读性更佳的拆开的风格在功能上并无优势。
1.4 字符串比较
int strcmp(char const *s1, char const *s2);
如果s1小于s2,strcmp函数返回一个小于零的值,如果s1大于s2,函数返回一个大于零的值,如果两个字符串相等,函数返回零。
把这种返回值当做布尔值进行测试是一种坏风格,因为它具有三个截然不同的结果,所以,更好的方法是把这个返回值与零进行比较。
1.5 长度受限的字符串函数
和strcpy一样,strncoy把源字符串的字符复制到目标数组,但是它总是正好向dst写入len个字符。如果strlen(src)的值小于len,dst数组就用额外的NUL字节填充到len长度。如果strlen(src)的值大于或等于len,那么只有len个字符被复制到dst中。注意!它的结果将不会以NUL字节结尾。
所以,strncpy调用的结果可能不是一个字符串,因为字符串必须以NUL字节结尾。如果在一个需要字符串的地方(例如strlen函数的参数)使用了一个不是以NUL结尾的字符序列,strlen()会一直找到发现一个NUL为止,如果函数试图访问系统分配给这个程序以外的内存范围,程序就会崩溃。这个问题只有当你使用strncpy函数创建字符串,然后或者对它们使用str开头的库函数,或在printf中使用%s格式打印它们时才发生。在使用不受限的函数之前,你首先必须确定字符串实际上是以NUL字节结尾的。
而strncat总是在结果字符串后面添加一个NUL字节。strncat不管目标参数除去原先存在的字符串后留下的空间够不够。
1.6 字符串查找基础
1.6.1 查找一个字符
在一个字符串中查找一个特定字符最容易的方法是使用strchr和strrchr函数。
char * strchr(char const *str, int ch);
char * strrchr(char const *str, int ch);
1.6.2 查找任何几个字符
char * strpbrk(char const *str, char const *group);
1.6.3 查找一个子串
char * strstr(char const *s1, char const *s2);
标准库中不存在strrstr或strrpbrk
1.6.4 查找一个字符串前缀
1.6.5 查找标记
char * strtok(char * str, char const *sep);
1.7 内存操作
根据定义,字符串由一个NUL字节结尾,所以字符串内部不能包含任何NUL字符。但是,非字符串数据内部包含零值的情况并不罕见,你无法使用字符串函数来处理这种类型的数据,因为当它们遇到第1个NUL字节时将停止工作。
此时,我们可以使用另一组相关的函数,它们的操作与字符串函数类似,但这些函数能够处理任意的字节序列。
void *memcpy(void * dst, void const *src, size_t length);
void *memmove(void *dst, void const *src, size_t length);
void *memcmp(void const * a, void const * b, size_t length);
void *memchr(void const *a , int ch, size_t length);
void *memset(void *a, int ch, size_t length);
每个原型都包含一个显示的参数说明需要处理的字节数,但和strn带头的函数不同,它们在遇到NUL字节时不会停止操作。
memcpy从src的起始位置复制length个字节到dst的内存起始位置。你可以用这个方法复制任何类型的值,第3个参数指定复制值的长度(以字节计)。如果src和dst以任何形式出现了重叠,结果的未定义的。
如果两个数组都是整型数组,不需要使用强制类型转换,因为在函数的原型中,参数的类型是void *指针,任何类型的指针都可以转换为viod*型指针。
2. 警告的总结
- 在应该使用有符号数的表达式中使用strlen函数(strlen返回无符号)
- 在表达式中混用有符号数和无符号数
- 使用strcpy函数把一个长字符串复制到一个较短的数组中,导致溢出。
- 使用strcat函数把一个长字符串添加到一个数组中,导致数组溢出
- 把strcmp函数的返回值当做布尔进行测试
- 把strcmp函数的返回值与1和-1进行比较
- 使用并非以NUL字节结尾的字符序列
- 使用strncpy函数产生不以NUL字节结尾的字符串
- 把strncpy函数和strxxx族函数混用
- 忘了strtok函数会修改它所处理的字符串
-
strtok函数是不可再入的
3.编程提示的总结
- 不要试图自己编写功能相同的函数来取代库函数
- 使用字符分类和转换函数可以提高函数的移植性