《C和指针》笔记34:字符串函数

1. 获取字符串长度

strlen

库函数strlen的原型如下:

size_t strlen( char const *string );

注意strlen返回一个类型为size_t的值。这个类型是在头文件stddef.h中定义的,它是一个无符号整数类型。在表达式中使用无符号数可能导致不可预料的结果。例如,下面两个表达式看上去是一样的:

if( strlen( x ) >= strlen( y ) ) ...
if( strlen( x )strlen( y ) >= 0 ) ...

但事实上它们是不相等的。第1条语句将按照你预想的那样工作,但第2条语句的结果将永远是真。strlen的结果是个无符号数,所以操作符>=左边的表达式也将是无符号数,而无符号数绝不可能是负的

表达式中如果同时包含了有符号数和无符号数,可能会产生奇怪的结果。和前一对语句一样,下面两条语句并不相等,其原因相同。

if( strlen( x ) >= 10 ) ...
if( strlen( x )10 >= 0 ) ...

如果把strlen的返回值强制转换为int,就可以消除这个问题。

2. 复制字符串

strcpy

用于复制字符串的函数是strcpy,它的原型如下所示:

char *strcpy( char *dst, char const *src );

这个函数把参数src字符串复制到dst参数。如果参数src和dst在内存中出现重叠,其结果是未定义的。由于dst参数将进行修改,所以它必须是个字符数组或者是一个指向动态分配内存的数组的指针,不能使用字符串常量。该函数返回它们第1个参数的一份拷贝,就是一个指向目标字符数组的指针。

目标参数的以前内容将被覆盖并丢失。即使新的字符串比dst原先的内存更短,由于新字符串是以NUL字节结尾,所以老字符串最后剩余的几个字符也会被有效地删除。举个例子来说:

char message[] = "Original message";
...
if (...)
	strcpy(message, "Different");

如果条件为真并且复制顺利执行,数组将包含下面的内容:

在这里插入图片描述

第1个NUL字节后面的几个字符再也无法被字符串函数访问,因此从任何现实的角度看,它们都已经是丢失的了。

程序员必须保证目标字符数组的空间足以容纳需要复制的字符串。如果字符串比数组长,多余的字符仍被复制,它们将覆盖原先存储于数组后面的内存空间的值。strcpy无法解决这个问题,因为它无法判断目标字符数组的长度。例如:

