目录
字符串基础
字符串就是一串零个或多个字符,并且以位模式为全0的NUL字节即空字符(‘\0’)结尾。C语言中字符串没有显示的数据类型,字符串通常存储在字符数组或动态分配的内存中。(#define NUL '\0')
- NUL字节是字符串的终止符,但它并不是字符串的一部分,所以字符串的长度并不包括NUL字节。
- 操作字符串时,必须保证字符串以空字符结尾(注:不以空字符结尾的字符序列,不是字符串)。
(为什么说NUL字节?那是因为每个字符占一个字节,所以NUL作为字符串的终止符,也应该占一个字节,所以叫做NUL字节)
字符串长度
字符串的长度就是它包含的字符个数。上面也说了,字符串的长度不包括NUL字节。
计算字符串长度的库函数strlen的原型如下:
size_t strlen( char const *string );
注意这个库函数的返回值类型是size_t,也许很多人和我一样会对此感到迷惑,于是我在知乎上看了下解释,如下是一个我比较能接受的答案:
size_t 这个类型的意义是什么?
size_t这个类型是在头文件stddef.h中定义的,它是一个无符号整数类型。
不受限制的字符串函数
最常用的字符串函数都是“不受限制的”,就是说它们只是通过寻找字符串参数结尾的NUL字节来判断它的长度。这些函数一般都指定一块内存用于存放结果字符串。在使用这些函数时,程序员必须保证结果字符串不会溢出这块内存。
C 中有大量操作字符串的函数:
序号 | 函数 & 目的 |
---|---|
1 | strcpy(s1, s2); 复制字符串 s2 到字符串 s1。 |
2 | strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。 |
3 | strlen(s1); 返回字符串 s1 的长度。 |
4 | strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。 |
5 | strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
6 | strstr(s1, s2); |
复制字符串
用于复制字符串的函数为 strcpy,它的原型如下:
char *strcpy( char *dst, char const *src );
这个函数把参数src字符串复制到dst参数。
关于这里面的const是什么意思,可见博文的最后部分:【C】const 学习笔记
如果参数src和dst在内存中出现重叠,其结果是未定义的。下面我们对此进行分析:
由于dst参数将进行修改,所以它必须是个字符数组或者是一个指向动态分配内存的数组的指针,不能使用字符串常量。考虑下面这个例子:
char message[] = "Original message";
...
if(...)
strcpy(message, "Different" );
第一个字符数组message[]在内存中存放示意图如下:
运行完这个函数: strcpy(message, "Different" );
内存存放情况如下:
第一个NUL字节后面的几个字符再也无法被字符串函数访问,因此从任何现实的角度看,它们都已经是丢失的了。
警告:
程序员必须保证目标字符数组的空间足以容纳需要复制的字符串。如果字符串比数组长,多余的字符仍被复制,它们将覆盖原先存储于数组后面的内存空间的值。strcpy 无法解决这个问题,因为它无法判断目标字符数组的长度。
例如:
char message[] = "Original message";
'''
strcpy( message, "A different message" );
第2个字符串太长了,无法容纳于message字符数组中。因此,strcpy 函数将侵占数组后面的部分内存空间,改写原来恰好存储在那里的变量。如果你在使用这个函数前确保目标参数足以容纳源字符串,就可以避免大量的调试工作。
连接字符串
要想把一个字符串添加到(连接)到另一个字符串的后面,你可以使用strcat 函数。它的原型是:
char *strcat( char *dst, char const *src );
strcat 函数要求dst 参数原先已经包含了一个字符串(可以是空字符串)。它找到这个字符串的结尾,并把源字符串的一份拷贝添加到这个位置。
下面的这个例子显示了这个函数的一种常用用法:
strcpy( message, "Hello ");
strcat( message, customer_name );
strcat( message, ", how are you ?);
需要说明的是customer_name[]这个字符数组的声明可能是这个样子的:
char customer_name[] = "Jim";
这样的话,上面运行的结果就是:
Hello Jim,how are you ?
警告:和前面一样,程序员必须保证目标字符数组剩余的空间足以保存整个源字符串。但这次并不是简单地把源字符串的长度和目标字符串的长度进行比较,你必须考虑目标数组中原先存在的字符串。
函数的返回值
上面说了两个处理字符串的函数,但是都没有提到它们的返回值,从它们的原型中可以看出,它们的返回值是一个指向char类型的指针,
但具体是什么,我们这里给出答案。
strcpy 和 strcat都返回它们的第一个参数的一份拷贝,就是一个指向目标字符数组的指针。
由于它们返回这种类型的值,所以你可以嵌套地调用这些函数,如下面的例子:
strcat( strcpy( dst, a ), a );
strcpy首先执行。它把字符串从a复制到dst并返回dst。然后这个返回值称为strcat函数的第一个参数,strcat函数把b添加到dst后面。
这种嵌套调用的风格较之下面这种可读性更佳的风格在功能上并无优势。
strcpy(dst, a);
strcat(dst, b);
事实上,在这些函数的绝大多数调用中,它们的返回值知识被简单的忽略。
字符串比较
库函数strcmp用于比较两个字符串,它的原型如下:
int strcmp( char const *s1, char const *s2 );
如果s1小于s2,strcmp返回一个小于零的值。如果s1大于s2,函数返回一个大于零的值。如果两个字符串相等,函数就返回零。
警告:
if( strcmp(a,b) )
如果a和b这两个字符串相等,则结果为假,否则为真,这点尤其重要。
但是把这个返回值当成一个布尔值进行测试是一种坏风格,因为它具有三个截然不同的结果,大于,等于,小于。所有更好的办法是把返回值和零进行比较。
注意,标准中并没有规定用于提示不相等的具体值。
由于strcmp并不修改它的任何一个参数,所以不存在溢出字符数组的危险,但是,和其他不受限制的字符串函数一样,strcmp函数的字符串也必须以一个NUL字节结尾。如果并非如此,strcmp就可能对参数后面的字节进行比较,这个比较结果将不会有任何意义。
长度受限的字符串函数
标准库中还包含了一些函数,它们一种不同的方式处理字符串。这些函数接受一个显式的长度参数,用于限定进行复制和比较的字符数。这些函数提供了一些方便的机制,可以防止难以预料的长字符串从它们的目标数组溢出。
这些函数的原型如下:
char *strncpy( char *dst, char const *src, size_t len );
char *strcat( char *dst, char const *src, size_t len );
int strncmp( char const *s1, char const *s2, size_t len );
和它们的不受限制的版本一样,如果源参数与目标参数发生重叠,strncpy 和 strcat的结果就是未定义的。
strncpy
和 strcpy 一样,strncpy把源字符串的字符复制到目标数组。然而,它总是正好向dst写入len个字符。 如果 strlen(src)的值小于len,dst 数组就用额外的NUL字节来填充len长度。如果strlen(src)的值大于或等于 len, 那么只有len个字符被复制到dst 中。
注意:它的结果将不会以NUL结尾。
从上面的介绍也能看出,strncpy调用的结果可能不是一个字符串,因为字符串必须以NUL字节结尾。如果在一个需要字符串的地方(例如strlen函数的参数)使用了一个不是以NUL字节结尾的字符序列,会发生什么呢?
strlen函数将无法知道NUL字节是没有的,所以它将继续进行查找,一个字符接着一个字符,直到它发现一个NUL字符为止。或许它找了几百个字符才找到,而strlen函数的这个返回值从本质上说是一个随机数。或者,如果函数试图访问系统分配给这个程序以外的内存范围,程序就会崩溃!
这个问题只有当你使用strncpy函数创建字符串,然后或者对它们使用str开头的库函数,或者在printf中使用%s格式码打印它们时才会发生。在使用不受限制的函数之前,你首先必须确定字符串实际上是以NUL字节结尾的。
例如,考虑下面这个代码:
char buffer[BSIZE];
...
strncpy( buffer, name, BSIZE );
buffer[BSIZE - 1] = '\0';
如果name的内容可以容纳于buffer中,最后那个赋值语句是没有任何作用的。但是,如果name太长,这条赋值语句可以保证buffer中的字符串是以NUL结尾的。以后对这个数组使用strlen或其他不受限制的字符串函数将能够正确工作。
strncat
尽管strncat也是一个长度受限的函数,但它和strncpy存在不同之处。它从src中最多赋值len个字符到目标数组的后面。但是,strncat总是在结果字符串后面添加一个NUL字节,而且它不会像strncpy那样对目标数组用NUL字节进行填充。注意目标数组中原先的字符串并没有算在strncat的长度中。strncat最多向目标数组赋值len个字符,再加一个结尾的NUL字节,它才不会管目标参数出去原先存在的字符串之后留下的空间够不够。(不够我就在规定内存的后面继续操作!这才叫霸气!)
strncmp
最后,strncmp也用于比较两个字符串,但它最多比较len个字节。如果两个字符串在第len个字符之前存在不相等的字符,这个函数就像strcmp一样停止比较,返回结果。如果两个字符串的前len个字符相等,函数就返回零。