【库函数的奇妙冒险:从内存到字符串】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

这篇博客主要介绍了一些与“内存”和“字符串”相关的函数,给出部分函数的简单模拟实现的代码详解,同时对易混函数进行识别和解析


一、与“拷贝”相关的3个强关联函数

其实还有一个叫做“strncpy”的函数,功能与strcpy非常相似;
但是它和strcpy相比要多了一个参数,这个参数是用来控制想要拷贝多少个字符的,也就是说只要把循环的部分控制一下就可以写出模拟实现的函数了
strcpy对应的strncpystrcmp对应的strncmp,还有strcat对应的strncat等函数也是也是在原来的函数的基础上多了一个可控的条件,这里就不赘述了

1.strcpy

先引用一下msdn中的注释:

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

需要重点阅读的部分就是函数类型,参数,功能还有返回值
从功能部分了解到:
strcpy会从strSourse这个位置的东西一个接一个的拷贝到strDestination这个位置上,直到拷贝完’\0’为止
但是这个函数本是是没有检测功能,也就是说如果拷贝的目标位置以后没有足够的空间的话,输出结果是不确定的,这点用的时候注意一下就可以了
下面是模拟代码:

char* my_strcpy(char* des, const char* src)
{
	char* ret = des;
	while (*des++ = *src++)//*src为'\0'时循环终止
	{
		;
	}
    return ret;
}

int main()
{
	char arr1[] = "abcd";
	char arr2[20] = { 'a','b','c','d' };
	my_strcpy(arr2+4, arr1);
	printf("%s", arr2);
	return 0;
}

输出结果
在这里插入图片描述


2.memcpy

这个函数的功能和strcpy很像啊,但是这个函数是针对内存操作的
通过msdn可以了解到这个函数是这样声明的:
在这里插入图片描述
2个void*类型的参数可以让函数里可以放置任意类型的参数,第3个参数之所以要用size_t类型声明是因为要确保函数的可移植性和高效性,这个参数直接当int使用就可以

先看一下模拟实现的代码:

#include <stdio.h>
void* my_memcpy(void* dest, void* src, size_t num)//为保证代码的可移植性和效率,
采用size_t,以及当前的循环写法
{
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}

由于使用的时*void类型的变量,所以必须强制类型转换之后才能进行“指针加减整数”的操作
如果还是使用上面my_strcpy的例子你会发现两次运行的结果是一样的

int main()
{
	char arr1[] = "abcd";
	char arr2[20] = { 'a','b','c','d' };
	//my_strcpy(arr2+4, arr1);
	my_memcpy(arr2 + 4, arr1,4);
	printf("%s", arr2);//结果依然是abcdabcd
	return 0;
}

所以换一个例子:

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

既然它是一个内存操作函数,这次我们先不打印了,下面是内存窗口中arr1的变化
不太清楚调试操作的读者可以参考一下我的上一篇博客: 传送门
my_memcpy运行之前:
在这里插入图片描述
运行之后
在这里插入图片描述
整形的copy和字符的copy可不是一回事
虽然你把上面的语句my_memcpy(arr1, arr1 + 3, 4);换成my_memcpy(arr1, arr1 + 3, 1),打印的话结果还是423456789,
但是在拷贝更大的数字时就可能会发生错误了


3.memmove

memmove这个函数功能上和memcpy几乎一致,但是它可以用来解决一些memcpy解决不了的问题,memcpy的注释中也提到了这一点:
在这里插入图片描述
最后一段文字我找不到合适的翻译,所以先看一段代码:

void* my_memcpy(void* dest, void* src, size_t num)
{
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}
int main()
{
	char arr1[20] = "Iloveyou";
	my_memcpy(arr1+2, arr1, 3);
	printf("%s", arr1);
	return 0;
}

这里我们想要把arr1[0]到arr[2]的内容依次拷贝到arr[2]到arr[4]里面,我们“脑杀”的结果是

"IlIloyou"

而实际上的输出结果却是
在这里插入图片描述
原因在于从前面拷贝的源头区块后面拷贝的目的地区块发生了重叠,我们模拟的函数又恰好是从前向后拷贝的,字符“o”在第一次循环里就被覆盖了
但不必担心,这个函数模拟的很好,这种情况下memcpy的输出结果是未定义的
所以memmove站出来解决了这个问题:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void* my_memmove(void* dest, void* src, size_t num)
{
	void* ret = dest;
	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;
}
int main()
{
	char arr1[20] = "Iloveyou";
	my_memmove(arr1 + 2, arr1, 3);
	printf("%s", arr1);
	return 0;
}

