【库函数】的模拟实现 --- 超详细解析注释

目录

前言

1.strlen(计算字符串长度的函数)

思路

话不多说模拟实现开始

2.strcpy

库函数strcpy的定义及解释

 模拟实现strcpy

3.strcat(功能在目标字符串末尾追加字符串)

库函数strcat的定义及解释

 模拟实现strcat

 4.strstr

库函数strstr的定义及解释

模拟实现strstr

 strstr不重叠内存拷贝图解(可以与提供代码对照)

 5.strcmp

库函数strstr的定义及解释 

​   模拟实现strcmp

6.memcpy - 内存拷贝(函数应该拷贝不重叠的内存)

库函数strstr的定义及解释 ​

 模拟实现memcpy

7.memmove- 内存拷贝(函数可以拷贝重叠的内存)

 模拟实现memmove

重叠内存拷贝图解 

 8.memcmp - 比较两个内存块

 库函数strstr的定义及解释 

 模拟实现memcmp

9.memset(填充内存块)

模拟实现memset

小白提示


前言

小祥一共实现了九种常用的字符函数和字符串函数,像mem开头的都是与内存相关的库函数,也叫内存函数。

小祥我已经对各个库函数的模拟实现的做了尽可能多的注释与解析

希望可以对不清楚的人提供一些帮助,能帮到你们摆脱迷糊,若有帮助的话请不要忘了在评论区留言

当然小祥我在里面可能有会有一些错误或失误不足,若发现请各位大佬们指出指正,相互讨论共同进步


1.strlen(计算字符串长度的函数)

思路

要实现strlen函数首先需要知道字符串是以什么为结束的标志,我想有人会说是'\0',没错就是以'\0'为结束的标志在strlen函数中返回的是字符串中'\0'前面出现的字符个数(注意不包含'\0'自己)

既然要返回字符串中字符的个数,那函数的返回值类型是什么?有人可能会说是int类型,当然也没什么问题,返回字符串中字符的个数用整形int来返回似乎没什么毛病,但仔细想想字符的个数只能是一个大于等于0的正数那我们可以用一个无符号的整形size_t(Unsigned int)来接受这个值是不是更好呢(这样可以确保你写的函数返回的永远是一个正数,从而避免得出的字符个数是负数的尴尬局面)

最后函数的参数要指向的字符串必须要以'\0'结尾(否则结果是个随机值)

话不多说模拟实现开始

//方式一
//计数器
size_t my_strlen(const char* str)//const关键字修饰*str,*str内容无法修改(更加安全)
{
	int count = 0;//计数
	//str为字符串首元素的地址,*str对str(进行解析引用操作)得到str指向的首元素a
	while (*str)//当*str='\0'时循环结束,'\0'的ASIIC 码值就是0,而0为假故循环结束
	{
		str++;//让str指向下一个元素的地址
		count++;		
	}
	return count;
}

//方式二
//不创建临时变量(本质是递归)
size_t my_strlen(const char* str)
{
	if (*str == '\0')
		return 0;
	else
		return 1 + my_strlen(str + 1);
}

//方式三
//指针-指针的方式
size_t  my_strlen(const char* s)
{
	char* p = s;
	while (*p != '\0')
		p++;
	return p - s;//指针减指针等于指针之间的元素个数 (a b c d )
}

//方式四
int test_strlen(const char* str)
{
	const char* eos = str;
	while (*eos++);	//*eos++最终会向后偏移两个/0,*eos='\0'时又要执行eos++
	return(eos - str-1);//结果指针eos-str最后多了个\0要减1,才是字符串的长度
}

int main()
{
	char arr[] = "abcd";//字符串结尾处隐藏了一个'\0'
    //char arr[]={'a','b','c','d','\0'};//计算字符数组的长度末尾要加'\0',否者计算的是个随机数
	printf("%d", my_strlen(arr));
}

 方法三指针-指针图示

2.strcpy

库函数strcpy的定义及解释

注意:库函数strcpy在把源字符内容,拷贝到目标数组里是通过指针的方式,指针是一个一个把指向的字符元素赋值给目标数组

