IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言进阶——字符串函数&&内存函数 -> 正文阅读

[C++知识库]C语言进阶——字符串函数&&内存函数

🌇🌆🌃本文已收录至:C语言——梦想系列_Yohifo的博客-CSDN博客

更多知识尽在此专栏中!

目录

🌇前言

🌇正文

🌆字符串函数

🌉长度不可控的字符串函数

🌃strlen 长度统计

🌃strcpy 拷贝

🌃strcmp 比较

🌃strcat 追加

🌉长度可控的字符串操作函数

🌃strncpy 可控拷贝

🌃strncmp 可控比较

🌃strncat 可控追加

🌉特殊字符串函数

🌃strstr 寻找

🌃strtok 分割

🌃strerror 报错?

🌆字符分类函数

🌃isdigit 十进制判断

🌃isxdigit 十六进制判断

🌃isupper 大写判断

🌃islower 小写判断

🌃toupper 转为大写

🌃tolower 转为小写

🌆内存函数

🌃memcpy 拷贝

🌃memmove 移动

🌃memcmp 比较

🌃memset 设置?

🌇总结


🌇前言

题目描述及其要求

? 这是牛客网上的一道简单题:判断输入字符是否为字母,一般的解决方法是通过ASCII码判断,不过这样做的话判断表达式较长,此时我们可以利用C语言中的库函数isalpha(判断是否为字母)?来完成这个题目,不仅代码量少,而且通俗易懂。要实现这种效果,就需要学习C语言中的各种库函数,而本文会列出大多数字符串函数和内存函数的使用及其实现,如果你想学习C语言库函数或对字符串、内存有好奇之心,不妨仔细来看看吧!🎉🎉🎉

此文章分为三部分:字符串函数、 字符分类函数、内存函数。

通过库函数简化后的代码

🌇正文

? 首先我们从字符串函数开始介绍,顾名思义,字符串函数就是为字符串设计的函数,我们比较熟悉的有字符串长度统计函数 strlen、字符串比较函数 strcmp,除此之外还有很多实用的字符串函数,比如字符串追加、字符串分割、字符串寻找等等,话不多说,让我们直接进入主题:

🌆字符串函数

🌉长度不可控的字符串函数

下面介绍的是对目标字符串操作长度不可控的函数,使用场景相对有限。

🌃strlen 长度统计

? strlen 是用来统计字符串长度的一个库函数,因为字符串有个重要特征:以'\0'作为结束标志。strlen 正是根据这一特点,从首地址开始,逐个比对统计,得出此字符串的长度。当然如果没有结束标记,strlen 就会一直往后找,得出一个随机值。

这是 strlen 的标准格式
strlen 计算字符串长度

?

使用注意事项:

  • strlen 统计的是 \0 之前出现的字符个数
  • 使用时,必须包含结束标志
  • 返回值是 size_t (unsigned int 无符号整型)

模拟实现 strlen

? 这个函数的工作原理我们已经清楚了,可以试着模仿库函数的方式,写出一个属于自己的 strlen。

? 既然是模仿库函数,那么在返回类型、参数类型等方面要和库函数一致,在统计长度前,我们可以使用一个临时指针记录起始位置,当我们的源指针指向结束标志时,循环结束,将两个指针相减,就能得到元素个数(关于指针 - 指针得到元素个数)?,也就是字符串长度。下面来看看具体代码实现吧:

//strlen 计算字符串长度
size_t myStrlen(const char* p)
{
	assert(p);//断言,防止空指针
	char* tmp = p;//记录起始位置
	while (*p)
	{
		p++;//在循环内指向+1操作,避免位置出错
	}
	return (size_t)(p - tmp);//指针 - 指针得到元素个数
}
int main()
{
	char* pa = "Hello World!";
	//size_t len = strlen(pa);
	//printf("库函数实现结果:\n%zu\n", len);
	size_t len = myStrlen(pa);//此时调用我们写的函数
	printf("模拟函数实现结果:\n%zu\n", len);
	return 0;
}

? 同样的,我们使用之前的示例来验证此函数的可行性?,可以看到结果与库函数一致。

🌃strcpy 拷贝