```c
char message[] = "Original message";
...
if (...)
	strcpy(message, "A different message");

第2个字符串太长了,无法容纳于message字符数组中。因此,strcpy函数将侵占数组后面的部分内存空间,改写原先恰好存储在那里的变量。这可能发生意想不到的效果。如果在使用这个函数前确保目标参数足以容纳源字符串,就可以避免大量的调试工作。

strncpy

strcpy一样,strncpy把源字符串的字符复制到目标数组。然而,它总是正好向dst写入len个字符。它的原型如下:

char *strncpy(char *dst, char const *src, size_t 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或其
他不受限制的字符串函数将能够正确工作。

3. 拼接字符串

strcat

要想把一个字符串添加(连接)到另一个字符串的后面,你可以使用strcat函数。它的原型如下:

char *strcat( char *dst, char const *src );

strcat函数要求dst参数原先已经包含了一个字符串(可以是空字符串)。它找到这个字符串的末尾,并把src字符串的一份拷贝添加到这个位置。如果src和dst的位置发生重叠,其结果是未定义的。该函数返回它们第1个参数的一份拷贝,就是一个指向目标字符数组的指针。

strcpy(message, "Hello ");
strcat(message, customer_name);
strcat(message, ", how are you?");

每个strcat函数的字符串参数都被添加到原先存在于message数组的字符串后面。其结果是下面这个字符串:

Hello Jim, how are you?

和前面一样,程序员必须保证目标字符数组剩余的空间足以保存整个源字符串。但这次并不是简单地把源字符串的长度和目标字符数组的长度进行比较,必须考虑目标数组中原先存在的字符串。

我们也可以这样嵌套,因为返回值是第一个参数的指针:

strcat( strcpy( dst, a ), b );

strcpy首先执行。它把字符串从a复制到dst并返回dst。然后这个返回值成为strcat函数的第1个参数,strcat函数把b添加到dst的后面。

它等价于:

strcpy( dst, a );
strcat( dst, b );

strncat

尽管strncat也是一个长度受限的函数,但它和strncpy存在不同之外。它从src中最多复制len个字符到目标数组的后面。它的原型如下:

char *strcat( char *dst, char const *src );

但是,strncat总是在结果字符串后面添加一个NUL字节,而且它不会像strncpy那样对目标数组用NUL字节进行填充。注意目标数组中原先的字符串并没有算在
strncat的长度中。strncat最多向目标数组复制len个字符(再加一个结尾的NUL字节),它才不管目标参数除去原先存在的字符串之后留下的空间够不够。

4. 字符串比较

strcmp

比较两个字符串涉及对两个字符串对应的字符逐个进行比较,直到发现不匹配为止。那个最先不匹配的字符中较“小”(也就是说,在字符集中的序数较小)的那个字符所在的字符串被认为“小于”另外一个字符串。如果其中一个字符串是另外一个字符串的前面一部分,那么它也被认为“小于”另外一个字符串,因为它的NUL结尾字节出现得更早。这种比较被称为“词典比较”,对于只包含大写字母或只包含小写字母的字符串比较,这种比较过程所给出的结果总是和我们日常所用的字母顺序的比较相同。

库函数strcmp用于比较两个字符串,它的原型如下:

int strcmp( char const *s1, char const *s2 );

如果s1小于s2,strcmp函数返回一个小于零的值。如果s1大于s2,函数返回一个大于零的值。如果两个字符串相等,函数就返回零。注意标准并没有规定用于提示不相等的具体值。它只是说如果第1个字符串大于第2个字符串就返回一个大于零的值,如果第1个字符串小于第2个字符串就返回一个小于零的值。一个常见的错误是以为返回值是1和-1,分别代表大于和小于。但这个假设并不总是正确的。

对于这个函数常出现的错误是:

if( strcmp( a, b ) )

他以为如果两个字符串相等,它的结果将是真。但是,这个结果将正好相反,因为在两个字符串相等的情况下返回值是零(假)。然而,把这个返回值当作布尔值进行测试是一种坏风格,因为它具有三个截然不同的结果:小于、等于和大于。所以,更好的方法是把这个返回值与零进行比较。

由于strcmp并不修改它的任何一个参数,所以不存在溢出字符数组的危险。但是,和其他不受限制的字符串函数一样,strcmp函数的字符串参数也必须以一个NUL字节结尾。如果并非如此,strcmp就可能对参数后面的字节进行比较,这个比较结果将不会有什么意义。

strncmp

strncmp也用于比较两个字符串,但它最多比较len个字节。它的原型为:

int strncmp( char const *s1,char const *s2,size_t len );

如果两个字符串在第len个字符之前存在不相等的字符,这个函数就像strcmp一样停止比较,返回结果。如果两个字符串的前len个字符相等,函数就返回零。

5. 查找字符

strchr和strrchr:查找一个字符

在一个字符串中查找一个特定字符最容易的方法是使用strchrstrrchr函数,它们的原型如下所示:

char *strchr(char const *str, int ch );
char *strrchr(char const *str, int ch );

注意它们的第2个参数是一个整型值。但是,它包含了一个字符值。strchr在字符串str中查找字符ch第1次出现的位置,找到后函数返回一个指向该位置的指针。如果该字符并不存在于字符串中,函数就返回一个NULL指针。strrchr的功能和strchr基本一致,只是它所返回的是一个指字符串中该字符最后一次出现的位置(最右边那个)。

char string[20] = "Hello there,honey.";
char *ans;
ans = strchr(string, 'h');

ans所指向的位置将是string+7,因为第1个’h’出现在这个位置。注意这里大小写是有区别的。

strpbrk:查找任何几个字符

strpbrk是个更为常见的函数。它并不是查找某个特定的字符,而是查找任何一组字符第1次在字符串中出现的位置。它的原型如下:

char *strpbrk( char const *str, char const *group );

这个函数返回一个指向str中第1个匹配group中任何一个字符的字符位置。如果未找到匹配,函数返回一个NULL指针。

char string[20] = "Hello there,honey.";
char *ans;
ans = strpbrk(string, "aeiou");

ans所指向的位置是string+1,因为这个位置是第2个参数中的字符第1次出现的位置。和前面一样,这个函数也是区分大小写的。

strstr:查找一个子串

为了在字符串中查找一个子串,我们可以使用strstr函数,它的原型如下:

char *strstr( char const *s1, char const *s2 );

这个函数在s1中查找整个s2第1次出现的起始位置,并返回一个指向该位置的指针。如果s2并没有完整地出现在s1的任何地方,函数将返回一个NULL指针。如果第2个参数是一个空字符串,函数就返回s1。标准库中并不存在strrstrstrrpbrk函数。不过,它们是很容易实现的。程序9.2显示了一种实现strrstr的方法。这个技巧同样也可以用于实现strrpbrk

程序mstrrstr.c查找子串最右一次出现的位置

/*
** 在字符串s1中查找字符串s2最右出现的位置,并返回一个指向该位置的指针。
*/
#include <string.h>
char*
my_strrstr( char const *s1, char const *s2 )
{
	register char *last;
	register char *current;
	/*
	** 把指针初始化为我们已经找到的前一次匹配位置。
	*/
	last = NULL;
	/*
	**只在第2个字符串不为空时才进行查找,如果S2为空,返回NULL。
	*/
	if( *s2 != '\0' ){
	/*
	** 查找s2在s1中第1次出现的位置。
	*/
		current = strstr( s1, s2 );
	/*
	** 我们每次找到字符串时,让指针指向它的起始位置。然后查找该字符串下一个匹配位置。
	*/
		while( current != NULL ){
			last = current;
			current = strstr( last + 1, s2 );
		}
	}
	/* 返回指向我们找到的最后一次匹配的起始位置的指针。*/
	return last;
}

strspn和strcspn:查找一个字符串前缀

strspnstrcspn函数用于在字符串的起始位置对字符计数。它们的原型如下所示:

size_t strspn( char const *str, char const *group );
size_t strcspn( char cosnt *str, char const *group );

group字符串指定一个或多个字符。strspn返回str起始部分匹配group中任意字符的字符数。例如,如果group包含了空格、制表符等空白字符,那么这个函数将返回str起始部分空白字符的数目。str的下一个字符就是它的第1个非空白字符。

int 1en1,1en2;
char buffer[]="25,142,330,Smith,J,239-4123";
len1 = strspn(buffer,"0123456789");
len2 = strspn(buffer,",0123456789");

当然,buffer缓冲区在正常情况下是不会用这个方法进行初始化的。它将会包含在运行时读取的数据。但是在buffer中有了这个值之后,变量len1将被设置为2,变量len2将被设置为11。下面的代码将计算一个指向字符串中第1个非空白字符的指针。

ptr = buffer + strspn( buffer, "\n\r\f\t\v" );

strcspn函数和strspn函数正好相反,它对str字符串起始部分中不与group中任何字符匹配的字符进行计数。strcspn这个名字中字母c来源于对一组字符求补这个概念,也就是把这些字符换成原先并不存在的字符。如果你使用“ \n\r\f\t\v”作为group参数,这个函数将返回第1个参数字符串起始部分所有非空白字符的值。


参考

《C和指针》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值