这其中就需要确定指针指向的内存空间是多大由于一个字符所占空间的大小是一个字节,故定义参数时定义的是char*的指针类型

可以看到库函数上char* source被const关键字修饰(目的为了保证安全性,防止source被修改)

const 关键字修改类型声明的类型或函数参数的类型,以防止值发生变化。

返回值类型是char*,由于数组内字符在内存中是依次存放,故最会只需返回目标的首地址就行了(关于内存的存储方式在此不过多的解释,以后可能会专门解释)

 模拟实现strcpy

#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest && src);//assert断言 防止传过来得为空指针
	while(*dest++ = *src++)//*src='\0'时把'\0'赋给了*dest然后跳出循环
	{
		;
	}
	//上面为下面的简写
	/*assert(dest != NULL);
	assert(src != NULL);*/
	//while (*dest = *src)
	//{		
	//	dest++;
	//	src++;
	//}
	return ret;
}
int main()
{
	char arr[10] = "abcdef";
	char arr1[10] = {0};
	my_strcpy(arr1,arr);
}

3.strcat(功能在目标字符串末尾追加字符串)

库函数strcat的定义及解释

 模拟实现strcat

char *my_strcat(char *dest, const char*src)
{
char *ret = dest;//记住首地址dest
assert(dest != NULL);
assert(src != NULL);
while(*dest)//先让dest指向'\0'
{
dest++;
}
while(*dest++ = *src++)//再在'\0'位置追加字符串
{
;
}
return ret;
}

int main()
{
	char arr[10] ="abcd";
	char arr1[] = "efg";
	my_strcat(arr,arr1);
}

 注意:指明目标空间的大小,让目标空间可以放下source内的字符串,下面为未指定目标空间大小导致栈溢出的错误演示

 

 

 

 4.strstr

库函数strstr的定义及解释

模拟实现strstr

#include <assert.h>
char* my_strstr(const char* str1,const char* str2)
{
	assert(str1,str2);
	const char* s1 = NULL;
	const char* s2 = NULL;
	const char* cp = str1;
	if (*str2 == '\0')
	{
		return (char*)str1;
	}
	while (*cp)
	{
		s1 = cp;
		s2 = str2;
		while (*s1 && *s2 && (*s1 == *s2))//*s1和*s2不为NULL,且*s1==*s2三个条件缺一不可
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return (char*)cp;
		}
		cp++;
	}
	return NULL;//若没有找到字符串则返回会空指针
}
int main()
{
	char arr1[] = "abcbcdef";
	char arr2[] = "bcd";
	char* ret = my_strstr(arr1,arr2);
	if (ret == NULL)
	{
		printf("没找到了");
	}
	else 
	{
		printf("找到了:%s",ret);
	}
}

 strstr不重叠内存拷贝图解(可以与提供代码对照

 5.strcmp

库函数strstr的定义及解释 

   模拟实现strcmp

int my_strcmp(char* a, char* b)
{
	assert(a && b);
	while (*a==*b)
	{
		if (*a == '\0')
		{
			return 0;//'\0'为结束标志
		}
		a++;
		b++;
	}
	return *a - *b;//返回的结果为俩个字符的ASIIC码值相减
}
int main()
{
	char p[20] = "abcd";
	char q[20] = "abad";
	int ret=my_strcmp(p,q);
	if (ret == 0)
	{
		printf("p==q");
	}
	else if(ret>0)
	{
		printf("p>q");
	}
	else
	{
		printf("p<q");
	}
}

6.memcpy- 内存拷贝(函数应该拷贝不重叠的内存)

库函数strstr的定义及解释 

 注意:memcpy可以给任意类型的数据进行拷贝操作,故要实现它需要用void*(无具体类型的指针,可以接受任意类型的数据,可用性更强)这也就导致需要指明拷贝内容的内存大小占多少个字节num作为第三个参数

对于重叠内存块部分标准strstr的库函数是不好实现的,有memmov这个库函数来实现,但vs里的strstr的库函数实现了和memmove一样的功能

memcpy : vs的库函数实现了内存重叠拷贝,其他编译器不一定实现了,标准只需实现不重叠内存的拷贝

 模拟实现memcpy

void* my_memcpy(void* dest,const void* src,size_t num)
{
	void* ret = dest;
	assert(dest && src);
	while (num--)
	{
		*(char*)dest = *(char*)src;//强转为char*类型,由于char类型大小为一个字节
		dest = (char*)dest + 1;
		src = (char*)src + 1;
		
		//*(((char*)dest)++) = *(((char*)src)++);
        //()分隔可以实现上面一样的功能在vs编译器下,其他编译器可能不行(不推荐使用)

		//*(char*)dest++ = *(char*)src++;//err
		//1.由于强制类型转换为一种临时的状态,++为后置的,
		//当++时强制类型的转换的状态已经过去了,依旧是void*
		//2.也可能是后置++的优先级要高于强制类型装换
        //上述为个人理解若有不对请大佬们在评论指出!!!!!
	}
	return ret;
}
int main()
{
	int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
	int arr2[20] = { 0 };
	my_memcpy(arr2,arr1,16);

}

7.memmove- 内存拷贝(函数可以拷贝重叠的内存)

与标准memcmp库函数类似也是实现内存的拷贝,但它可以处理内存重叠的情况

 模拟实现memmove

void* my_memmove(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	assert(dest && src);
	if (dest < src)
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		while(num--)
		{
			*((char*)dest + num) = (*(char*)src + num);
		}
	}
}
int main()
{
	int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
	int arr2[20] = { 0 };
	my_memcpy(arr2,arr1,16);

    //内存重叠拷贝
    my_memcpy(arr1+2,arr1,16);
    my_memcpy(arr1,arr1+2,16);

}