输出结果:
在这里插入图片描述
为了避免将本应拷贝过去的字符被覆盖,上面的my_memmove函数内部包含了两种拷贝方式(拷贝顺序):
在这里插入图片描述
(1)当蓝色部分为拷贝的源头区块,红色部分为拷贝的目标区块时:
先把字符“e”拷贝到字符“o”的位置,从后向前拷贝才能避免拷贝数据的丢失
(2)当蓝色部分为拷贝的源头区块,红色部分为拷贝的目标区块时:
先把字符“I”拷贝到字符“L”的位置,从前向后拷贝才能避免拷贝数据的丢失


二、其他弱关联函数

1.strcmp

参数:
在这里插入图片描述
注释:
在这里插入图片描述返回值:
在这里插入图片描述
这个函数的功能是从头开始对两个字符串通过ASCII码值进行比较,返回值以两个字符串相同位置上第一个不同字符的ASCII码值之差决定
演示代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int my_strcmp(const char* s1, const char* s2)
{
	while (*s1 == *s2)
	{
		if (*s1 == '\0')
		{
			return 0;
		}
		s1++;
		s2++;
	}
	if (*s1 > *s2)
		return 1;
	else
		return -1;
}
int main()
{
	char arr1[] = "abcdefg";
	char arr2[] = "abcdezr";
	printf("%d", my_strcmp(arr1, arr2));
	return 0;
}

由于f的ASCII码值小于z,所以返回值为-1:
在这里插入图片描述
当然也存在用于比较内存的函数memcmp,这一对函数类比strcpy和memmove就可以了


2.strcat

函数类型和参数:
在这里插入图片描述
这个函数的功能是把从strSourse开始,直到第一个\0(包括这个\0)结束的字符全部挪到
strDestination后面的第一个\0那里,这个\0会被覆盖,而粘贴过去的内容会保留\0
有几个限制条件需要记一下:
1:源字符串必须以\0结束
2:目标可以修改,且拥有足够大的空间
下面是模拟代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
char* my_strcat(char* dest, const char*src)
{
	
	char* cur = dest;
	while (*cur)
	{
		cur++;
    }
	while (*cur++ = *src++)
	{
		;
	}

	return dest;
}

int main()
{
	char arr1[20] = "m ";
	char arr2[] = "tired";
	printf("%s\n", my_strcat(arr1, arr2));
	return 0;
}

最后的输出结果是m tired
注意这个函数是不可以自己给自己追加字符的,否则会造成死循环


3.strerror

这个函数是用来“翻译”错误码的,这里只演示功能,由于逻辑相对简单,加上数据量太大,就不写模拟函数了

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
`int main()
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s\n", strerror(i));
	}
	printf("%s", strerror(404));
	//输出结果:
	//No error
	//Operation not permitted
	//No such file or directory
	//No such process
	//Interrupted function call
	//Unknown error,看来404和它们不是一个系列的
}

后记

上面的函数也没必要都记下来,做题时要用到的函数其实也不多,但就我个人感受而言(只是初学者的有感而发),在遇见一些存在“不定长输入”的题目中,想要求字符数组元素个数的时候,如果不考虑那一丁点内存占用,strlen要比sizeof更好一些
原因就在于strlen运行的时候读取到‘\0’就停止计数了;
而sizeof计算的是“内容创建时占用的内存大小”,先看一下代码吧

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "abcd";
	char arr2[] = { 'a','b','c','d' };
	printf("%d\n", strlen(arr1));
	printf("%d\n", strlen(arr2));
	printf("%d\n", sizeof(arr1));
	printf("%d\n", sizeof(arr2));
	return 0;
}

运行结果:在这里插入图片描述
毕竟多组输入的时候还是要用\0来终止循环
所以这就导致用sizeof计算出来的结果实际要比strlen计算出来的结果多1,
如果忘记减1的话一定要记得减回来,如果还是出现莫名其妙的报错的话可以试一试strlen

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alexanderite

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值