C语言第十一课,内存函数的使用及模拟实现

本文详细介绍了C语言中常见的字符串处理和内存操作函数,包括strlen、strerror、strcpy、strncpy、memcpy、memmove、strcat、strncat、strcmp、strncmp、memcmp、strstr和strtok的模拟实现与使用示例。通过这些函数,可以理解字符串长度计算、错误信息转换、字符串和内存拷贝、比较以及查找等操作,对于C语言的学习和实践非常有帮助。
摘要由CSDN通过智能技术生成

这些函数在不久的将来,频繁大量地使用。通过对各类操作函数的模拟实现,不仅对理解类似函数的功能有所帮助,还对往后的刷题环节有帮助,多多练习总结,方可悟道。

1 求字符串长度strlen

strlen
strlen函数返回字符串中的字符数,不包括终端NULL。发生错误也不会返回表示错误的值。

size_t是编译器本身用typedef重命名的一个unsigned int类型,也就是该函数的返回类型。该函数的输入类型是字符串的首地址。计算从首地址开始,一直到’\0’之间的字符个数。

下面是strlen的用法,输出的结果为:<
思考一下,如果判断条件是if (strlen(“abc”) - strlen(“abcdef”)>0),结果为什么会是:> ???

#include<string.h>
int main()
{
	if (strlen("abc") > strlen("abcdef") )
		printf(">");
	else
		printf("<");
	return 0;
}

因为size_t是一个无符号类型的数,两个无符号类型的数相减,结果肯定不会是一个负数,所以不可能会出现小于0 的情况。

模拟实现

程序员在设计这个函数的时候,使用unsigned int类型有两种可能的原因:1是因为字符串的长度不可能出现负数,2是因为无符号类型可以表示的长度更长(仅仅是个人的猜测)。

我在模拟实现的时候,打算使用int类型,因为这样可以使代码更加灵活,可以使上面的两种写法都能运行成功!

有三种方法模拟实现:
法一:计数器

int my_strlen(const char* str)
{
	assert(str);
	int count = 0;
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}

法二:迭代

int my_strlen(const char * str)
{
 if(*str == '\0')
 return 0;
 else
 return 1+my_strlen(str+1);
}

法三:指针 减 指针

int my_strlen(char *s)
{
       char *p = s;
       while(*p != ‘\0)
              p++;
       return p-s;
}

2 错误信息报告 strerror

当程序运行性出现错误的时候,本次执行库函数产生的错误码会放到全局变量errno中。而strerror函数的功能就是把这个错误代码翻译成人可以看懂的语言。
下面的操作可以看出:不同的errno值,翻译成不同的错误:

int main() 
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%s\n", strerror(i));
	}
	return 0;
}

其中errno是C语言提供的一个全局变量,放在errno.h文件中,可以直接使用。
错误报告函数通常出现在文件操作中,如下的代码,以只读的形式打开一个不存在的文件,会报出错误:文件不存在(No such file or directory)

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf)
	{
		//出错误的原因是什么
		printf("%s\n", strerror(errno));
		return 0;
	}
	//读文件
	//...
	
	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

关于更多的文件操作,可以点击这里>>>

3 拷贝 函数

strcpy

strcpy
实现的功能:拷贝一个字符串。

当被拷贝的字符串中没有\0的时候,打印出来的结果会出现乱码,而带有\0的时候,打印的结果就不会出现乱码。

int main()
{
	//char arr1[] = "abcdef";
	char arr1[] = { 'a','b','c','d', };//出问题了,要有\0
	//char arr1[] = { 'a','b','c','d','\0' };
	char arr2[20] = "xxxxxxxxxxxxxxxx";

	strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}
模拟实现

形参是字符串的地址,是char* 类型,为了防止有人把原地址和目的地址的位置写反,使用const修饰,使它指向的元素不能被更改。

#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest && src);
	while (*dest++ = *src++)
	{
		;
	}
	//while (*src)
	//{
	//	*dest=*src;
	//	dest++;
	//	src++;
	//}
	//*dest = *src;//最后还要把\0拷贝进去
	return ret;
}

strncpy

strncpy
拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加’\0’,直到num个。

例如以下的代码:arr2的长度就不足7个,所以当字符串拷贝到arr1之后,自动在后面补上三个’\0’。