? 字符串拷贝需要两个字符串,字符数组?dest(目标) 与字符串 src(源),strcpy 中只需要这两个参数,即把源字符串内容拷贝到目标字符数组中(源字符串中的结束标志也会拷贝),其中要确保字符数组? dest 可改变,数组空间也要足够大,不然装不下源字符串就尴尬了。

strcpy 标准格式

使用注意事项:

  • 源字符串中必须包含 \0
  • 源字符串中的 \0 会拷贝到目标字符数组中
  • 目标空间必须足够大,能够装下源字符串
  • 目标空间必须是可修改的?

模拟实现 strcpy?

? 同样的,我们可以对这个函数进行模拟实现,拷贝的本质就是赋值,当源字符串中的首元素拷贝到目标字符数组中后仍然位于首位置,也就是说两个字符串元素拷贝位置是同步的,既然源字符串中的结束标志也要拷贝过去,那么我们就可以将其和赋值写进一个循环判断条件中(这样会构成一段非常奇妙的代码),这样一来我们整个程序的可读性就很不错了。

//strcpy 字符串拷贝
char* myStrcpy(char* dest, const char* src)
{
	assert(dest && src);//断言
	char* tmp = dest;//记录起始位置
	//当*src 为结束标志并赋给 *dest时,整体为假,
	//循环终止,目标数组也拿到了结束标志
	while (*dest++ = *src++)
	{
		;//空语句
	}
	return tmp;//返回起始地址
}
int main()
{
	char arr1[20] = "xxxxxxxxx";
	char arr2[] = "Hello!";
	//printf("库函数实现结果:\n%s\n", strcpy(arr1, arr2));
	printf("模拟函数实现结果:\n%s\n", myStrcpy(arr1, arr2));
	return 0;
}

? 当然,我们的模拟函数也能实现需求?

🌃strcmp 比较

? 字符串比较函数是从首字符开始比较,通过字符对应的ASCII码值对比,就能知道大小,如果str1>str2,返回1,如果str1<str2,返回-1,如果两个字符串相等,则返回0。因为比较并不需要改变值,所以使用常量字符串也能比较。

strcmp 标准格式
strcmp 的返回值

使用注意事项:

  • 字符串大小比较时,与长度无关
  • 从首字符开始,逐字符比较
  • 通过字符对应的ASCII码值做对比

模拟实现 strcmp?

? 我们可以通过指针的移动来模拟实现这个函数,即指向 str1 的指针 dest、指向 str2 的指针 src,对两个指针解引用后的值进行比较,如果相同就同时向后偏移,直到找到不同的值或移动到结束标志处停止,最后再分情况确定返回值就行了。

//strcmp 字符串比较
int myStrcmp(const char* dest, const char* src)
{
	assert(dest && src);//断言
	//当找到不同数或移动到\0处时,循环停止
	while (*dest == *src && (*dest || *src))
	{
		dest++;//没有找到不同的元素
		src++;//需要向后偏移
	}
	//分情况判断,确定返回值
	if (*dest - *src > 0)
		return 1;
	else if (*dest - *src < 0)
		return -1;
	else
		return 0;
}
int main()
{
	char* str1 = "BATZ";
	char* str2 = "BAT";
	printf("库函数实现结果:\n%d\n", strcmp(str1, str2));
	//printf("模拟实现函数结果:\n%d\n", myStrcmp(str1, str2));
	return 0;
}

? 使用模拟函数通过测试用例:?

🌃strcat 追加

? 追加,就是在目标字符数组的末尾(\0处)添加源字符串的值,比如目标字符串数组中为abcd,源字符串为1234,经过追加后,字符数组就变为了abcd1234。值得一提的是,strcat?无法自己给自己追加,因为在追加过程中,目标字符数组结束标志会被覆盖掉,导致源字符串(其实就是目标字符,因为是自己给自己追加)中的结束标志也消失了,追加过程会无法停止。

strcat 标准格式

使用注意事项:

  • 源字符串和目标字符数组中都必须有\0
  • 目标空间必须足够大
  • 目标空间必须可修改,所以是字符数组?

模拟实现 strcat

