字符函数与字符串函数(关于一些常用字符串函数的模拟实现,非常详细)

写在文章之前

我们都知道在C语言里面有非常多的库函数,那么在我们使用这些库函数的时候会不会好奇这些库函数都是怎么实现的呢?本文讲解了C语言库函数中用的比较多的字符串函数的模拟实现。

strcpy函数的模拟实现

俗话说:“工欲善其事,必先利其器”。所以在模拟实现strcpy函数之前,我们可以先查一下strcpy函数的参数是什么,并查一下strcpy函数是如何使用的。这里我推荐一个网站叫做cplusplus.com的网站,这里面有非常多的C语言库函数,并且我们可以通过这个网站查询到那些库函数的用法,并可以尝试着去模拟实现这些库函数。这是具体网址:https://legacy.cplusplus.com/
在这里插入图片描述
在打开了这个网站之后我们点击搜索框就可以来搜索我们所需要的库函数了。
在这里插入图片描述
在这里我们可以看到strcpy函数的函数名、函数的参数和函数的返回类型。
在这里插入图片描述
在这里我们可以看到一些关于函数的用法的介绍和函数参数的解释和返回类型的解释以及函数使用的案例。
通过我们对函数的用法的介绍我们了解到这是一个将一个字符串拷贝到另一个字符串的函数,其中destination是需要拷贝到的目标位置,source是拷贝的源头也就是我们需要拷贝的字符串。最后该函数会返回一个char类型的指向目标位置的指针给我们。
在模拟实现这个库函数之前,我们可以先试着使用一下这个库函数,看看是否真的如同这个网站上所说的这样子。
以下是 strcpy 函数的使用示例:

#include <string.h>
#include <stdio.h>

int main()
{
	char arr1[30] = { 0 };
	char arr2[] = "abcdef";
	printf("%s\n", strcpy(arr1, arr2));
	return 0;
}

下面是这个代码的运行结果:
在这里插入图片描述
我们发现我们真的通过这个库函数实现了字符串拷贝。
那么接下来我们就可以基于我们上面对这个库函数的分析来模拟实现一下 strcpy 函数了。
下面就是strcpy函数的模拟实现的代码:

#include <stdio.h>
#include <assert.h>

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	while (*src)
	{
		*dest = *src;
		dest++;
		src++;
	}
	return ret;
}

int main()
{
	char arr1[10] = { 0 };
	char arr2[] = "abcdef";
	printf("%s\n", my_strcpy(arr1, arr2));
	return 0;
}

代码分析:
首先,我们得传两个参数给 strcpy 函数,并且其中一个是源头,我们不希望修改源头里面的字符,因此需要加一个 const 来修饰该指针。
然后,由于我们得把 src 中的字符串拷贝到 dest 中去,所以只要当 src 解引用之后不等于 ‘\0’ 我们就得将 src 字符串中的内容拷贝到 dest 中去。所以这就需要一个循环,而循环的条件就是 * src 。循环的内容就是将字符串 src 进行解引用后得到的字符赋值给字符串 dest 并且指针 dest 和 src 都得后移。
最后,因为函数的返回类型是 char * 类型,所以我们得返回一个指向 dest 字符串的首元素地址的指针。所以在一切开始之前我们只需要将指针 dest 赋值给一个新的指针。最后返回这个指针所指向的地址就行了。
下面是该函数运行起来的结果:
在这里插入图片描述

strcpy函数的危险性

在上面的代码,我们发现我们对数组 arr1 的长度是10,那如果我们改动以下这个数组的长度,那么会发生什么事呢?
以下是改动后程序运行的结果:
在这里插入图片描述

我们发现,虽然程序将我们的字符串成功地拷贝过去了,但是程序崩溃了。
这就是strcpy函数的危险性,它不会去自动识别数组长度够不够用。那么有没有什么安全一点的函数让我们去使用呢?strncpy 就是一个这样的函数,它可以通过用户自己输入需要拷贝多少字符串来做到一定程度上的限制。

strncpy函数的模拟实现

同样我们可以去cplusplus这个网站上去找 strncpy 这个库函数。
在这里插入图片描述
我们发现,这个库函数比 strcpy 函数多了一个叫做 num 的参数,我们通过这个参数就可以自己决定拷贝多少个字符串进入目标位置。
同样,我们可以试着写一个案例来试着使用以下 strncpy 函数。
以下是strncpy函数的演示示例:

#include <string.h>
#include <stdio.h>

int main()
{
	char arr1[3] = { 0 };
	char arr2[] = "abcdef";
	printf("%s\n", strncpy(arr1, arr2, 2));
	return 0;
}

下面是运行结果:
在这里插入图片描述
当我们输入2的时候,这个库函数就将数组 arr2 中的两个字符拷贝到了数组 arr1 中,该程序并没有崩溃或者报错。
那么,我们应该如何模拟实现这个库函数呢?
其实,只要增加一个计数器的功能就可以模拟实现这个库函数了。
代码示例如下:

#include <stdio.h>
#include <assert.h>

char* my_strncpy(char* dest, const char* src, size_t num)
{
	assert(dest && src);
	char* ret = dest;
	int i = 0;
	for (i = 0; i < num; i++)
	{
		*dest++ = *src++;
	}
	return ret;
}

int main()
{
	char arr1[3] = { 0 };
	char arr2[] = "abcdef";
	printf("%s\n", strncpy(arr1, arr2, 2));
	printf("%s\n", my_strncpy(arr1, arr2, 2));
	return 0;
}

运行结果如下:
在这里插入图片描述
我们通过在函数中增加了一个计数器的功能来达到了我们需要拷贝多少个字符串的目的。

strcat函数的模拟实现

同样的,我们先在cplusplus上看看 strcat 函数有什么作用吧。
在这里插入图片描述
通过cplusplus我们了解到,strcat 函数是一个拼接函数。destination是需要拼接到的目标位置,source是需要拼接的字符串。最后该函数会返回一个char类型的指向目标位置的指针给我们。那么现在开始我们就可以来模拟实现我们的strcat函数了。同样,再模拟实现 strcat 函数之前,我们先来看一段使用示例。
strcat 函数的使用示例:

#include <string.h>
#include <stdio.h>

int main()
{
	char arr1[30] = { "abc" };
	char arr2[] = "def";
	strcat(arr1, arr2);
	for (int i = 0; i < 6; i++)
	{
		printf("%c", arr1[i]);
	}
	return 0;
}

下面是运行结果:
在这里插入图片描述
我们发现我们成功地把两个字符串拼接到一起并将其存储在数组arr1中打印出来了。
那么,我们应该如何实现strcat函数呢?
下面就是strcat函数模拟实现的代码:

#include <assert.h>
#include <stdio.h>

char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	while (*dest != '\0')
	{
		dest++;
	}

	while (*src != '\0')
	{
		*dest = *src;
		dest++;
		src++;
	}
	return ret;
}

