模拟实现字符串函数与内存函数——strcat,strcmp,strstr memcpy,memmove

strcat函数

认识

进入MSDN中查阅

该函数的返回值是 char* 类型的,因此返回一个字符指针。两个参数也是 char* 类型的,不过第二个参数是常量指针,指向的值不可改变。

 这里就解释两个参数的意义,大致意思就是说:将第二个参数(源字符空间)的内容追加到第一个参数(目标字符空间)的内容后面,也就是遍历第一个参数内容,在\0的位置开始追加。

条件

  1. 源字符必须有 '\0' 
  2. 目标空间必须足够大,可以放得下源字符和目标字符的内容
  3. 目标空间可以被修改

举例使用

 因为返回值是 char* 字符指针类型,所以以上的两种打印方式都是可以的,但是第二张图中调用了两次 strcat 函数所以就打印出两个world了。

 模拟实现

char* my_strcat(char* des, const char* src)
{
	char* p = des;//存下des指向的第一个元素的地址
	assert(des && src);//断言一下,提升安全性 
	while (*des++)//找到\0时,while就停止循环,此时p已经指向\0后面的一个元素
	{
		;
	}
	des--;//指向 '\0'
	while (*des++ = *src++)//等价于*des=*src; des++; src++;
	{
		;
	}
	return p;
}
int main()
{
	char arr[25] = "hello ";
	char arr1[] = "everybody";

	printf("%s\n", my_strcat(arr, arr1));

	return 0;
}

//其实函数的返回值改成void,打印时 printf("%s",arr);也可以实现相同的效果,以上实现的模拟度更高。


那你有没有想过字符串自己给自己追加的呢?

扫描二维码关注公众号,回复: 16749840 查看本文章

这里的 p1 与 p2 都是实参 arr 的地址。 

 当自己追加自己的时候就不满足条件1,所以代码就如同嚼了炫迈一般。


strcmp函数

我们首先得知道比较两个字符串可不可以像数字一样直接比较呢?

 这里是不行的,上面的字符串作为表达式进行比较的时候,实际上比较的是字符串首元素的地址,而不是比较字符串的内容,所以用 ==就显得很尴尬了。所以就有了 strcmp 字符串函数。


认识 

 该函数的返回值是整型,两个参数都是常量字符指针类型。

 返回值小于0时:字符串1 < 字符串2 //一般返回-1

 返回值等于0时:字符串1 = 字符串2

 返回值大于0时:字符串1 > 字符串2 //一般返回1

实际比较的是字符串中每个对应元素的ASCLL码值(包括\0) ,然后通过返回值判断大小。

举例使用

 这里依次进行比较,由于 'c' 的ACSCLL码值小于 ’e‘ 的ASCLL码值,所以返回一个小于0的数,后面的字符就不用判断了

 模拟实现

int my_strcmp(char* p1, char* p2)
{
	assert(p1 && p2);
	while (*p1 == *p2)
	{
		if (!*p1)//先解引用,再逻辑非,及表示*p='\0'时返回0
			return 0;
		p1++; p2++;
	}
	if (*p1 > *p2)
		return 1;
	else
		return -1;

}
int main()
{
	int rul = my_strcmp("abcd", "abcde");
	printf("%d", rul);

	return 0;
}

 //这里里为了更加切合字符串函数在VS上的使用 strcmp 所以返回了1,-1,0这三个结果。实际可以将后面的直接改成:return *p1 - *p2;


strstr函数

认识

 函数的返回值是字符指针类型,两个参数都是常量字符指针类型

 ​​​​​

作用就是:在 str1 这个字符串中找 str2 这字符串,找 str2 第一次 str1 中出现的位置。

如果没找到就返回一个NULL,如果找到了就返回 str1 中第一处出现 str2 的地址。

举例使用

//这里为空时会报错,你也可以直接用 if 语句进行判断是否为NULL。 

 // arr2 中的字符串第一次出现在 字符串 arr1 中的第二个字符的地址处,所以就返回该地址。

模拟实现

char* my_strstr(const char* p1, const char* p2)
{
	int i, j;
	int len1 = strlen(p1);
	int len2 = strlen(p2);

	for (i = 0; i < len1 - len2 + 1; i++)//比较次数
	{
		int count = 0;
		for (j = 0; j < len2; j++)//每一次比较的元素个数
		{
			if (*(p1 + i +j) == *(p2+j))//不使用自加加,p1指向的依旧是arr1中的首字符地址
			{
				count++;
				if (count == len2)
					return p1+i;//p1+i就是进行判断的字符
			}
		}
	}
	return NULL;

}
int main()
{
	char arr1[] = "abcdebcd";
	char arr2[] = "cde";
	char* p = my_strstr(arr1, arr2);
	assert(p);//找不到时就为空

	printf("%s", p);

	return  0;
}

  

 图文分析