? 既然是在目标字符数组的末尾处追加字符,就需要把指向首地址处的指针 dest 移向尾地址,当然在移动前要保存此地址,将此时的尾地址看作首地址2,将源字符串中的元素从此处开始拷贝至目标字符数组中,这样就完成了追加的操作,最后再返回之前记录的首地址就行了。

//strcat 字符串追加
char* myStrcat(char* dest, const char* src)
{
	assert(dest && src);//断言
	char* tmp = dest;//记录目标字符数组首地址
	while (*dest)
	{
		dest++;//将指针dest移动至尾元素处
	}
	//类似 strcpy 拷贝操作
	while (*src)
	{
		//判断条件用 *src就行了
		*dest++ = *src++;//确保源字符串中的每个元素都能追加上
	}
	return tmp;//返回首地址
}
int main()
{
	char arr1[20] = "ABCD";
	char arr2[] = "1234";
	//printf("库函数实现结果:\n%s\n", strcat(arr1,arr2));
	printf("模拟函数实现结果:\n%s\n", myStrcat(arr1, arr2));
	return 0;
}

🌉长度可控的字符串操作函数

下面开始介绍字符串操作函数的升级版,这些函数使用起来更加自由方便。

🌃strncpy 可控拷贝

? 相比于前面的?strcpy,strncpy?多了一个字母n,n?的含义是长度可控,即在传递参数时,需要一个额外变量控制拷贝的字节数,相较于前面的固定拷贝,这里的可控拷贝更为灵活,且如果选定的n字节中没有包含结束标志\0,则在拷贝结束后,strncpy?会自动添加一个\0。

strncpy 标准格式

使用注意事项:

  • 目标空间必须足够大
  • 目标空间必须可修改
  • 源字符串中不一定要有\0(函数会自己添加)

模拟实现 strncpy

? 这个模拟实现也比较简单,注意两点就行了,循环终止条件标为 k,在循环结束后,还要对 *dest 再一次进行赋值,即 \0 。这样一来就能和 strncpy 一样了。

//strncpy n个字符串拷贝
char* myStrncpy(char* dest, const char* src, size_t k)
{
	assert(dest && src);//断言
	char* tmp = dest;//记录起始位置
	//循环k次,确保拷贝足够的字节数
	while (k--)
	{
		*dest++ = *src++;//类似拷贝的操作
	}
	*dest = '\0';//再次给目标字符数组中的元素赋值
	return tmp;//返回起始地址
}
int main()
{
	char arr1[20] = "xxxxxxxx";
	char arr2[] = "Hello World!";
	//printf("库函数实现结果:\n%s\n", strncpy(arr1, arr2, 5));
	printf("模拟函数实现结果:\n%s\n", myStrncpy(arr1, arr2, 5));
	return 0;
}

🌃strncmp 可控比较

? 同样的,strncmp?也能控制比较长度,当然控制长度不能超过源字符串的长度,不然是无意义的

strncmp 标准格式
返回值与 strcmp 完全一致

使用注意事项:

  • 与 strcmp 基本一致
  • 控制比较字节数不能为负数?

模拟实现 strncmp

? 这个模拟实现也比较简单,大体思路与 strcmp 的模拟一样,只是循环判断条件变为了 k 和?*dest (当对比到结束标志处,循环也会停止)。

//strncmp n个字符串比较
int myStrncmp(const char* dest, const char* src, size_t k)
{
	assert(dest && src);//断言
	//为何使用前置--?
	//因为这样能有效避免多判断一次的情况
	while (--k && *dest == *src)
	{
		dest++;//确保每位都能对比到
		src++;
	}
	//分情况返回
	if (*dest - *src > 0)
		return 1;
	else if (*dest - *src < 0)
		return -1;
	else
		return 0;
}
int main()
{
	char* str1 = "BATZ";
	char* str2 = "BAT";
	//printf("库函数实现结果:\n%d\n", strncmp(str1, str2, 3));
	printf("模拟函数实现结果:\n%d\n", myStrncmp(str1, str2, 3));
	return 0;
}

🌃strncat 可控追加

? 可控追加,旨在控制源字符串中字符追加数,比如目标字符数组为abcd,源字符串为1234,我们传递字节数为2,当追加结束后,目标字符数组变为abcd12,同所有可控家族成员一样,strncat?也会自动添加结束标志?\0。因此利用?strncat 自己给自己追加,能够很好的完成任务,避免结束标志吞噬问题。

