C语言进阶——字符串&&内存函数

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

更多知识尽在此专栏中!

目录

前言

正文

字符串函数

长度不可控的字符串函数

strlen 长度统计

strcpy 拷贝

strcmp 比较

strcat 追加

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

strncpy 可控拷贝

strncmp 可控比较

strncat 可控追加

特殊字符串函数

strstr 寻找

strtok 分割

strerror 报错 

字符分类函数

isdigit 十进制判断

isxdigit 十六进制判断

isupper 大写判断

islower 小写判断

toupper 转为大写

tolower 转为小写

内存函数

memcpy 拷贝

memmove 移动

memcmp 比较

memset 设置 

总结


前言

C语言进阶——字符串&&内存函数_第1张图片 题目描述及其要求

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

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

C语言进阶——字符串&&内存函数_第2张图片 通过库函数简化后的代码

正文

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

C语言进阶——字符串&&内存函数_第3张图片

字符串函数

长度不可控的字符串函数

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

strlen 长度统计

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

这是 strlen 的标准格式
C语言进阶——字符串&&内存函数_第4张图片 strlen 计算字符串长度

 

使用注意事项:

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

C语言进阶——字符串&&内存函数_第5张图片

模拟实现 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;
}

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

C语言进阶——字符串&&内存函数_第6张图片

strcpy 拷贝

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

C语言进阶——字符串&&内存函数_第7张图片 strcpy 标准格式

C语言进阶——字符串&&内存函数_第8张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第9张图片

模拟实现 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;
}

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

C语言进阶——字符串&&内存函数_第10张图片

strcmp 比较

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

C语言进阶——字符串&&内存函数_第11张图片 strcmp 标准格式
C语言进阶——字符串&&内存函数_第12张图片 strcmp 的返回值

C语言进阶——字符串&&内存函数_第13张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第14张图片

模拟实现 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;
}

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

C语言进阶——字符串&&内存函数_第15张图片

strcat 追加

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

C语言进阶——字符串&&内存函数_第16张图片 strcat 标准格式

C语言进阶——字符串&&内存函数_第17张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第18张图片

模拟实现 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;
}

C语言进阶——字符串&&内存函数_第19张图片

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

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

strncpy 可控拷贝

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

C语言进阶——字符串&&内存函数_第20张图片 strncpy 标准格式

C语言进阶——字符串&&内存函数_第21张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第22张图片

模拟实现 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;
}

C语言进阶——字符串&&内存函数_第23张图片

strncmp 可控比较

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

C语言进阶——字符串&&内存函数_第24张图片 strncmp 标准格式
C语言进阶——字符串&&内存函数_第25张图片 返回值与 strcmp 完全一致

C语言进阶——字符串&&内存函数_第26张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第27张图片

模拟实现 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;
}

C语言进阶——字符串&&内存函数_第28张图片

strncat 可控追加

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

C语言进阶——字符串&&内存函数_第29张图片 strncat 标准格式

C语言进阶——字符串&&内存函数_第30张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第31张图片

模拟实现 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++;//确保每个元素都能追加
	}
    *dest = '\0';
	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;
}

C语言进阶——字符串&&内存函数_第32张图片

特殊字符串函数

strstr 寻找

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

C语言进阶——字符串&&内存函数_第33张图片 ststr 标准格式

C语言进阶——字符串&&内存函数_第34张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第35张图片

模拟实现 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;
}

C语言进阶——字符串&&内存函数_第36张图片

strtok 分割

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

C语言进阶——字符串&&内存函数_第37张图片 strtok 标准格式

C语言进阶——字符串&&内存函数_第38张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第39张图片

//strtok 字符串分割
int main()
{
	char str1[] = "[email protected]";
	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) 搭配就能很好的打印出错误信息。 

C语言进阶——字符串&&内存函数_第40张图片 strerror 标准格式
C语言进阶——字符串&&内存函数_第41张图片 errno 标准格式

C语言进阶——字符串&&内存函数_第42张图片

使用注意事项:

  • strerror 中的参数必须是整型,字符型会按ASCII码处理
  • error 在使用时需要包含头文件 errno.h
//strerror 字符串报错
#include
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。

C语言进阶——字符串&&内存函数_第43张图片

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

isxdigit 十六进制判断

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

C语言进阶——字符串&&内存函数_第44张图片

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

isupper 大写判断

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

C语言进阶——字符串&&内存函数_第45张图片

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

islower 小写判断

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

C语言进阶——字符串&&内存函数_第46张图片

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

toupper 转为大写

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

C语言进阶——字符串&&内存函数_第47张图片

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

tolower 转为小写

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

C语言进阶——字符串&&内存函数_第48张图片

//tolower 大写转小写
#include
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

C语言进阶——字符串&&内存函数_第49张图片 memcpy 标准格式

C语言进阶——字符串&&内存函数_第50张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第51张图片

模拟实现 memcpy

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

C语言进阶——字符串&&内存函数_第52张图片

 值得一提的是,当我们自己给自己拷贝,并且拷贝空间与目标空间重叠时,自己设计的函数会出问题,会有值被覆盖掉,因为它默认从前往后拷贝(会产生覆盖现象)。但是微软在设计 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 的功能,使其变成了一个满分拷贝。

C语言进阶——字符串&&内存函数_第53张图片 memmove 标准格式

C语言进阶——字符串&&内存函数_第54张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第55张图片

模拟实现 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;
}

C语言进阶——字符串&&内存函数_第56张图片

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

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

C语言进阶——字符串&&内存函数_第57张图片

memcmp 比较

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

C语言进阶——字符串&&内存函数_第58张图片 memcmp 标准格式
C语言进阶——字符串&&内存函数_第59张图片 memcmp 返回值及其意义

C语言进阶——字符串&&内存函数_第60张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第61张图片

模拟实现 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;
}

C语言进阶——字符串&&内存函数_第62张图片

memset 设置 

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

C语言进阶——字符串&&内存函数_第63张图片 memset 标准格式

C语言进阶——字符串&&内存函数_第64张图片

使用注意事项:

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

C语言进阶——字符串&&内存函数_第65张图片


总结

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

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

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

C语言进阶——字符串&&内存函数_第66张图片

相关文章推荐

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

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

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

你可能感兴趣的:(C语言——梦想系列,c语言,c++,库函数,字符串,内存操作)