C库提供了许多处理字符串的函数:ANSI C 用头文件string.h给出这些函数的原型。下面是一些最有用和最常用的函数:strlen() 、strcat()、strncat() 、strcmp() 、strncmp() 、strcpy()、 strncpy()。此外,我们也将研究一下头文件stdio.h支持的sprintf()函数。
11.5.1 strlen( )函数
我们已经知道,用strlen()函数可以得到字符串的长度。下面的函数中用到了strlen()函数,这是一个可以缩短字符串长度的函数:
/*test_fit.c-*/void fit (char * string,unsigned int size){ if(strlen(string)>size) *(string+size)='\0';}
这个函数确实要改变字符串,因此在函数头中声明形式参量string时没有使用const修饰符。
在程序清单11.13的程序中测试一下fit( )函数。注意,代码中用到C的字符串文本串联功能。
程序清单11.13 test.c程序
/*test.c 试用缩短字符串的函数*/#include#include void fit(char *,unsigned int );int main(void){ char msg[]="Hold on to your hats,hackers. "; puts(msg); fit(msg,7); puts(msg); puts("Let's look at some more of the string. "); puts(msg+8); return 0;}void fit(char *string,unsigned int size){ if(strlen(string)>size) *(string+size)='\0';}
输出如下:
Hold on to your hats,hackers.Hold onLet's look at some more of the string.to your hats,hackers.
fit( )函数在数组的第8个元素中放置了一个‘\0'字符来代替原来的空格字符。puts()函数输出时停在第一个空字符处,忽略数组的其他元素。然而,数组的其他元素还是存在的,如下面的函数调用的输出结果所示:
puts(msg+8);
ANSI C的sting.h头文件中包含了C字符串函数系列的原型,因此这个示例程序要包含这个文件。
11.5.2 strcat( )函数
strcat( )(代表string concatenation)函数接受两个字符串参数。它将第二个字符串的一份拷贝添加到第一个字符串的结尾,从而使第一个字符串成为一个新的组合字符串,第二个字符串并没有改变。
strcat( )函数是char*类型。这个函数返回它的第一个参数的值,即其后添加了第二个字符串的那个字符串中第一个字符的地址。
程序清单11.4 str_cat.c 程序
/*str_cat.c 连接两个字符串*/#include#include #define SIZE 80int main(void){ char flower[SIZE]; char addon[]="s smell like old shoes."; puts("What is your favorite flower?"); gets(flower); strcat(flower,addon); puts(flower); puts(addon); return 0;}输出如下:What is your favorite flower?RoseRoses smell like old shoes.s smell like old shoes.
11.5.3 strcat( )函数
strcat( )函数并不检查第一个数组是否能够容纳第二个字符串。如果没有为第一个数组分配足够大的空间,多出来的字符溢出到相邻单元时就会出问题。您也可以使用strncat( )函数,这个函数需要另一个参数来指明最多 允许添加的字符数目。例如,strncat(bugs,addon,13)函数把addon字符串中的内容添加到bugs上,直到加到13个字符或遇到空字符为止,由二者中先符合的那一个来终止添加过程。因此,把空字符计算在内,bugs数组应该足够大,以存放原始字符串、增加的最多13个字符和结束的空字符。程序清单11.15使用这一知识来计算available变量值,这个值被用作最多允许添加的字符数。
程序清单11.15 join_chk.c程序
/*join_chk.c--连接两个字符串,并检查第一个字符串的大小*/#include#include #define SIZE 30#define BUGSIZE 13int main(void){ char flower[SIZE]; char addon[]="s smell like old shoes."; char bug[BUGSIZE]; int available; puts("what is your favorite flower?"); gets(flower); if((strlen(addon)+strlen(flower)+1)<=SIZE) strcat(flower,addon); puts(flower); puts("what is your favorite bug?"); gets(bug); available=BUGSIZE-strlen(bug)-1; strncat(bug,addon,available); puts(bug); return 0;}
11.5.4 strcmp( )函数
假定您希望把用户的一个输入和一个已有的字符串进行比较,如程序清单11.16所示。
程序清单11.16 nogo.c程序
/*nogo.c--这个程序能满足要求吗?*/#include#define ANSWER "Grant"int main(void){ char try[40]; puts("who is buried in Grant's tomb?"); gets(try); while(try!=ANSWER) { puts("No,that's wrong.Try again. "); gets(try); } puts("That's right!"); return 0;}
ANSWER和try实际上是指针,因此比较式try!=ANSWER并不检查这两个字符串是否一样,而是检查这两个字符串的地址是否一样。由于ANSWER和try被存放在不同的位置,所以这两个地址永远不会一样,用户永远被告知结果是"wrong"。
我们需要一个比较字符串内容,而不是比较字符串地址的函数。您可以自行设计一个,但并不需要这样做。strcmp( )函数就可以实现这个功能。这个函数对字符串的操作,就像关系运算符对数字的操作一样。特别地,如果两个字符串参数相同,它就返回0。改进后的程序清单 11.17.
程序清单11.17 compare.c 程序
/*compare.c--这个程序可以满足要求*/#include#include #define ANSWER "Grant"#define MAX 40int main(void){ char try[MAX]; puts("who is buried in Grant's tomb?"); gets(try); while(strcmp(try,ANSWER)!=0) { puts("No,that's wrong.Try again."); gets(try); } puts("That's right!"); return 0;}
**说明:由于任何非零值都为真,因此,大多数程序员会把while语句简单的写为while(strcmp(try,ANSWER))。
strcmp( )函数的一个优点是它比较的是字符串,而不是数组。尽管数组try占用40个内存单元,而字符串“Grant”只占用6个内存单元(一个用来存放空字符),但是函数在比较时只看try的第一个空字符之前的部分。因此,strcmp()可以用来比较存放在不同大小数组里的字符串。
strcmp( )的返回值
如果字符串不相同,strcmp返回什么值呢?
程序清单11.18 compback.c
/*compback.c strcmp()的返回值*/#include#include int main(void){ printf("strcmp(\"A\",\"A\") is "); printf("%d\n",strcmp("A","A")); printf("strcmp(\"A\",\"B\") is "); printf("%d\n",strcmp("A","B")); printf("strcmp(\"B\",\"A\") is "); printf("%d\n",strcmp("B","A")); printf("strcmp(\"C\",\"A\") is "); printf("%d\n",strcmp("C","A")); printf("strcmp(\"Z\",\"a\") is "); printf("%d\n",strcmp("Z","a")); printf("strcmp(\"apples\",\"apple\") is "); printf("%d\n",strcmp("apples","apple")); return 0;}
这些结果说明如果第一个字符串在字母表中的顺序先于第二个字符串,则strcmp函数返回的是负数;相反,返回的就是正数。ANSI标准规定,如果第一个字符串在字母表中的顺序先于第二个字符串,strcmp返回一个负数;如果两个字符串相同,它返回0;如果第一个字符串在字母表中的顺序落后于第二个字符串,它返回一个正数。而确切的数值是依赖于C实现的。
如果两个字符串中初始的字符相同会怎么样呢?一般来说,strcmp函数一直往后查找,直到找到第一对不一致的字符。然后它就返回相应的值。apples和apple只有最后一个字符不同,匹配进行到apple的第6个字符,即空字符,由于空字符在ASCII中排行第一,字符s在它的后面,因此,函数返回一个正数。
上面的比较说明strcmp( )比较所有的字符,而不仅仅是字母;因此我们不应称比较是按字母顺序,而应该称strcmp()是按机器编码(collating sequence)顺序进行比较的。这意味着字符的比较是根据它们的数字表示法,一般是ASCII值。在ASCII中,大写字母先于小写字母。因此,strcmp("Z","a")是负数。
通常我们不会在意返回的确切值,只想知道结果为0还是非0;或者我们是把字符串按字母表顺序排序,希望知道比较结果是正数、负数还是0。
**说明:strcmp()函数用于比较字符串,而不是字符。因此,可以使用诸如“apples"或"A"之类的参数;但是不能使用字符参数,如'A'。考虑到char类型是整数类型,因此可以使用关系运算符来对字符进行比较。
程序清单11.19 quit_chk.c程序(判断一个程序是否应该停止读取输入)
/*quit_chk.c --某程序的开始*/#include#include #define SIZE 81#define LIM 100#define STOP "quit"int main(void){ char input[LIM][SIZE]; int ct=0; printf("Enter up to %d lines(type quit to quit):\n",LIM); while(ct
当程序遇到一个EOF字符(此时gets()返回空)时,或者您输入单词quit时,或者达到LIM的上限时,程序退出对输入的读取。
顺便提一下,有时候输入一个空行来终止输入更方便,也就是说,在一个新行中不输入任何字符就按下Enter键。要这样做,您可以对while循环的控制语句做如下的修改:
while(ct<LIM && gets(input[ct])!=NUll && input[ct][0]!='\0')
此处,input[ct]是刚输入的字符串,input[ct][0]是该字符串的第一个字符。如果用户输入一个空行,gets( )就把空字符放在第一个元素处,因此如下表达式是用来检测空输入行的:
input[ct][0] != '\0'
11.5.5 strncmp( )变种
strcmp( )函数比较字符串时,一直比较到找到不同的相应字符,搜索可能要进行到字符串结尾处。而strncmp( )函数比较字符串时,可以比较到字符串不同处,也可以比较完全由第三个参数字控制的符数。程序清单11.20示例了这个函数的用法。
程序清单 11.20 starsrch.c 程序
/*starsrch.c --使用strncmp( )函数*/#include#include #define LISTSIZE 5int main(void){ char * list[LISTSIZE]={ "astronomy","astounding", "astrophysics","ostracize", "asterism"}; int count=0; int i; for(i=0;i
11.5.6 strcpy( )和strncpy( )函数
我们已经提到过 ,如果pts1和pts2都是指向字符串的指针,则下面的表达式只复制字符串的地址而不是字符串本身:
pts2=pts1;
假定您确实希望复制字符串,那么可以使用strcpy( )函数。程序清单11.21要求用户输入以q开头的单词。程序把输入一个临时的数组里,如果第一个字母是q,程序就使用strcpy( )函数把字符串从临时数组里复制到永久的目的地。strcpy() 函数在字符串运算中上作用造价于赋值运算符。
程序清单11.21 copy1.c程序
/*copy1.c --strcpy()示例程序*/#include#include #define SIZE 40#define LIM 5int main(void){ char qwords[LIM][SIZE]; char temp[SIZE]; int i=0; printf("Enter %d words beginning with q:\n",LIM); while(i < LIM;i++) puts(qwords[i]); return 0;}
请注意只有当输入的单词通过了q判断,计数值i才会增加。还要注意程序使用了一个基于字符的判断:
if(temp[0] != 'q')
这相当于,temp数组的第一个字符是否不为q?还可以使用一个基于字符串的判断:
if(strncmp(temp[0],"q",1) != 0)
这相当于,字符串temp和字符串“q"的第一个元素是否不同。
注意,第二个参数temp指向的字符串被复制到第一个参数qword[i]指向的数组中。复制的那份字符串被称为目标(target)字符串,最初的字符串被称为源(source)字符串。如果注意到它和赋值语句的顺序一样,目标字符串在左边,就容易记住参数的顺序。
确保目标数组对复制源字符串来说有足够大的空间就是您的责任了。
char *str;strcpy(str,"The C of Tranquility"); /*存在一个问题*/
函数将把字符串“The..."复制到str指定的地址中,但是str没有初始化, 因此这个字符串可能被复制到任何地方!
总之,strcpy( )接受两个字符串指针参数。指向最初字符串的第二个指针可以是一个已声明的指针、数组名或字符串常量。指向复制字符串的第一个指针应指向空间大到足够容纳该字符串的数据对象,比如一个数组。记住,声明一个数组将为数据分配存储空间,而声明一个指针只为一个地址分配存储空间。
一、strcpy( )的高级属性
stycpy( )函数还有另外两个有用的属性。首先,它是char *类型,它返回的是第一个参数的值,即一个字符串的地址;其次,第一个参数不需要指向数组的开始,这样就可以只复制数组的一部分。程序清单11.22举例说明了这两个属性。
程序清单11.22 copy2.c 程序
/*copy2.c --strcpy( )示例程序*/#include#include #define WORD "beast"#define SIZE 40int main(void){ char *orig = WORD; char copy[SIZE] = "Be the best that you can be."; char *ps; puts(orig); puts(copy); ps=strcpy(copy+7,orig); puts(copy); puts(ps); return 0;}输出如下beastBe the best that you can be.Be the beastbeast
注意,strcpy( )从源字符串复制空字符。在这个例子中,空字符覆盖了that中的第一个t,这样新的字符串就以beast结尾。还要注意,ps指向copy的第8个元素(索引为7),这是因为第一个参数是copy+7。因此,puts(ps)从这个地方开始输出字符串。
二、较为谨慎的选择:strncpy( )
strcpy()和gets( )函数同样有一个问题,那就是都不检查目标字符串是否容纳得下源字符串。复制字符串使用strncpy( )比较安全。它需要第三个参数来指明最大可复制的字符数。程序清单11.23用strncpy()代替了程序清单中的strcpy()。为了说明源字符串太大会产生的问题,它使用了一个相当小的目标字符串。
程序清单11.23 copy3.c 程序
/*copy3.c --strncpy() 示例程序*/#include#include #define SIZE 40#define TARGSIZE 7#define LIM 5int main(void){ char qword[LIM][TARGSIZE]; char temp[SIZE]; int i=0; printf("Enter %d words beginning with q: \n",LIM); while(i
函数调用strncpy(target ,source, n)从source把n个字符(或空字符之前的字符,由二者中最先满足的那个决定何时终止)复制到target。因此,如果源字符串的字符数比n小,整个字符串都被复制过来,包括空字符。函数复制的字符数绝不会超过n,因此如果源字符串还没结束就达到了限制,就不会添加空字符。结果,最终的字符串可能有也可能没有空字符。出于这个原因,程序设置的n比目标数组的大小要少1,这样就可以把空字符放到数组的最后一个元素里。
strncpy(qwords[i],temp,TARGSIZE-1);qwords[i][TARGSIZE-1]='\0';
这就确保您已经存储了一个字符串。如果源字符串确实可以容纳得下,和它一起复制的空字符就标志着字符串的真正结束。如果源字符串在目标数组中容纳不下,这个最后的空字符就标志着字符串的结束。
11.5.7 sprintf()函数
sprintf()函数是在stdio.h而不是在string.h中声明的。它的作用和printf()一样,但是它写到字符串里而不是写到输出显示。因此,它提供了把ds几个元素组合成一个字符串的一种途径。sprintf()的第一个参数是目标字符串的地址,其余的参数和printf()一样:一个转换说明字符串,接着是要写的项目列表。
程序清单11.24 format.c
/*format.c 格式化一个字符串*/#include#define MAX 20int main(void){ char first [MAX]; char last [MAX]; char formal[2*MAX+10]; double prize; puts("Enter your first name: "); gets(first); puts("Enter your last name: "); gets(last); puts("Enter your prize money: "); scanf("%lf",&prize); sprintf(formal,"%s,%-19s: $%6.2f\n",last,first,prize); puts(formal); return 0;}下面是一个运行示例Enter your first name:TeddyEnter your last name:BehrEnter your prize money:2000Behr,Teddy : $2000.00
sprintf( )命令获取输入,并把输入格式化为标准形式后存放在字符串formal中。
11.5.8 其他字符串函数
- char *strchr(const char *s, int c )
该函数返回一个指向字符串s中存放字符c的第一个位置的指针(标志结束的空字符也是字符串的一部分,因此也可以搜索到它)。如果没找到该字符,函数就返回空指针。
- char *strpbrk(const char *s1,const char *s2)
该函数返回一个指针,指向字符串s1中存放s2字符串中的任何字符的第一个位置。如果没有找到任何字符,函数就返回空指针。
- char *strrchr(const char *s,int c )
该函数返回一个指针,指向字符串s中字符c最后一次出现的地方(标志结束的空字符也是字符串的一部分,因此也可以搜索到它)。如果没有找到该字符函数就返回空指针。
- char *strstr(const char *s1,const char *s2)
该函数返回一个指针,指向s1字符串中第一次出现s2字符串的地方。如果在s1中没有找到s2字符串,函数就返回空指针。
- size_t strlen(const char *s);
该函数返回s字符串中的字符个数,其中不包括标志结束的空字符。
注意:这些原型使用const指出哪个字符串是函数不能改动的。
第5章“运算符、表达式和语句”中已经讨论过,size_t类型是sizeof运算符返回的任何类型。C规定sizeof运算符返回一个整数类型,但是没有指定是哪种整数类型。因此size_t在一系统上可以是unsigned int类型;在另一个系统上又可以是unsigned long 类型。string.h文件为您的特定系统定义了size_t,或者您可以参考其他有该定义的头文件。
让我们看一下这引起函数其中的一个简单应用。前面已经学习过fgets()函数。在读取一行输入时,这个函数把换行符存储到目标字符串中。可以使用strchr()函数来用一个空字符代替这个换行符。首先,使用strchr()找到换行符(如果有的话)。如果找到了,函数就返回这个换行符的地址,于是就可以在该地址中放一个空字符:
char line[80];char * find;fgets(line,80,stdin);find=strchr(line,'\n');if(find) //如果该地址不为null, *find='\0'; //就把一个空字符放在这里
如果strchr()没有找到换行符,说明fgets()在行未结束时就达到了大小限制。您可以给if加一个else来处理这种情况。
接下来,我们看一下处理字符串的完整程序(11.6)。