strncat 标准格式

使用注意事项:

  • 目标字符数组中必须有\0
  • 目标空间必须足够大
  • 目标空间必须可修改
  • 源字符串中可以不包含\0

模拟实现 strncat

? 代码大体与模拟 strcat 的一致,只不过有两个地方需要注意:1.循环判断条件 2.最后 \0 的添加

//strncat n个字符串追加
char* myStrncat(char* dest, const char* src, size_t k)
{
	assert(dest && src);//断言
	char* tmp = dest;//记录起始位置
	//使指针移向目标字符数组的末尾处
	while (*dest)
	{
		dest++;
	}
	//判断条件为k,即传入的控制字节数
	while (k--)
	{
		*dest++ = *src++;//确保每个元素都能追加
	}
	return tmp;//返回目标字符数组的起始地址
}
int main()
{
	char arr1[20] = "xxxxxxx";
	char arr2[] = "Hello World!";
	//printf("库函数实现结果:\n%s\n", strncat(arr1, arr2, 5));
	printf("模拟函数实现结果:\n%s\n", myStrncat(arr1, arr2, 5));
	return 0;
}

🌉特殊字符串函数

🌃strstr 寻找

? 字符串寻找函数,作用是在目标字符串中寻找是否出现过源字符串,如果出现则返回第一次其在目标字符串中第一次出现的地址,如果没有出现,则返回一个空指针。

ststr 标准格式

使用注意事项:

  • 只要传入的字符串地址就行了
  • 这个函数没有什么需要特别注意的事项

模拟实现 strstr

? 这个函数实现起来就比较复杂了,需要用到多个指针,不断记录位置、移动位置、刷新位置,当然我们这里模拟实现的是效率比较低的算法,如果想要追求时间,可以参考参考KMP算法,?提高寻找效率。

//strstr 字符串寻找
char* myStrstr(const char* dest, const char* src)
{
	assert(dest && src);//断言
	//特殊情况之一,如果源字符串为空,则返回目标字符串
	if (!*src)
		return (char*)dest;//强制类型转换
	const char* str1 = dest;//使用替身指针
	const char* str2 = src;//避免影响源字符串和目标字符串
	char* p = (char*)dest;//记录字符串、判断字符串指针
	while (*p)
	{
		char* s1 = (char*)str1;//比较部分的指针
		char* s2 = (char*)str2;
		//这是主要判断部分,当两个字符相等,并且不为\0
		//才有继续往后走的资格
		while (*s1 && *s2 && *s2 == *s1)
		{
			s1++;//往后移动,逐字比较
			s2++;
		}
		//如果是因为子串的\0结束的,就找到了目标字符串
		if (*s2 == '\0')
			return p;//直接返回记录指针即可
		str1++;//此时没找到,目标字符串指针需要向后移动
		p = (char*)str1;//记录指针矫正
	}
	return NULL;//如果走到这一步,说明没找到,返回空指针
}
int main()
{
	char* str1 = "abcdef";
	char* str2 = "cdef";
	//printf("库函数实现结果:\n%s\n", strstr(str1, str2));
	printf("模拟函数实现结果:\n%s\n", myStrstr(str1, str2));
	return 0;
}

🌃strtok 分割

? 字符串分割函数,顾名思义就是对字符串进行分割操作,比如字符串abcd&1234,我们把&视为分隔符,再把字符串首地址和分隔符传给 strtok 函数,就能分别得到字符串 abcd 和字符串 1234 的首地址。字符串分割函数有个值得注意的点:如果字符串中有多个分隔符,在第一次分割时传入的是首字符地址,第二次及后续分割需要传递一个空指针,因为 strtok 有记忆功能,当第一次分割结束后,它会记录下此地址,为下次分割做准备,因此需要传递有一个空指针。当然如果想要分割其他字符串,只需传递其他字符串的首地址就行了,此时记忆块会刷新。

strtok 标准格式