int main()
{
	char arr1[] = "XXXXXXXXXXX";
	char arr2[] = "abcd";
	strncpy(arr1, arr2, 7);//一个不多,一个也不少,如果字符串长度不够,后面自动补上\0
	printf("%s\n", arr1);
	return 0;
}
模拟实现
char* my_strncpy(char* strDest, const char* strSource, size_t count)
{
	assert(strDest && strSource);
	char* tmp = strDest;
	int i = 0;
	for (i = 0; i < count; i++)
	{
		if (*(strSource+i))
		{
			*(strDest+i) = *(strSource+i);
		}
		else
		{
			*(strDest + i) = '\0';
		}

	}
	return tmp;
}
int main()
{
	char arr1[] = "XXXXXXXXXXX";
	char arr2[] = "abcd";
	printf("%s\n", my_strncpy(arr1, arr2,3));
	printf("%s\n", my_strncpy(arr1, arr2,7));
	return 0;
}

memcpy

memcpy
实现功能:在两个数组之间拷贝任意类型的数据。
与strcpy不同的是,strcpy只能拷贝字符串类型的数据,而memcpy可以拷贝任意类型的数据。

void test1()
{
	int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
	memcpy(arr3+2, arr3, 20);//不重叠拷贝
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr3[i]);
	}
}

int main()
{
	test1();
	return 0;
}
模拟实现

为了接收任意类型的数据,形参的类型必须是void※类型,(如果是char※类型,就不能接收int※在内的其他类型)。

将void※转化为char※类型,一次对一个字节进行操作。

void* my_memcpy(void* dest, const void*src, size_t num)
{
	void* ret = dest;
	assert(dest && src);

	//每次拷贝一个字节
	while (num--) 
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}

	return ret;
}

int main()
{
	int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memcpy(arr3+2, arr3, 20);//不重叠拷贝
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr3[i]);
	}
	return 0;
}

memcpy运行的结果是1 2 1 2 3 4 5 8 9 10(结果一),所以我模拟的函数运行的结果也应该是结果一

但是my_memcpy得到的结果是:1 2 1 2 1 2 1 8 9 10(结果二)

这并不是说写的有问题。memcpy能拷贝不重叠的空间就达到要求了。如果发现有重叠拷贝,使用memmove去处理。

vs环境下的memcpy也能实现重叠拷贝,相当于超额完成目标

memmove

memmove
功能更加强大的拷贝函数,
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。

int main ()
{
  char str[] = "memmove can be very useful......";
  memmove (str+20,str+15,11);
  puts (str);
  return 0;
}
模拟实现

在这里插入图片描述

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);
		}
	}
	return ret;
}

4 字符串追加

strcat

strcat
将字符串二,放在字符串一的后面,并返回字符串一的地址。