重叠内存拷贝图解 

 8.memcmp- 比较两个内存块

 库函数strstr的定义及解释 

 模拟实现memcmp

//memcmp - 内存比较
int  my_memcmp(const void* str1,const void* str2,int num)
{

	//方式一
	//while (*(((char*)str1)++)== *(((char*)str2)++))
	//{
	//	num--;
	//	if (num == 0)
	//	{
	//		return 0;
	//	}
	//}
	//return *((char*)str1-1) - *((char*)str2-1);
    //这里减1是因为while()内是后置++,当跳出循环时会加1,导致结果后偏移1个字节

    //方式二
	while (*(char*)str1==*(char*)str2)
	{
		str1 = (char*)str1 + 1;
		str2 = (char*)str2 + 1;
		num--;
		if (num == 0)
			return 0;
	}
	return *(char*)str1 - *(char*)str2;
}

int main()
{
	int arr1[] = {1,8,3,4,5,6};
	int arr2[] = {9,8,4,5,6};
	int ret =my_memcmp(arr1, arr2,8);
    //arr1>arr2返回>0的数,arr1<arr2返回<0的数,arr1=arr2返回0
	printf("%d\n",ret);
	return 0;
}

9.memset(填充内存块)

第一个参数:指向要填充的内存块的指针。

 第二个参数:要设置的值。该值作为 int 传递,但该函数使用此无符号字符转换填充内存块。

第三个参数:要填充的的字节数,size_t(unsigned int)类型

模拟实现memset

//memset - 内存设置
void* my_memset(void* dest,int value,size_t num)
{
	assert(dest);//断言
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = value;//给每个字节的内存赋值
		dest = (char*)dest + 1;
	}
	return ret;
}
int main()
{
	int arr[10] = { 0 };
	my_memset(arr, 1, 20);//以字节为单位设置内存	
	for (int i = 0; i < 10; i++)
	{
		printf("%d\n", arr[i]);
	}
	return 0;
}

小白提示

下图为内存调试时如何显示内存里值被改为1(右键arr以16进制显示及可看到20个字节都被改为1)

 

 

 尾声

相信看到这里,大家已经对函数的模拟实现已经有了一定了解了,如果你感觉这篇博客对你有帮助的话,不要忘了一键三连支持一下再离开噢!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值