使用注意事项:

  • 目标字符数组中要包含结束标志
  • 第一次传递的是字符串首地址
  • 如果针对同一个字符串,第二次需要传递一个空指针?
  • 在使用此函数前,一般会创建一个临时变量存储目标数组值,避免分割对目标字符串造成影响

//strtok 字符串分割
int main()
{
	char str1[] = "12345678@gmail.com";
	char* str2 = "@.";
	char buf[100] = { 0 };
	strcpy(buf, str1);
	char* p;
	for (p = strtok(buf, str2); p; )
	{
		printf("%s\n", p);
		p = strtok(NULL, str2);
	}
	return 0;
}

🌃strerror 报错?

? 这个特殊字符串函数需要配合错误码使用;错误码:是指包含各种错误信息的数字代码,比如数字0表示没有错误,经过博主测试,C语言中有大约141个错误码。如果直接将错误码放入 strerror 中并打印,会出现相应的错误信息;当然,C语言中有一个专门的函数记录错误码,即 errno,需要引出头文件 errno.h,当程序运行出错后,errno 会获取当前的错误码,这样一来,strerror(errno) 搭配就能很好的打印出错误信息。?

strerror 标准格式
errno 标准格式

使用注意事项:

  • strerror 中的参数必须是整型,字符型会按ASCII码处理
  • error 在使用时需要包含头文件 errno.h
//strerror 字符串报错
#include<errno.h>
int main()
{
	//打开不存在的文件,使程序报错
	FILE* pf = fopen("test.txt", "r");
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return -1;
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

🌆字符分类函数

? 这部分的字符串函数相对简单,无非就是通过ASCII码判断,不过前人已经打包成函数了,我们只需要调用 ctype.h 这个头文件就能直接使用。

🌃isdigit 十进制判断

? 如果成立,返回1,否则返回0。

//isdigit 判断十进制
#include<ctype.h>
int main()
{
	if (isdigit('8'))
		printf("字符8是十进制数字");
	else
		printf("字符8不是十进制数字");
	return 0;
}

🌃isxdigit 十六进制判断

? 如果成立,返回1,否则返回0。

//isxdigit 判断十六进制
#include<ctype.h>
int main()
{
	if (isxdigit('f'))
		printf("字符f是十六进制数字");
	else
		printf("字符f不是十六进制数字");
	return 0;
}

🌃isupper 大写判断

? 如果成立,返回1,否则返回0。

//isupper 判断大写
#include<ctype.h>
int main()
{
	if (isupper('f'))
		printf("字符f大写字母");
	else
		printf("字符f不是大写字母");
	return 0;
}

🌃islower 小写判断

? 如果成立,返回1,否则返回0。

//islower 判断小写
#include<ctype.h>
int main()
{
	if (islower('f'))
		printf("字符f是小写字母");
	else
		printf("字符f不是小写字母");
	return 0;
}

🌃toupper 转为大写

? 返回类型为整型,对应ASCII码值

//toupper 小写转大写
#include<ctype.h>
int main()
{
	char c = 'a';
	printf("%c\n", toupper(c));
	return 0;
}

🌃tolower 转为小写

? 返回类型为整型,对应ASCII码值

//tolower 大写转小写
#include<ctype.h>
int main()
{
	char c = 'Z';
	printf("%c\n", tolower(c));
	return 0;
}

? 因为这几个函数都比较简单,所以就没有详细讲解,其实不用这些函数,我们自己也能写出解法,不过会麻烦一些,比如开头提到的那题,下面给大家做了一个汇总表格,让大家看看有哪些现成可用的库函数:

函数当条件满足时(即所传递参数符合条件时)返回真
iscntrl任何控制字符
isspace空白字符,比如空格、换页、换行、回车、制表符等
isdigit十进制数字 0~9
isxdigit十六进制数字 0~f (或0~F都行)
islower小写字母 a~z
isupper大写字母 A~Z
isalpha任意字母,即 a~z 或 A~Z
isalnum字母或数字,即 a~z 、A~Z 或 0~9
ispunct标点符号,即不属于数字或字母的圆形字符
isgraph任何图形字符
isprint任何可打印的字符,包括图形字符和空白字符
toupper、tolower除ASCII码为0外的任何字符

🌆内存函数

? 内存操作函数比较高端,它们更像是不可控字符串函数的Pro版,因为内存函数的操作对象是所有类型,而字符串函数只是面向字符串设计的,话不多说,让我们一起看看内存函数。

🌃memcpy 拷贝

? 相当于全能版的?strcpy

memcpy 标准格式

使用注意事项:

  • 目标空间必须足够大
  • 目标空间必须可修改
  • 传入的字节数需要慎重考虑,这里推荐 sizeof(类型)*想传入的元素数

模拟实现 memcpy

?跟当时我们模拟实现 strncpy 一样,需要把字节控制数 num 作为判断依据,不过因为这是全能型的拷贝函数,需要强制类型转化为 char* 型再解引用赋值,确保所有数据都能进行拷贝,同样在移动时也需要进行强制类型转化,与 strncpy 不同的是它不会自动补\0,这样就对字符串不太友好了,所以如果是专门处理字符串,还是使用字符串函数比较合适。

?值得一提的是,当我们自己给自己拷贝,并且拷贝空间与目标空间重叠时,自己设计的函数会出问题,会有值被覆盖掉,因为它默认从前往后拷贝(会产生覆盖现象)。但是微软在设计?memcpy 时留了一手,就是库函数 memcpy 也能完成这个任务,原因很简单,他们在设计时考虑到了这个问题,所以在函数执行前加了个判断,以决定是从前往后拷贝,还是从后往前拷贝。其实这个任务应该交给 memmove 完成的,但是 memcpy 的设计者实现了类 memmove 的操作,所以说库函数中的 memcpy 是个满分拷贝。至于如何实现这个功能,下面会介绍到。

//memcpy
void* myMemcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src);//断言
	void* tmp = dest;//记录
	//判断依据同样是传入的控制字节数
	while (num--)
	{
		*(char*)dest = *(char*)src;//需要进行强制类型转化
		((char*)dest)++;//转化后移动,确保步长为1字节
		((char*)src)++;
	}
	return tmp;//返回目标空间地址
}
int main()
{
	int arr1[20] = { 0 };
	int arr2[] = { 1,2,3,4,5 };
    //memcpy(arr1, arr2,sizeof(int)*5);
	myMemcpy(arr1,arr2,sizeof(int)*5);//传入时推荐这种写法
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr1[i]);
	}

	char arr3[20] = "xxxxxxxxx";
	char arr4[] = "Hello World";
	//printf("%s\n", (char*)memcpy(arr3, arr4, sizeof(char) * 5));//经测试,不会自己添加\0
	//printf("%s\n", (char*)myMemcpy(arr4+6, arr4, sizeof(char) * 5));

	char arr5[] = "abcdefg123456";
	//printf("%s\n", (char*)memcpy(arr5 + 3, arr5, sizeof(char) * 5));//测试自己拷贝自己时
	//printf("%s\n", (char*)myMemcpy(arr5 + 3, arr5, sizeof(char) * 5));//出现重叠的情况
	return 0;
}