int main()
{
	char arr1[30] = "abcdef";
	char arr2[] = "ghij";
	printf("%s\n", my_strcat(arr1,arr2);
	return 0;
}

代码分析:
首先,我们得传两个参数给 strcat 函数,并且其中一个是源头,我们不希望修改源头里面的字符,因此需要加一个 const 来修饰该指针。
然后,因为我们得吧 src 中的字符串增加给 dest 指针,也就意味着数组 arr1 中的内容应该会增大。那我们应该如何增大数组 arr1 中的内容呢?我们都知道,字符串的结束标志是 ‘\0’。因此,我们只要吧 arr1 数组中的 ‘\0’ 修改掉就可以把数组 arr2 中的内容增加到数组 arr1 中了。但是 dest 指针指向的是数组 arr1 首元素的地址。所以我们要先让 dest 指针移动到 数组arr1 中 ‘\0’ 的位置上才可以进行接下来的拼接操作了。
最后,因为 src 指向的是数组 arr2 首元素的地址,所以我们只需要直接用数组 arr2 中的内容去拼接到数组 arr1 中就行了,由于数组 arr2 中也有 ‘\0’ 因此当我们读取到 数组 arr2 中 ‘\0’ 的位置的时候就可以停止拼接了。
需要注意的是:函数 strcpy 返回的是一个指向 destination 的指针,所以我们得在开始之前把 dest 的地址先传给一个指针变量,最后我们直接返回那个指针变量的地址就行了。
下面是程序运行起来的结果:
在这里插入图片描述

strcat函数的危险性

strcat 函数的危险性其实与 strcpy 函数的危险性非常类似,也会出现一个数组越界的现象,具体运行情况如下:
在这里插入图片描述

和 strcpy 函数一样,我们也有一个函数来保证程序的安全性,那就是 strncat 函数。

strncat函数的模拟实现

通过 cplusplus 这个网站我们也可以查到关于 strncat 函数的一些相关的信息。
具体信息如下:
在这里插入图片描述
我们发现这个 strncat 与 strcat 函数的区别和 strcpy 与 strncpy 的区别是一样的都是多了一个参数 num 通过这个参数我们来选择拷贝多少字符串和拼接多少字符串。
在模拟实现这个函数之前,我们同样可以先使用一下这个函数,看看是否如这个网站上所说的那样。
以下是 strncat 的使用示例:

#include <stdio.h>
#include <string.h>

int main()
{
	char arr1[6] = "abc";
	char arr2[] = "defg";
	strncat(arr1, arr2, 2);
	printf("%s", arr1);
	return 0;
}

下面是运行结果:
在这里插入图片描述
下面是 strncat 的模拟实现的代码:

#include <assert.h>
#include <stdio.h>

char* my_strncat(char* dest, const char* src, size_t num)
{
	int count = 0;
	assert(dest && src);
	char* ret = dest;
	while (*dest != '\0')
	{
		dest++;
	}
	while (count < num)
	{
		*dest = *src;
		dest++;
		src++;
		count++;
	}
	return ret;
}

int main()
{
	char arr1[3] = { 0 };
	char arr2[] = "abcdef";
	printf("%s\n", my_strncat(arr1, arr2, 2));
	return 0;
}

下面是运行结果:
在这里插入图片描述
和上面的strncpy函数的模拟实现一样,我们只要加入一个计数器的功能就可以实现strncat函数了。

strcmp函数的模拟实现

strcmp函数的模拟实现

这是cplusplus网站上对于strncmp函数的介绍:
在这里插入图片描述
我们可以了解到,strcmp函数是一个字符串比较函数,当我们比较的第一个字符串中的元素大小小于第二个字符串中的元素大小时会返回一个小于0的数字,当我们比较的第一个字符串中的元素大小大于第二个字符串中的元素大小时会返回一个大于0的数字,当我们比较的第一个字符串中的元素大小等于第二个字符串中的元素大小时会返回0。
那么,我们现在来尝试着使用以下strcmp函数吧。
以下是strcmp函数的使用示例:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们发现这个函数确实如同cplusplus网站上说的那样。那么我们应该如何模拟实现这个函数呢?
以下是strcmp函数的模拟实现:

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while (*str1 || *str2)
	{
		if (*str1 > *str2)
		{
			return 1;
		}
		else if (*str1 < *str2)
		{
			return -1;
		}
		str1++;
		str2++;
	}
	return 0;
}

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	int ret = my_strcmp(arr1, arr2);
	if (ret > 0)
		printf(">\n");
	else if (ret < 0)
		printf("<\n"); 
	else
		printf("=\n");
	return 0;
}

下面是运行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
以下是代码分析:
strcmp函数是一个字符串比较函数,这个函数的比较原理是比较字符串中的字符的大小进而返回一个数字。所以我们只需要两个指针去遍历我们的字符串并进行比较就行了。
同样,既然有strcmp函数也自然有一个strncmp函数来控制我们字符串中的元素个数的比较。

strncmp函数的模拟实现

以下是cplusplus网站中的strcmp函数的介绍:
在这里插入图片描述
以下是strncmp函数的使用示例:
在这里插入图片描述
我们也同样可以增加一个计数器来实现我们的控制字符串元素比较的数量。
具体代码示例如下:

#include <stdio.h>
#include <assert.h>

int my_strncmp(const char* str1, const char* str2, size_t num)
{
	assert(str1 && str2);
	int i = 0;				//用来记录比较次数
	for (i = 0; i < num; i++)
	{
		if (*str1 > *str2)
			return 1;
		else if (*str1 < *str2)
			return -1;
		str1++;
		str2++;
	}
	//超过了比较次数,如果还是相等的话就返回0
	return 0;
}

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcd";
	int ret = strncmp(arr1, arr2, 4);
	if (ret > 0)
		printf(">\n");
	else if (ret < 0)
		printf("<\n");
	else
		printf("=\n");
	return 0;
}

运行结果如下:
在这里插入图片描述
以上就是strncmp函数的模拟实现。

strstr函数的模拟实现

strstr函数的模拟实现