代码优化

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);

	while (*str1)
	{
		char* p1 = str1;//重置p1位置
		char* p2 = str2;//
		while (*p1 == *p2 && *p2 && *p1)//*p1用来判断str1字符串中是否有\0在
		{								//*p2是判断是否找到
			p1++;
			p2++;
		}
		if (*p2 == 0)
		{
			return str1;
		}
		str1++;//没找到就后移一个字符
	}
	return NULL;
}

int main()
{
	char arr1[] = "abbdcbbcdef";
	char arr2[] = "de";
	char* p = my_strstr(arr1, arr2);
	if (p)
		printf("找到了:%s", p);
	else
		printf("NULL");

	return 0;
}

  //这个代码其实也有一些重复的地方,没有第一个代码那么直观,但是方法挺像的。如果你还有更C的方法,评论区就交给你们了哟!


以上函数只针对于字符串,而一下的函数使用所有类型函数

memcpy函数

认识

 这里介绍了函数的声明,返回值是通用类型的指针,前两个参数分别是通用类型指针和常量通用类型指针,另一个参数是无符号整型。头文件以上两个都可以。

之所以用通用类型是因为该函数适用于所有的类型函数,所以函数的形参用无符号类型接收会显得合理。

 这里介绍了函数返回值与三个参数的含义,大概就是将src中的数据拷贝到des中,而拷贝空间就是参数三(单位是字节),返回的是目标空间(des)的起始地址。

举例使用

 考你一道:

#include<stdio.h>
int main()
{
	int a[5] = { 0 };
	int b[] = { 1,2,3,4,5 };
	memcpy(a, b, 9);//注意这里是9个字节的空间 
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", a[i]);
	}

	return 0;
}

 //提一下这里博主的机器是小端模式存储 (⊙o⊙)

模拟实现

#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* des, const void* src, size_t num)
{
	assert(des && src);//遇到有解引用的就考虑断言一下

	//这里由于des通过移动来接收内存数据,所以des指向会改变即创建变量
	//而且void* 类型的指针不可以进行解引用与++与--操作,所以最好避免使用++与--
	//强制类型转换只是临时的转换当前类型

	char* p = des;//char是最小字节数的类型(1个字节)
	char* q = src;
	while (num--)
	{
		*p++ = *q++;
	}
	return des;
}
int main()
{
	int a[5] = { 0 };
	int b[] = { 1,2,3,4,5 };
	//拷贝前面三个整型
	my_memcpy(a, b,12);
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", a[i]);
	}

	return 0;
}

  

另一种函数部分写法

void* my_memcpy(void* des, const void* src, size_t num)
{
	assert(des && src);//遇到有解引用的就考虑断言一下

	//这里由于des通过移动来接收内存数据,所以des指向会改变即创建变量
	//而且void* 类型的指针不可以进行解引用与++与--操作,所以最好避免使用++与--
	//强制类型转换只是临时的转换当前类型

	void* p = des;
	while (num--)
	{
		*(char*)des = *(char*)src;    //char是最小字节数的类型(1个字节)
		des = (char*)des + 1;    //拆解++操作
		src = (char*)src + 1;
	}
	return p;
}


 //其实这里的模拟实现与VS上还是有一定区别的,但在其他编译器上类似,例如:

 上面是自己模拟实现的,下面是库函数里头的。因为模拟实现的函数是将内存一个字节一个字节的拷贝,所以在拷贝到重复部分时就出现上面这种情况。

memmove函数

认识

返回值与参数和memcpy函数可以说是一模一样,头文件是<string.h>

但memmove函数是进行内存数据的迁移也可以说是拷贝。

 与memcpy函数也是一样的。

举例使用

模拟实现

我们首先肯定会想到在创建一个新的数组将需要拷贝的部分存起来在一一复制不就行了,这种想法当然ok,但是有没有另一种更节省空间的写法呢?

由于memmove函数与memcpy函数是及其的相似,那么我们可不可以通过改进模拟实现的memcpy函数进而实现memmove的功能呢。

如果使用我们写的memcpy函数就会产生以下的效果;

 //你是否发现主要是重复的部分在搞怪,所以我们是不是先处理掉重复的部分在处理没有重复的是不是就可以达到效果呢:

 //那转成代码的形式又是如何的呢?

#include<string.h>
void* my_memmove(void* des, const void* src, size_t num)
{
	assert(des && src);
	void* p = des;//保留原来的”味道“
	if (des < src)//des指向的内容在src的前面
	{
		//src的元素从前向后拷贝到des上
		while (num--)//按照memcpy实现
		{
			*(char*)des = *(char*)src;
			des = (char*)des + 1;
			src = (char*)src + 1;
		}
	}
	else//des指向的内容在src的后面
	{
		//src的元素从后向前拷贝到des上
		while (num--)//后置--,while从num开始第一次循环
		{
			*((char*)des + num) = *((char*)src + num);//此时的num已经-1了
		}

	}
	return p;

}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memmove(arr + 2, arr,20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	 
	return 0;
}

 //其实在memcpy函数上可以实现的代码在memmove函数上都可以实现。

猜你喜欢

转载自blog.csdn.net/C_Rio/article/details/129464527
今日推荐