🌃memmove 移动

? 内存移动函数,移动可以看作拷贝,memmove 包含 memcpy,能实现更多操作,可以这样比喻,memcpy 是男人,memmove 是帅气的男人。微软在VS中重写了 memcpy,使得 memcpy 也能实现?memmove 的功能,使其变成了一个满分拷贝。

memmove 标准格式

使用注意事项:

  • 目标空间必须足够大
  • 目标空间必须可修改
  • 传入字节数要慎重考虑

模拟实现 memmove

? ?memmove 是在 memcpy 的基础进行改进的,两者在设计之初就分工明确:memcpy 实现不重叠的拷贝,memmove 实现的是有重叠的拷贝,VS中的 memcpy 做了升级,也可以实现有重叠的拷贝。其实要实现这一点也不难,只需要在拷贝前判断是需要从前往后还是从后往前进行拷贝就好了,判断依据为目标字符地址与源字符地址的大小。

//memmove
void* myMemmove(void* dest, const void* src, size_t num)
{
	assert(dest && src);//断言
	void* tmp = dest;//记录
	//情况1,目标地址在源地址之前
	//此时执行的就是memcpy的操作
	//从前往后拷贝就行了
	if (dest < src)
	{
		while (num--)
		{
			//从前往后赋值
			*(char*)dest = *(char*)src;
			((char*)dest)++;
			((char*)src)++;
		}
	}
	//情况2,需要从后往前拷贝
	//此时可以把num当作偏移量
	else
	{
		while (num--)
		{
			//从后往前赋值
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return tmp;
}
int main()
{
	int arr1[20] = { 0 };
	int arr2[10] = { 6,7,8,9,10 };
	memmove(arr1, arr2, sizeof(int) * 5);
	//myMemmove(arr1, arr2, sizeof(int) * 5);
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr1[i]);
	}
	char arr3[] = "abcdefg123456";
 	//printf("%s\n", (char*)memmove(arr3 + 3, arr5, sizeof(char) * 5));//模拟自己拷贝自己且重叠
	//printf("%s\n", (char*)myMemmove(arr3 + 3, arr5, sizeof(char) * 5));//同上,测试模拟函数
	return 0;
}