下面是cplusplus对于strstr函数的介绍:
在这里插入图片描述
我们可以通过这个了解到strstr函数是找到字符串str2在字符串str1中首次出现的位置并返回这个位于str1的位置。
我们先试着使用以下这个函数看看是不是这样子。
以下是strstr函数的使用示例:

#include <string.h>
#include <stdio.h>

int main()
{
	char arr1[] = "bcd";
	char arr2[] = "abcdef";
	char* ret = strstr(arr2, arr1);
	printf("%s\n", ret);
	return 0;
}

下面是运行示例:
在这里插入图片描述
那么它应该如何进行实现呢?
以下是strstr函数的模拟实现的示例:

#include <stdio.h>
#include <assert.h>

const char* my_strstr(const char* destination, const char* source)
{
	assert(destination && source);
	const char* str1 = destination;
	const char* str2 = source;
	const char* cmp = str1;
	//如果要我们在一个字符串中找空字符串我们就直接str1的地址就行了
	if (source == '\0')
	{
		return str1;
	}
	while (*cmp)
	{
		if (*str1 == *str2)
		{
			cmp = str1;//存放可能返回的地址
		}
		while (*str1 == *str2 && *str1 && *str2)
		{
			str1++;
			str2++;
		}
		//出循环说明不等
		//如果str2遍历完了说明就是我们需要的地址
		//如果还没有遍历完说明不是需要的地址,开始回溯
		if (*str2 == '\0')
		{
			return cmp;
		}
		cmp++;
		str1 = cmp;
		str2 = source;
	}
	return NULL;
}
int main()
{
	char arr1[] = "bcd";
	char arr2[] = "abcdef";
	char* ret = my_strstr(arr2, arr1);
	printf("%s\n", ret);
	return 0;
}

下面是运行结果:
在这里插入图片描述
在这里插入图片描述
以下是代码分析:
首先我们来解释为什么这个函数中需要这么多的指针变量:
如果我们直接去遍历查找相应的字符串的话,如果这两个字符串一直是相等的话,str1和str2就会一直不停地往后找相同的元素,直到其中的一个字符串被遍历完了。但是如果我们发现有一个字符不一样的话,我们的destination和source就不知道找到哪里去了,为了避免这个情况,我们就干脆让destination和source指针一直指向字符串的首元素地址,创建一个临时指针变量str1和str2,再由我们的str1和str2来遍历字符串。这就是str1和str2创建的理由,因为destination和source都用const修饰过了,如果不对str1和str2用const修饰的话就有可能我们通过str1和str2来改变字符串中原有的值。所以str1和str2都要用const修饰。这是为什么str1和str2都要使用const进行修饰的原因。
然后,我们来解释为什么需要cmp这个指针变量:
当我们的str1和str2不停地往后遍历的时候,我们会发现我们一旦后面发现了有一个字符不一样,str2可以很容易地回到原点,只要通过对str2赋值source的地址就行了。但是我们的str1还是无法回去,因此我们需要一个cmp指针变量来存放我们有可能出现所需要求的地址,这样子就算后面有一个字符不一样我们的str1也能通过赋值cmp的地址回到之前相同字符串的位置。cmp指针变量前面加const的原因和前面的str1和str2的原因一样。
最后,这个代码的执行逻辑是:
先通过遍历str1,当我们遇到str1和str2相同的字符的时候就将该地址先赋值给cmp指针变量以方便以后str1回溯。然后我们就可以遍历str1和str2后面的元素了,如果我们遇到了有一个字符不一样且两个数组都没有遍历完,我们就可以用cmp赋值给str1和用source赋值给str2来达到回溯的效果,然后比较后面的字符串是否相同。如果我们在遍历的过程中发现str2已经遍历完了,那就说明我们已经找到我们所需要的地址了。直接返回cmp就行了。当我们的cmp指针变量已经遍历完str1中的内容时,说明destination中没有出现source中的字符串,这个时候我们可以直接传一个空指针回去。
其实这种在一个字符串中找另一个字符串属于暴力算法,就是直接遍历这个字符串,时间复杂度为(m*n)耗时较长。还有一种时间复杂度更低一点的方法叫KMP算法,它将这个时间复杂度转换为线性时间复杂度。感兴趣的可以找相关文章看一看。
这次博客的代码已经传到码云链接上去了,需要的自取哦https://gitee.com/g_666/c-language-code-warehouse/commit/7103fbd19be6517308b40d1947ec41eaab6e7dce

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值