ANSI C中有20多个用于处理字符串的函数:
注意:const 形参使用了const限定符,表示该函数不会改变传入的字符串。因为源字符串是不能更改的。
strlen函数:
函数原型:unsigned int strlen(const char*)
用于统计字符串的长度。举例如下
1 void fit(char *,unsigned int); 2 3 int main(void) 4 { 5 char mesg [] = "Things should be as simple as possible,""but not simpler."; 6 7 puts(mesg); 8 fit(mesg, 38); 9 puts(mesg); 10 puts("Let's look at some more of the string."); 11 puts(mesg + 39); //我们在38位置(空字符\0)后的39位置开始打印缓冲区余下的字符串。 12 13 return 0; 14 } 15 16 void fit(char *string, unsigned int size) //利用strlen函数,设计一个函数可以缩短字符串的长度。 17 { 18 if(strlen(string)>size) 19 string[size] = '\0'; 20 }
strcat()函数:
函数原型:char *strcat(char *strDest, const char *strSrc)
接受两个字符串作为参数,把第2个字符串的备份附加在第1个字符串的末尾。并把拼接后形成的新字符串作为第1个字符串。第2个字符串不变。
1 #include <stdio.h> 2 #include <string.h> 3 #define SIZE 80 4 5 char * s_gets(char * st, int n); 6 7 int main() 8 { 9 char flower[SIZE]; 10 char addon [] = "s smell like old shoes."; 11 12 puts("What is your favorite flower?"); 13 if(s_gets(flower,SIZE)) 14 { 15 strcat(flower,addon); //使用了strcat函数进行字符串拼接 16 puts(flower); 17 puts(addon); 18 } 19 else 20 puts("End of file encountered!"); 21 puts("bye"); 22 23 return 0; 24 } 25 26 char * s_gets(char * st, int n) 27 { 28 char * ret_val; 29 int i=0; 30 31 ret_val = fgets(st, n, stdin); //读取成功,返回一个指针,指向输入字符串的首字符; 32 if(ret_val) 33 { 34 while(st[i]!='\n' && st[i]!='\0') 35 i++; 36 if(st[i] =='\n') //fgets会把换行符也吃进来了,fgets会在末尾自动加上\0; 37 st[i]='\0'; 38 else 39 while(getchar() != '\n') 40 continue; 41 } 42 return ret_val; 43 }
strncat()函数:
strcat()函数有个缺点就是无法检查第1个数组是否能容纳第2个字符串。如果给第1个数组的空间不够大,多出来的字符溢出到相邻存储单元时就会出问题。
函数原型:extern char *strncat(char *dest,char *src,int n);
参数说明:第3个参数指定了最大添加字符数;
这段代码会拼接两个字符,检查第1个数组的大小,重点是确定最大能添加多少字符数;
1 #include<stdio.h> 2 #include<string.h> 3 4 #define SIZE 30 5 #define BUGSIZE 13 6 7 char * s_gets(char * st,int n); 8 9 int main(void) 10 { 11 char flower[SIZE]; 12 char addon [] = "s smell like old shoes."; 13 char bug[BUGSIZE]; 14 int available; 15 16 puts("What is your favorite flower?"); 17 s_gets(flower,SIZE); 18 if((strlen(addon)+strlen(flower)+1)<=SIZE) //important 19 strcat(flower,addon); 20 puts(flower); 21 puts("What is your favorite bug?"); 22 s_gets(bug,BUGSIZE); 23 available= BUGSIZE -strlen(bug)-1; 24 strncat(bug,addon,available); 25 puts(bug); 26 return 0; 27 } 28 29 30 31 char * s_gets(char * st, int n) 32 { 33 char * ret_val; 34 int i=0; 35 36 ret_val = fgets(st, n, stdin); //读取成功,返回一个指针,指向输入字符串的首字符; 37 if(ret_val) 38 { 39 while(st[i]!='\n' && st[i]!='\0') 40 i++; 41 if(st[i] =='\n') //fgets会把换行符也吃进来了,fgets会在末尾自动加上\0; 42 st[i]='\0'; 43 else //其实是'\0' 44 while(getchar() != '\n') //会把缓冲区后续的字符都清空 45 continue; 46 } 47 return ret_val; 48 }
我们发现strcat()也会造成缓冲区溢出。但是它没有像gets()一样被废除。gets()的安全隐患来自使用程序的人。而strcat()的安全隐患来自粗心的程序员。我们无法控制用户会进行什么操作,但是可以控制程序做什么。因此C语言相信程序员。程序员也有责任保证strcat()使用的安全。
缓冲区溢出漏洞:程序员必须保证strcat的第一个参数有足够的空间。编译器无法报错是因为,这个函数的参数是指针类型,函数中也只是通过指针来读写这些内存的。函数根本不知道第一个参数所指的内存空间到底够不够大,函数本身不会对此进行检查。函数的大致行为是找到第一个参数所指的内存中字符串结尾的位置,然后从此处开始写入第二个参数的字符,直到写完。如果向第一个参数写入过多的字符,有可能会引起问题,也有可能不会。这取决于内存空间后面的内存是否可用来读写。万一覆盖了内存空间重要数据,就会引起错误。所以这是严重的安全隐患。
strcmp()函数:
函数原型:int strcmp(const char *str1,const char *str2);
strcmp()函数返回的具体值不重要,我们只在意该值是0还是非0;比较两个字符串是否相等;我们关注的是字符串是否相等;
如果真要关心返回值的话,要理解比较的机制:其实是ASCII码值的比较;大写的字母ASCII值比小写的字母小;
比较的是:第一个字符串,相对第二个字符串的大小
str1<str2 返回负值;(ASCII的差值)
str1=str2 等于0;
str1>str2 返回正值;(ASCII的差值)
1 //检查程序是否要停止读取输入 2 3 #include <stdio.h> 4 #include <string.h> 5 6 #define SIZE 80 7 #define LIM 10 8 #define STOP "quit" 9 10 char * s_gets(char *st, int n); 11 12 int main(void) 13 { 14 char input[LIM][SIZE]; 15 int ct =0; 16 17 printf("Enter up to %d lines(type quit to quit):\n",LIM); 18 while(ct<LIM && s_gets(input[ct],SIZE)!=NULL && strcmp(input[ct],STOP)!=0) 19 ct++; 20 printf("%d strings entered\n",ct); 21 22 return 0; 23 } 24 25 26 char * s_gets(char * st, int n) 27 { 28 char * ret_val; 29 int i=0; 30 31 ret_val = fgets(st, n, stdin); //读取成功,返回一个指针,指向输入字符串的首字符; 32 if(ret_val) 33 { 34 while(st[i]!='\n' && st[i]!='\0') 35 i++; 36 if(st[i] =='\n') //fgets会把换行符也吃进来了,fgets会在末尾自动加上\0; 37 st[i]='\0'; 38 else //其实是'\0' 39 while(getchar() != '\n') //会把缓冲区后续的字符都清空 40 continue; 41 } 42 return ret_val; 43 }
strncmp()函数:
函数原型:extern int strcmp(char *str1,char * str2,int n)
参数说明:str1为第一个要比较的字符串,str2为第二个要比较的字符串,n为指定的str1与str2的比较的字符数。
函数功能:比较字符串str1和str2的前n个字符。
返回说明:返回整数值:当str1<str2时,返回值<0; 当str1=str2时,返回值=0; 当str1>str2时,返回值>0。
1 //比较前五个字符是否相等 2 #include <stdio.h> 3 #include <string.h> 4 5 #define LISTSIZE 6 6 7 int main() 8 { 9 const char *list[LISTSIZE]= 10 { 11 "astronomy","astounding", 12 "astrophysics","ostracize", 13 "asterism","astrophobia" 14 }; 15 int count =0; 16 int i; 17 18 for(i=0;i<LISTSIZE;i++) 19 { 20 if(strncmp(list[i],"astro",5)==0) 21 printf("Found:%s\n",list[i]); 22 count++; 23 } 24 printf("The list contained %d words beginning" " with astro.\n",count); 25 26 return 0; 27 }
strcpy()函数:
函数原型:char *strcpy(char *strDest, const char *strSrc)
这个函数相当于字符串的赋值运算,拷贝字符串;
1 #include <stdio.h> 2 #include <string.h> 3 #define SIZE 40 4 #define LIM 5 5 char * s_gets(char * st, int n); 6 7 int main(void) 8 { 9 char qwords[LIM][SIZE]; 10 char temp[SIZE]; 11 int i = 0; 12 13 printf("Enter %d words beginning with q:\n",LIM); 14 while(i<LIM && s_gets(temp,SIZE)) 15 { 16 if(temp[0]!='q') 17 printf("%s doesn't begin with q!\n",temp); 18 else 19 { 20 strcpy(qwords[i],temp); 21 i++; 22 } 23 } 24 puts("Here are the words accepted:"); 25 for(i=0;i<LIM;i++) 26 { 27 puts(qwords[i]); 28 } 29 return 0; 30 }
声明数组将分配储存数据的空间,声明指针只分配储存一个地址的空间;
使用strcpy函数的时候,程序员有责任保证目标字符串有足够的空间储存源字符串的副本;
strcpy的一些属性:其返回类型是char *,第一,该函数返回的是第1个参数的值,即一个字符的地址;第二,第1个参数不必指向数组的开始。这个属性可用于拷贝数组的一部分。代码如下:
1 #include <stdio.h> 2 #include <string.h> 3 #define WORDS "beast" 4 #define SIZE 40 5 6 int main(void) 7 { 8 const char * orig = WORDS; 9 char copy[SIZE]= "Be the best that you can be."; 10 char *ps; 11 12 puts(orig); 13 puts(copy); 14 ps = strcpy(copy+7,orig); 15 puts(copy); 16 puts(ps); 17 18 return 0; 19 }
注意:源字符串也会把空字符也拷贝在内。
strncpy()函数:更谨慎的选择
函数原型:char *strncpy(char *dest, char *src, int n);
strcpy()和strcat()都有同样的问题,就是不能检查目标空间能否容纳源字符串的副本。拷贝字符串用strncpy()更安全。第3个参数指明可拷贝的最大字符数。
1 //还是输入五个q开头的单词,但是对单词输入的长度有限制 2 #include <stdio.h> 3 #include <string.h> 4 #define SIZE 40 5 #define TARGSIZE 7 6 #define LIM 5 7 8 char * s_gets(char * st, int n); 9 10 int main(void) 11 { 12 char qwords[LIM][TARGSIZE]; 13 char temp[SIZE]; 14 int i=0; 15 16 printf("Enter %d words beginning with q:\n",LIM); 17 while(i<LIM && s_gets(temp,SIZE)) 18 { 19 if(temp[0]!='q') 20 printf("%s doesn't begin with q!\n",temp); 21 else 22 { 23 strncpy(qwords[i],temp,TARGSIZE-1); 24 qwords[i][TARGSIZE-1] ='\0'; 25 i++; 26 } 27 } 28 puts("Here are the words accepted:"); 29 for (i=0;i<LIM;i++) 30 { 31 puts(qwords[i]); //puts末尾自动加上换行符 32 } 33 34 return 0; 35 } 36 37 38 char * s_gets(char * st, int n) 39 { 40 char * ret_val; 41 int i=0; 42 43 ret_val = fgets(st, n, stdin); //读取成功,返回一个指针,指向输入字符串的首字符; 44 if(ret_val) 45 { 46 while(st[i]!='\n' && st[i]!='\0') 47 i++; 48 if(st[i] =='\n') //fgets会把换行符也吃进来了,fgets会在末尾自动加上\0; 49 st[i]='\0'; 50 else //其实是'\0' 51 while(getchar() != '\n') //会把缓冲区后续的字符都清空 52 continue; 53 } 54 return ret_val; 55 }
strncpy(target,source,n)把source中的n个字符或空字符之前的字符(先满足哪个条件就执行哪个)拷贝到target中。
如果source中的字符数小于n,则拷贝整个字符串,包括空字符。
但是有一种情况就是万一没有把末尾的空字符拷贝进去的话,就不是存的字符串了。所以一般要把n设置成比目标数组的大小少1,然后把数组最后一个元素设置为空字符。
代码如下:
1 strncpy(qwords[i],temp,TARGSIZE-1); 2 qwords[i][TARGSIZE-1] ='\0';
这样做确保是一个字符串。
sprintf()函数:
函数原型:int sprintf( char *buffer, const char *format [, argument] … );
注意:它函数声明在stdio.h中,而不是在string.h中;该函数和printf()有点像。但是它是把数据写入字符串中,而不是打印在显示器上。
第一个参数是目标字符串的地址。其余参数和printf()相同,即格式字符串和待写入项的列表。
sprintf 是个变参函数,使用sprintf 对于写入buffer的字符数是没有限制的,这就存在了buffer溢出的可能性。
1 //格式化字符串 2 #include <stdio.h> 3 #define MAX 20 4 char * s_gets(char * st, int n); 5 6 int main(void) 7 { 8 char first[MAX]; 9 char last[MAX]; 10 char formal[2*MAX+10]; 11 double prize; 12 13 puts("Enter your first name:"); 14 s_gets(first,MAX); 15 puts("Enter your last name:"); 16 s_gets(last,MAX); 17 puts("Enter your prize money:"); 18 scanf("%lf",&prize); 19 sprintf(formal,"%s, %-19s: $%6.2f\n",last,first,prize); 20 puts(formal); 21 22 return 0; 23 } 24 25 26 27 28 29 char * s_gets(char * st, int n) 30 { 31 char * ret_val; 32 int i=0; 33 34 ret_val = fgets(st, n, stdin); //读取成功,返回一个指针,指向输入字符串的首字符; 35 if(ret_val) 36 { 37 while(st[i]!='\n' && st[i]!='\0') 38 i++; 39 if(st[i] =='\n') //fgets会把换行符也吃进来了,fgets会在末尾自动加上\0; 40 st[i]='\0'; 41 else //其实是'\0' 42 while(getchar() != '\n') //会把缓冲区后续的字符都清空 43 continue; 44 } 45 return ret_val; 46 }
strchr函数:
函数原型:char *strchr(const char *s, int c)
如果s字符串中包含c字符,该函数返回指向s字符串首位置(指向字符)的指针;如果在字符串中未找到c字符,该函数则返回空指针;
strrchr函数:
函数原型:char *strrchr(const char * s,int c)
该函数返回s字符串中c字符的最后一次出现的位置(末尾的空字符也是字符串的一部分,所以在查找范围内)。如果未找到c字符,则返回空指针;
strstr函数:
函数原型:char *strstr(const char * s1, const char * s2);
返回值是指向s1字符串中s2字符串出现的首位置。如果在s1中没有找到s2,则返回空指针。
strpbrk函数:
函数原型:char *strpbrk(const char * s1, const char * s2);
如果s1字符中包含s2字符串中的任意字符,该函数返回指向s1字符串首位置的指针;如果在s1字符串中未找到任何s2字符串中的字符,则返回空指针。
size_t strlen(const char * s)
该函数返回s字符串中的字符数,不包括末尾的空字符
size_t类型是sizeof运算符返回的类型。
C规定sizeof运算符返回一个整数类型,但是并未指定是哪种整数类型。
所以size_t在一个系统中可以是unsigned int,而在另一个系统中可以是unsigned long。
string.h头文件针对特定系统定义了size_t。这样就可以跨平台了,屏蔽平台之间的差异性。
//fgets()读入一行输入时,在目标字符串的末尾添加换行符
//处理末尾换行符的方法之一是循环检测换行符,然后替换成\0
1 char * s_gets(char * st, int n) 2 { 3 char * ret_val; 4 int i=0; 5 6 ret_val = fgets(st, n, stdin); //读取成功,返回一个指针,指向输入字符串的首字符; 7 if(ret_val) 8 { 9 while(st[i]!='\n' && st[i]!='\0') 10 i++; 11 if(st[i] =='\n') //fgets会把换行符也吃进来了,fgets会在末尾自动加上\0; 12 st[i]='\0'; 13 else //其实是'\0' 14 while(getchar() != '\n') //会把缓冲区后续的字符都清空 15 continue; 16 } 17 return ret_val; 18 }
有其他办法,使用strchr()代替s_gets():
1 #include <stdio.h> 2 3 char line[80]; 4 char * find; 5 6 fgets(line, 80, stdin); 7 find = strchr(line, '\n'); 8 if(find) 9 * find ='\0';
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
注意点:
1 while(* string)
2 while(*string != '\0')
许多程序员会在while循环条件中使用第一种的测试条件;string指向空字符时,*string的值是0,即测试条件为假,while循环结束。作为C语言程序员应该熟悉方法,第二种的测试条件没有第一种简洁。
const char * string和const char string[]的区别:
从技术方面讲,两者等效且都有效;
使用方括号是为了提醒用户,实际处理的参数是数组;
如果要处理字符串,使用指针形式的话,实际参数可以是数组名,双引号括起来的字符串或声明为char *类型的变量。这种写法是为了提醒用户,实际参数不一定是数组;
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
演示1:嵌套函数的调用
1 #include <stdio.h> 2 3 void put1(const char *); 4 int put2(const char *); 5 6 int main(void) 7 { 8 put1("If I'd as much money"); 9 put1(" as I could spend,\n"); 10 printf("I count %d characters.\n", 11 put2("I never would cry old chairs to mend.")); 12 13 return 0; 14 } 15 16 void put1(const char * string) 17 { 18 while(*string) 19 putchar(* string++); 20 21 } 22 23 int put2(const char * string) 24 { 25 int count = 0; 26 while(*string) 27 { 28 putchar(*string++); 29 count++; 30 } 31 putchar('\n'); 32 33 return(count); 34 }
分析:使用printf()打印put2的值,但是为了获得put2的返回值,计算机必须先执行put2(),因此执行put2()的过程中,打印了put2中的字符串。