int main()
{
	char arr1[30] = "hello ";//字符串长度足够大才行
	char arr2[20] = "world";
	strcat(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}
模拟实现

在执行的时候,首先要找到目的字符串的’\0’,从这个位置开始,把字符串二拷贝进来,这个追加到目标空间的过程,可以分为几步:
str指针指向的内容给到Dest指向的内容;
然后两个指针指向的内容往后移动一个位置;
最后str指向了’\0’,还是要把’\0’拷贝进去,至此就完成了字符串的追加。

char* my_strcat(char* Dest, const char* str)
{
	char* ret = Dest;
	assert(Dest && str);
	//1 找到目标空间中的\0
	while (*Dest)
	{
		Dest++;
	}
	//2 追加内容到目标空间
	while (*Dest++ = *str++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[30] = "hello ";//字符串长度足够大才行
	char arr2[20] = "world";
	my_strcat(arr1, arr2);
	printf("%s\n", my_strcat(arr1, arr2));//可以实现链式访问
	return 0;
}

strncat

int main ()
{
 char str1[20];
 char str2[20];
 strcpy (str1,"To be ");
 strcpy (str2,"or not to be");
 strncat (str1, str2, 6);
 puts (str1);
 return 0;
}

5 比较函数

strcmp

strcmp
在这里插入图片描述

int main()
{
	char arr1[] = "abc";
	char arr2[] = "abq";
	int ret = strcmp(arr1, arr2);
	printf("%d\n", ret);
	return 0;
}

比如这个程序对两个字符串进行比较(比较每一位的ASCII码),a和a一样;b和b一样,c和q明显q的大小更大,所以arr<arr2,返回的结果为一个小于0的数。

模拟实现

先对字符串的每一位进行比较(循环),该循环结束有两种可能:
1 遇到了不一样的字符。
2 字符串每一位比较结束了(’\0’结束)

int my_strcmp(const char* string1, const char* string2)
{
	assert(string1 && string2);
	while (*string1 == *string2)
	{
		if (*string1 == '\0')
			return 0;
		string1++;
		string2++;
	}
	if (*string1 > *string2)
		return 1;
	else
		return -1;
	//retrun *string1-*string2//标准 的规定是返回负数 正数就行,并不是+1 -1
}
int main()
{
	char arr1[] = "abc";
	char arr2[] = "abq";
	int ret = my_strcmp(arr1, arr2);
	printf("%d\n", ret);
	return 0;
}

当然标准规定的只是返回大于或小于零的数,并没有要求它的返回一定是+1或者-1!
所以在使用这个函数做判断的时候,一定要注意。

strncmp

strncmp
比较前n相的字符。

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcqqqqqqq";
	int ret=strncmp(arr1, arr2, 3);
	printf("%d\n", ret);

	return 0;
}
模拟实现
int __cdecl strncmp
(
    const char *first,
    const char *last,
    size_t      count
)
{
    size_t x = 0;

    if (!count)
    {
        return 0;
    }
    if( count >= 4 )
    {
        /* unroll by four */
        for (; x < count-4; x+=4)
        {
            first+=4;
            last +=4;

            if (*(first-4) == 0 || *(first-4) != *(last-4))
            {
                return(*(unsigned char *)(first-4) - *(unsigned char *)(last-4));
            }

            if (*(first-3) == 0 || *(first-3) != *(last-3))
            {
                return(*(unsigned char *)(first-3) - *(unsigned char *)(last-3));
            }

            if (*(first-2) == 0 || *(first-2) != *(last-2))
            {
                return(*(unsigned char *)(first-2) - *(unsigned char *)(last-2));
            }

            if (*(first-1) == 0 || *(first-1) != *(last-1))
            {
                return(*(unsigned char *)(first-1) - *(unsigned char *)(last-1));
            }
        }
    }

    /* residual loop */
    for (; x < count; x++)
    {
        if (*first == 0 || *first != *last)
        {
            return(*(unsigned char *)first - *(unsigned char *)last);
        }
        first+=1;
        last+=1;
    }

    return 0;
}

memcmp

对每一个比特数进行比较,当比较哦前9位的话,得到的结果为1
当比较哦前8位的话,得到的结果为0.

int main()
{
	int arr1[] = { 1,2,7,4,5 };
	int arr2[] = { 1,2,3,4,5 };
	int ret = memcmp(arr1, arr2, 9);//最后一个是比较的bit数
	ret = memcmp(arr1, arr2, 8);//小端存储

	printf("%d\n", ret);
	return 0;
}

5 字符串查找函数

strstr

strstr
在字符串中找到一个子字符串。如果这个要子字符串不存在,返回空指针;如果存在,返回找到位置的地址。

int main()
{
	char arr1[] = "abcdefabcdef";
	char arr2[] = "bcd";
	char* ret = strstr(arr1, arr2);
	if (NULL == ret)
	{
		printf("没找到\n");
	}
	else
	{
		printf("%s\n",ret);
	}
	return 0;
}
模拟实现

需要考虑一下细节:
比如子字符串为空,那么我就不需要找了,直接返回;

最简单的一种情况:substr和str每一位相比,当substr找到‘\0’就说明已经在str中找到了substr。

如果str=abbbcdef
sub=bbc,为了能返回原来的位置,最好创建新的指针保存其 当前比较的位置(cur)

#include<assert.h>
char* my_strstr(const char* str, const char* substr)
{
	assert(str, substr);
	const char* s1 = str;
	const char* s2 = substr;
	char* cur = str;

	if (*substr == '\0')
	{
		return (char*)str;
	}
	while (*cur)
	{
		s1 = cur;
		s2 = substr;
		while (*s1!='\0'&& *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
			return (char*)cur;
		cur++;
	}
	return NULL;
}

int main()
{
	char arr1[] = "abcdefabcdef";
	char arr2[] = "bcd";
	char* ret = my_strstr(arr1, arr2);
	if (NULL == ret)
	{
		printf("没找到\n");
	}
	else
	{
		printf("%s\n",ret);
	}
	return 0;
}

strtok

strtok

第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标
记。

strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:
strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
并且可修改。)

strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,
strtok函数将保存它在字符串中的位置 (不妨使用静态变量保存它)
如果字符串中不存在更多的标记,则返回 NULL 指针。

int main()
{
	const char* p = "@.#";
	char arr[] = "zpengwei@yeah.net#hehe";
	char buf[50] = { 0 };// "zpengwei@yeah.net"
	strcpy(buf, arr);

	//这样子的循环写的好啊,巧妙运用了初始化的不同
	char* str = NULL;
	for (str = strtok(buf, p); str != NULL; str=strtok(NULL, p))
	{
		printf("%s\n", str);
	}

	//char* str = strtok(buf, p);//zpengwei
	//printf("%s\n", str);
	//str = strtok(NULL, p);//yeah
	//printf("%s\n", str);
	//str = strtok(NULL, p);//net
	//printf("%s\n", str);
	//strtok - 开始返回NULL

	return 0;
}

函数找第一个标记的时候,函数的第一个参数不是NULL
函数找第二个标记的时候,函数的第一个参数是NULL

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值