“字符串是一种重要的数据类型,但是C语言并没有显式的字符串数据类型,因为字符串以字符串常量的形式出现或者存储于字符数组中。字符串常量适用于那些程序不会进行修改的字符串。所有其他字符串必须存储于字符数组或动态分配的内存中。本文描述处理字符串和字符的库函数,以及一些相关的,具有类似能力的函数。”
01
—
字符串基础
首先,我们了解下字符串的基础知识。字符串就是一串零个或多个字符,并且以一个位模式为全0的NUL字节结尾。因此,字符串包含的字符内部不能出现NUL字节。这个限制很少会引起问题,因为NUL字节并不存在与它相关联的可打印字符,这也是它被选为终止符的原因。NUL字节是字符串的终止符,但它本身并不是字符串的一部分,所以字符串的长度并不包括NUL字节。
02
—
字符串的长度
字符串的长度就是它所包含的字符个数。我们很容易通过对字符进行计数来计算字符串的长度,下面程序就是这样做的。
#include <stddef.h>size_t strlen(char const *string){ int length; for (length = 0; *string++ != '\0'; ) length += 1; return length;}
这种实现方法说明了处理字符串所使用的的处理过程的类型。但是,事实上你极少需要编写字符串函数,因为标准库所提供的函数通常能完成这些任务。不过,如果你还是希望自己编写一个字符串函数,请注意标准保留了所有以str开头的函数名,用于标准库将来的拓展。
03
—
不受限制的字符串函数
最常用的字符串函数都是“不受限制”的,就是说它们只是通过寻找字符串参数结尾的NUL字节来判断它的长度。这些函数一般都指定一块内存用于存放结果字符串。在使用这些函数时,程序员必须保证结果字符串不会溢出这块内存。这节将做详细讨论。
复制字符串
用于复制字符串的函数是strcpy,它的原型如下所示:
char *strcpy(char *dst, char const *src);
这个函数把参数src字符串复制到dst参数,如果src和dst在内存中出现重叠,其结果是未定义的。由于dst参数将进行修改,所以它必须是个字符数组或者是一个指向动态分配内存的指针,不能使用字符串常量。
目标参数的的以前内容将被覆盖并丢失。即使新的字符串比dst原先的内存更短,由于新字符串是以NUL字节结尾的,所以老字符串最后剩余的几个字符也会被有效地删除。
char messgae[] = "Original messgae";...strcpy(message, "Different");
顺利执行strcpy之后,数组将包含下面内容:
|'D'|'i'|'f'|'e'|'r'|'e'|'n'|'t'|0|'e'|'s'|'s'|'a'|'g'|'e'|0|
第一个NUL字节后面的几个字符无法被字符串函数访问,因此从实际角度来看,它们已经是丢失的了。
警告:
你必须保证目标字符数组的空间足以容纳需要复制的字符串,如果字符串比数组长,多余的字符仍被复制,它们将覆盖原先存储于数组后面的内存空间的值,strcpy无法解决这个问题,因为它无法判断目标字符数组的长度。例如:
char message[] = "Original messgae";...strcpy(message, "A different message");
第二个字符串太长了,无法容纳于message数组中。因此,strcpy函数将侵占数组后面的部分内存空间,改写原先恰好存储在那里的变量。如果你在使用这个函数前确保目标函数足以容纳源字符串,就可以避免大量调试工作。
2.连接字符串
要想把一个字符串添加(连接)到另一个字符串的后面,你可以使用strcat函数。它的原型如下:
char *strcat(char *dst, char const *src);
strcat要求dst参数原先已经包含了一个字符串(可以是空字符串),它找到这个字符串的末尾,并把src字符串的一份拷贝添加到这个位置。如果src和dst的位置发生重叠,其结果是未定义的。
下面是它的常见用法,
char name[] = "Jim";strcpy(message, "Hello ");strcat(message, name);strcat(message, ", how are you?");
每个strcat函数的字符串参数都被添加到原先存于message数组的字符串后面,其结果是下面这个字符串:
Hello Jim, how are you?
3.函数的返回值
strcpy和strcat都返回它们第一个参数的一份拷贝,就是一个指向目标字符数组的指针,由于它们都是返回这种类型的值,所以你可以嵌套地调用这些函数,如下所示:
strcat(strcpy(dst, a), b);
strcpy首先执行。它把字符串从a复制到dst并返回dst。然后这个返回值称为strcat函数的第一个参数,strcat把b添加到dst后面。
这种嵌套调用的风格较之下面这种可读性更佳的风格在功能上并无优势。
strcpy(dst, a);strcat(dst, b);
事实上,这些函数的的绝大多数调用中,它们的返回值只是被简单地忽略。
4.字符串比较
比较两个字符串涉及对两个字符串对应的字符逐个比较,直到发现不匹配为止。那个最先不匹配的字符中较“小”(在字符集中的序数较小)的那个字符所在的字符串被认为小于另外一个字符串。如果其中一个字符串是另一个字符串的前面一部分,那么它也被认为小于另外一个字符串,因为它的NUL结尾字节出现得更早。这种比较被称为“词典比较”,对于只包含大写字母或只包含小写字母的字符比较,这种比较过程所给出的结果总是和我们日常所用的字母顺序的比较相同。
库函数strcmp用于比较两个字符串,它的原型如下:
int strcmp(char const *s1, char const *s2);
如果s1小于s2,函数返回一个小于零的值,如果s1大于s2,函数返回一个大于零的值。如果两个字符串相等,函数就返回零。
04
—
长度受限的字符串函数
标准库还包含了一些函数,它们以一种不同的方式处理字符串。这些函数接受一个显式的长度参数,用于限定进行复制或比较的字符数。这些函数提供了一种方便的机制,可以防止难以预料的长字符从它们的目标数组溢出。
这些函数的原型如下,和它们不受限制的版本一样,如果源参数与目标参数发生重叠,strncpy和strncat的结果就是未定义的。
char *strncpy(char *dst, char const *src, size_t len);char *strncat(char *dst, char const *src, size_t len);int strncmp(char const *s1, char const *s2, size_t len);
和strcpy一样,strncpy把源字符串的字符复制到目标数组。然而,它总是正好向dst写入len个字符。如果strlen(src)的值小于len,dst就用额外的NUL字符填充到len长度。如果strlen(src)的值大于等于len,那么只有len个字符被复制到dst中。注意!它的结果将不会以NUL字节结尾。
警告:
strncpy调用的结果可能不是一个字符串,因为字符串必须以NUL字节结尾。如果在一个需要字符串的地方(如strlen函数的参数)使用了一个不是以NUL字节结尾的字符序列,会发生什么情况呢?strlen函数将无法知道NUL字节是没有的,所以它会继续查找,直到它发现一个NUL字节为止。或许找了几百个字符才找到,而strlen函数的这个返回值从本质上说是一个随机数。或者,如果函数试图访问系统分配给这个程序以外的内存范围,程序就会崩溃。
尽管strncat也是一个长度受限的函数,但它和strncpy存在不同之处。它从src中最多复制len个字符到目标数组后面。但是,strncat总是在结果字符串后面添加一个NUL字节,且它不会像strncpy那样对目标数组用NUL字节进行填充。注意目标数组中原先的字符串并没有算在strncat的长度中。strncat最多向目标数组复制len个字符(再加一个结尾的NUL字节),它不会管目标参数除去原先字符串后剩下的空间够不够。
最后,strncmp也用于比较两个字符串,但它最多比较len个字节。如果两个字符串在第len个字符之前存在不相等的字符,这个函数就像strcmp一样停止比较,返回结果。如果两个字符串的前len个字符相等,函数就返回零。
05
—
字符串查找
标准库中存在许多函数,它们用各种不同的方法查找字符串。这样各种各样的工具给了码手很大的灵活性。
1.查找一个字符
在一个字符串中查找一个特定字符最容易的方法是使用strchr和strrchr函数,它们的原型如下:
char *strchr(char const *str, int ch);char *strrchr(char const *str, int ch);
注意它们的第2个参数是一个整型值。但是,它包含了一个字符值。strchr在字符串str中查找字符ch第1次出现的位置,找到后函数返回一个指向该位置的指针。如果该字符串中不存在该字符,函数就返回一个NULL指针。strrchr的功能和strchr基本一致,只是它返回的是一个指向该字符串该字符最后一次出现的位置。
2.查找任何几个字符
strpbrk是个更为常见的函数。它并不是查找某个特定的字符,而是查找任何一组字符第一次在字符串中出现的位置。它的原型如下:
char *strpbrk(char const *str, char const *group);
这个函数返回一个指向str中第一个匹配group中任何一个字符的字符位置。如果未找到匹配,函数返回一个NULL指针。
如下代码:
char string[20] = "Hello there, honey.";char *ans;ans = strpbrk(string, "aeiou");
ans指向的位置是string+1,因为这个位置是第二个参数中的字符第一次出现的位置。和前面一样,这个函数也是区分大小写的。
3.查找一个子串
为了在字符串中查找一个子串,可以使用strstr函数,它的原型如下:
char *strstr(char const *s1, char const *s2);
这个函数s1中查找整个s2第一次出现的起始位置,并返回一个指向该位置的指针。如果s2并没有完整地出现在s1的任何地方,函数将返回一个NULL指针。如果第二个参数是一个空字符串,函数就返回s1。
标准库中并不存在strrstr和strrpbrk。不过,如果你需要它们,它们是很容易实现的。