(C语言)字符串库函数解析与模拟实现

本文详细介绍了C语言中的几种常用字符串库函数,包括strlen、strcpy、strcat、strstr、strcmp、memcpy和memmove,以及它们的参数、功能和返回值。同时,还提供了模拟实现的代码示例,强调了理解库函数细节的重要性。
摘要由CSDN通过智能技术生成

前言

C语言本身自带许多的库函数,通过包含相应的头文件就可以直接使用。
学习如何使用库函数是学习C语言重要的一环,其中最常用的一类当属字符串库函数。
接下来介绍最常用的几种字符串库函数以及他们的模拟实现。

从三个方面来介绍函数:
1.参数
2.函数功能
3.返回值

#1 strlen

传递一个字符指针。
计算字符串的长度。
返回该长度。

//strlen的基本格式、

size_t strlen ( const char * str );

Tips:
1.字符串以符号 ‘\0’ 作为结束标志,计算的是 ‘\0’ 之前的字符数量(不包含 ‘\0’ 本身)。
2.如果作为参数的字符串内不包含 ‘\0’ 那么就会越界访问,最后计算出随机值。
3.函数的返回值类型为 size_t ,无符号整形。

模拟实现:
任何对于空指针的解引用都会导致错误。
所以在需要对指针解引用的场合,都建议加上assert 来判断。

//内部计数器
size_t my_strlen(const char* str)
{
	assert(str);
	size_t count = 0;
	if (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}

#2 strcpy

两个字符指针作为参数,分别指向目标空间和源字符串。
复制字符串,将源字符串 source 拷贝到目标空间 destination 中。
返回指向目标空间的指针。

//strcpy的基本格式

char* strcpy(char * destination, const char * source );

Tips:
1.源字符串必须以 ‘\0’ 结尾,否则同样会造成越界访问。
2.因为是拷贝字符串,所以也会将结果的 ‘\0’ 拷贝到目标空间中。
3.注意目标空间需要足够大。

模拟实现:

char* my_strcpy(char* destination, const char* source)
{
	assert(destination);
	assert(source);
	//保留初始destination所指向的位置
	char* ret = destination;
	while (*source != '\0')
	{
		*destination = *source;
		destination++;
		source++;
	}
	*destination = *source;
	return ret;
}

#3 strcat

两个字符指针作为参数,分别指向目标空间和源字符串。
将源字符串拷贝到目标空间内原有字符串之后。即字符串的追加。
返回指向目标空间的指针。

//strcat的基本格式

char * strcat ( char * destination, const char * source );

Tips:
1.源字符串必须以 ‘\0’ 结尾。
2.追加操作会覆盖目标字符串最后的 ‘\0’ ,保证最终合并的字符串也只有最后的 ‘\0’ 。
3.目标空间需要足够大,能够容纳追加后的长度。

模拟实现:

char* my_strcat(char* destination, const char* source)
{
	assert(destination);
	assert(source);
	char* ret = destination;
	//先找到 '\0' 的位置,这就是追加开始的位置
	while (*destination != '\0')
	{
		destination++;
	}
	//后续就和strcpy一致了
	while (*source != '\0')
	{
		*destination = *source;
		destination++;
		source++;
	}
	*destination = *source;
	return ret;
}

#4 strstr

传递两个字符指针,一个作为母字符串,一个作为子字符串。
判断母字符串中是否包含子字符串。
如果有,返回指向子字符串在母字符串中第一个字符的指针;否则,返回空指针。

//strstr的基本格式

char * strstr ( const char *str1, const char * str2);

两个字符串当然都要以 ‘\0’ 结尾。

模拟实现:

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1);
	assert(str2);
	//如果子字符串仅仅只有一个'\0',那么也视作包含,返回母字符串的首字符位置即可。
	if (*str2 == '\0')
	{
		return (char*)str1;
	}
	//只要head还不是 '\0',就说明母字符串没走到头,循环就继续
	char* head = (char*)str1;
	while (*head != '\0')
	{
		//保存两字符串的起始位置
		//母字符串每次不从初始位置开始走,而是从head开始走
		char* s1 = head;
		char* s2 = (char*)str2;
		//两个字符相同且不为'\0',循环就继续,判断下两个字符
		while ((*s1 != '\0') && (*s2 != '\0') && (*s1 == *s2))
		{
			s1++;
			s2++;
			//当子字符串走到头了,说明子字符串的确包含在母字符串内
			if (*s2 == '\0')
			{
				return head;
			}
		}
		//出这个循环不代表不包含,可能只是起始位置不对.让起始位置向下走
		head++;
	}
	//出这个循环才说明真的不包含,此时返回空指针
	return NULL;
}

