「C语言」一些库函数的模拟实现

发布于:2022-11-05 ⋅ 阅读:(330) ⋅ 点赞:(0)

不愤不启,不悱不发。举一隅不以三隅反,则不复也。

目录

为什么要模拟库函数

memmove模拟实现

strcpy模拟实现

strlen模拟实现

strcat模拟实现

strstr模拟实现


为什么要模拟库函数

我们都知道C语言给我们提供了很多库函数给我们使用,功能强大,使用方便。如此一来我们直接使用不就可以了吗,为啥还要模拟实现呢?正所谓知其然知其所以然,模拟库函数可以让我们进一步加深对某个函数的理解,它的参数、它的返回值为什么这样设计,等以后用起来便更加得心应手。

memmove模拟实现

它的功能是把一块内存空间的数据移动到另一块内存空间,并且允许这两个内存空间有重叠的部分。

包含它的头文件为 string.h,它的格式是这样的:

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

可以看到,memmove有3个参数。

第一个参数是一个空指针,表示这块内存空间的数据要被移动到哪,destination即为目的地地址。

第二个参数也是一个空指针,表示这块内存空间的起始位置,source即为起始地址。

第三个参数的类型是一个无符号整型,表示这块内存空间的大小,单位是字节,num即为空间大小。

返回值是一个空指针,内容为目的地地址。

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

void* my_memmove(void* dest, const void* src, size_t num)
{
	assert(dest && src);//断言,判断空指针
	char* x = (char*)dest;
	char* y = (char*)src;
	if (x > y)//目标地指针地址高于起始指针地址,起始指针内容从后往前覆盖目标指针内容
	{
		x = x + num - 1;
		y = y + num - 1;
		while (num--)
		{
			*x-- = *y--;
		}
	}
	else
	{
		while (num--)
		{
			*x++ = *y++;
		}
	}
	return dest;
}

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

	return 0;
}

memmove模拟实现的难点在于当两块内存空间有相互重叠的部分的时候,把起始指针的内容从前往后覆盖目标指针的内容时,会丢失重叠部分的数据,导致错误。

strcpy模拟实现

它的功能是把一个字符串复制给另一个字符串。

包含它的头文件为 string.h,它的格式是这样的:

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

strcpy有两个参数,他们都是字符指针,destination为复制内容的接收方,source为复制内容的提供方。接收方的空间大小要大于等于提供方的空间大小,不然会造成越界写入,导致报错。

返回值也是字符指针,内容为接收方的起始地址。

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

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);//断言,判断空指针
	char* ret = dest;
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}

int main()
{
	char str1[] = "violet";
	char str2[50] = { 0 };
	printf("%s\n", my_strcpy(str2, str1));

	return 0;
}

这里有一个小细节,复制什么时候停止呢?是当复制内容的提供方读到 '\0' 时停止,并把 ’\0' 也复制过去。代码中while的停止条件是赋值语句的返回值,当把 '\0' 赋值过去后,赋值语句的返回值为0,循环停止,这样就完成了整个字符串的复制。

strlen模拟实现

strlen函数是用来求字符串长度的,包含它的头文件为 string.h,它的格式为:

size_t strlen ( const char * str );

它的参数很简单,只有一个字符指针,返回值为一个无符号整型,表示这个字符串的长度。

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

size_t my_strlen(const char* str)
{
	assert(str);
	size_t i = 0;
	while (*str != '\0')
	{
		i++;
		str++;
	}

	return i;
}

int main()
{
	char str[] = "violet\0xyz";
	printf("%zd\n", my_strlen(str));

	return 0;
}

strlen计算字符串长度的时候到第一个 '\0' 就停止了,并且不把 '\0' 计算在内,所以打印的结果是6,而不是11。

strcat模拟实现

strcat是在一个字符串后面追加一个字符串,包含它的头文件为 string.h,它的格式为:

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

它有两个参数,类型都是字符指针,destination为追加内容的接收方,source为追加内容的提供方。接收方需要预留足够大的空间来接收提供方给予的字符串,否则会造成越界写入,导致报错。

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

char* my_strcat(char* str1, const char* str2)
{
	assert(str1 && str2);
	char* ret = str1;
	while (*str1)
	{
		str1++;
	}
	while (*str1++ = *str2++)
	{
		;
	}

	return ret;
}

int main()
{
	char str1[] = "hello \0xxxxxxxx";
	char str2[] = "world";
	printf("%s\n", my_strcat(str1, str2));

	return 0;
}

当读到str1的 '\0' 后,所要进行的操作就和strcpy一致了,所以直接使用strcpy的代码就行。

strstr模拟实现

strstr是用来判断一个字符串str1是否含有另一个字符串str2,如果含有,则返回第一次出现str2的地址,如果不含有,则返回NULL。

包含它的头文件为 string.h,它的格式为:

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

可以看到,strstr的两个参数都是不可修改的字符指针,我们可以使用两个可以修改的指针x1、x2来分别指向str1、str2,如果x1和x2相等则进入循环判断,这个时候我们还需要再创建一个指针y记录x1的位置,如果x2走完str2的内容,说明str1包含str2的内容,返回y即可。

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

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	char* x1 = (char*)str1;
	char* x2 = (char*)str2;
	char* y = (char*)str1;
	while (*y)
	{
		x1 = y;
		while (*x1 == *x2 && *x1 != '\0')//不要忘了加 *x1 != '\0'
		{
			x1++;
			x2++;
		}
		if (*x2 == '\0')
			return y;
		y++;
		x2 = (char*)str2;
	}

	return NULL;
}

int main()
{
	char str1[] = "abcbcd";
	char str2[] = "bcd";
	printf("%s\n", my_strstr(str1, str2));

	return 0;
}

本文含有隐藏内容,请 开通VIP 后查看