? 图解从前往后拷贝与从后往前拷贝的区别:

注:为了简化讲解,使用的是另一个示例

🌃memcmp 比较

? 内存比较函数,有点像 strncmp 的升级版,为了适用于所有数据,我们在接收参数时会使用空指针进行接收,比较时会转化为字符型指针进行解引用比较,确保每位都能对比到,返回值和 strncmp 一样。

memcmp 标准格式
memcmp 返回值及其意义

使用注意事项:

  • 传递参数时,要传地址(指针)
  • 返回参数类型为整型
  • 传入字节数要慎重考虑?

模拟实现 memcmp

? 模拟实现的话也比较简单,抓住两个点就行了,1.判断条件中要包含 num 2.判断时要逐字节进行判断,即强制类型转换为 char* 后解引用。

//memcmp
int myMemcmp(const void* dest, const void* src, size_t num)
{
	assert(dest && src);
	if (!*(char*)src && *(char*)dest)
		return 1;//当源空间为空,且目标空间不为空时
	if (!*(char*)src && !*(char*)dest)
		return 0;//当源空间与目标空间都为空时,避免无效循环
	//类似于 strncmp 的判断条件
	while (--num && *(char*)dest == *(char*)src)
	{
		((char*)dest)++;//逐字符比较
		((char*)src)++;
	}
	//分流判断返回
	if (*(char*)dest > *(char*)src)
		return 1;
	else if (*(char*)dest < *(char*)src)
		return -1;
	else
		return 0;
}
int main()
{
	char* str1 = "BBAZ";
	char* str2 = "BBA";
	//printf("%d\n", memcmp(str1, str2, sizeof(char) * 2));
	printf("%d\n", myMemcmp(str1, str2, sizeof(char) * 2));
	return 0;
}

🌃memset 设置?

? 内存设置函数,顾名思义就是对数据在内存中的存储值做修改,memset 可用于某些数据的初始化,当然内存设置这个函数也适用于所有类型的数据,因为这个函数实现起来也比较简单,无非就是逐字节进行修改,类似于 memcpy 吧,不过源字符串为传入的固定值,因此这个函数我们就进行模拟实现了。

memset 标准格式

使用注意事项:

  • 参数1要为指针,如果不是指针类型,就传入地址
  • 参数2为整型,代表在内存中设置后的具体值
  • 参数3需要慎重考虑,不能超过原数据的大小?


🌇总结

? 本次字符串函数&&内存函数的学习就到此为止了,回顾全文,我们从字符串类型操作开始,延伸到全数据类型的操作,中间还介绍了几个字符分类函数,这些库函数就像一件件实用的工具,是前人智慧的结晶,所以我们不仅要会用,还要用好,并将它们运用到代码中。知其然,并知其所以然,对这些库函数的模拟实现能让我们更为全面的了解实现原理,并避免犯错。 🎊🎊🎊

?如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!

?如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正。

相关文章推荐

C语言进阶——数据在内存中的存储_Yohifo的博客-CSDN博客?

C语言初阶——实用调试技巧_Yohifo的博客-CSDN博客

【算法】KMP算法——解决字符串匹配问题_榶曲的博客-CSDN博客

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-21 00:08:17  更:2022-09-21 00:09:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 13:17:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码