#5 strcmp

两个字符指针。一号字符串与二号字符串。
判断两个字符串大小。
如果一号字符串大于二号字符串,返回大于0的数字。
如果相等大小,则返回0.
如果一号字符串小于二号字符串,返回小于0的数字。

//strcmp的基本格式

int strcmp ( const char * str1, const char * str2 );

Tips:
1.比较两个字符串大小,是看两个字符串中字符的ascll码值的大小,而不是比较长度。
例如abc就小于qp,因为a的ascll码值小于q。
2.如果第一位符号相同,就比第二位,以此类推。

模拟实现:

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1);
	assert(str2);
	//如果两个字符相同且不为'\0',就继续比较
	while ((*str1 == *str2) && (*str1 != '\0'))
	{
		str1++;
		str2++;
	}
	//出上面循环有两种情况
	//第一种,两个符号不同
	if (*str1 != *str2)
	{
	//判断大小,返回相应的值
		if ((*str1 - *str2) > 0)
		{
			return 1;
		}
		else
		{
			return -1;
		}
	}
	//第二种,都走到头了,说明两字符串相同
	return 0;
}

#6 memcpy

三个参数,两个任意相同类型指针,一个无符号整形。指针分别指向目标空间和源空间。
将源空间的前几个字节复制到目标空间,具体多少个取决于无符号整形参数。
返回值为指向目标空间起始位置的指针。

//memcpy的基本格式

void * memcpy ( void * destination, const void * source, size_t num );

Tips:
1.memcpy并不局限于字符串,指针参数类型和返回值类型都为void*,意味着可以接收和返回任意类型的指针。
2.与strcpy不同的是,memcpy并不会在遇到 '\0’的时候停下,所以需要额外传参确定复制多少字节。
3.源空间与目标空间不能有任何的重叠。如果有重叠,就可能导致不完整的复制。

模拟实现:

void* my_memcpy(void* destination, const void* source, size_t num)
{
	assert(destination);
	assert(source);
	void* ret = destination;
	while (num--)
	{
		*(char*)destination = *(char*)source;
		destination = (char*)destination + 1;
		source = (char*)source + 1;
	}
	return ret;
}

#7 memmove

参数及类型与memcpy完全一致。

//memmove的基本格式

void * memmove ( void * destination, const void * source, size_t num );

与memcpy的区别只在于memmove处理的源空间和目标空间是可以重叠的。
所以如果有这种场合,就必须使用memmove而不是memcpy。

模拟实现:

void* my_memmove(void* destination, const void* source, size_t num)
{
	assert(destination);
	assert(source);
	void* ret = destination;
	//判断空间是如何重叠的
	//如果目标空间的的尾部与源空间的头部重叠,或者根本不重叠,那就和memcpy一致
	if ((destination < source) || ((char*)destination >= (char*)source + num))
	{
		while (num--)
		{
			*(char*)destination = *(char*)source;
			destination = (char*)destination + 1;
			source = (char*)source + 1;
		}
		return ret;
	}
	//否则就得倒着来复制,不然源空间的尾部数据还没有来得及复制就会被覆盖,导致结果不符合预期
	else
	{
		(char*)destination = (char*)destination + num - 1;
		(char*)source = (char*)source + num - 1;
		while (num--)
		{
			*(char*)destination = *(char*)source;
			destination = (char*)destination - 1;
			source = (char*)source - 1;
		}
		return ret;
	}
	return ret;
}

结尾

有更多没有提及的库函数,这里展现的仅仅只是冰山一角。
以及模拟实现函数的方法可能不止一种,文中也只展现了一种。

至于,为什么要进行库函数的模拟实现?
比方说,如果没有仔细了解过strlen,可能会认为返回值是整形 int ,这就会导致在某些场合错误的使用库函数。
通过模拟实现,我们更深刻的重新认识这些库函数,或发现了以前没有注意过的细节,或加深了记忆,都有利于以后得心应手的使用。

以上。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值