深入C语言库:字符与字符串函数模拟实现

前言

C语言的库函数,是我们经常在编写程序所用到的函数,我们可以借用库函数去实现各种各样的功能,在本篇文章,我们介绍的是C语言中字符串和字符的相关库函数,以及他们的模拟实现,通过模拟实现我们可以深入了解到库函数的工作原理,以便今后更好的使用。

我们在学习C语言的过程中,除了使用最多的头文件<stdio.h>,还会使用其他头文件,利用其中的库函数帮助我们简化代码的过程,比如像<math.h>,<string.h>等头文件,而今天带大家详细了解一下**<string.h>**吧。

一、C语言相关字符串函数一览表

|--------------|------------------------------|
| 求字符串长度 | strlen |
| 长度不受限制的字符串函数 | strcpy strcat strcmp |
| 长度受限制的字符串函数 | strncpy strncat strncmp |
| 字符串查找 | strchr strstr strtok |
| 错误信息报告 | strerror |
| 内存操作函数 | memcpy memmove memset memcmp |

二、strlen函数(求字符串长度)

2.1 用法

1.声明:size_t strlen(const char *str)

  • str -- 要计算长度的字符串。

2.作用:计算字符串 str 的长度,直到空结束字符('\0'),但不包括空结束字符。

3.返回值:该函数返回字符串的长度

2.2 实例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char* str1 = "abcd";
	int ret = strlen(str1);
	printf("%d", ret);
	return 0;
}

运行结果为4

我们看到这就是strlen函数的功能,即求字符串的长度,这里的长度是不算'\0'的,所以求一个字符数组的长度时,一定要规定'\0'的位置。这里求出来的字符串长度就是我们可以看到的这4个字符

注意事项:

  1. 字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
  2. 参数指向的字符串必须要以 '\0' 结束。
  3. 注意函数的返回值为size_t,是无符号的。

2.3 模拟实现

2.3.1 计数法

思路:我们可以用一个指针变量p指向首元素和一个计数变量count并初始化为0,然后循环解引用指针所指向的元素,判断这个元素是否为'\0',不是每次p++,count++,是就跳出循环,返回count。

cpp 复制代码
#include<stdio.h>
int my_strlen(char* p)
{
	int  count = 0;
	while (*p)//当指向'\0',也就是0,为假跳出循环
	{
		p++;//指向下一个元素
		count++;//计数
	}
	return count;
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);//计算arr字符串的长度
	printf("%d\n", len);
	return 0;
}

2.3.2 指针-指针

思路:首先定义两个指针p1,p2,让两个指针指向首元素,然后让一个指针p2循环++,直到指向'\0'就停止,最后返回p2-p1

首先大家要清楚指针-指针是指在同一空间内,两个指针之间的元素个数

cpp 复制代码
int my_strlen(char* p1)
{
	char* p2 = p1;//使两个指针都指向首元素
	while (*p2)
	{
		p2++;
	}
	return p2 - p1;//返回两指针直接的元素的个数就是其长度
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);//计算arr字符串的长度
	printf("%d\n", len);
	return 0;
}

2.3.3 递归法

思路:假设我们要计算字符串"abcdef"的长度,我们可以拆分为1+"bcdef"的长度,同理"bcdef"的长度可以拆分为1+"cdef"的长度...理解了这个思路,我们就可以实现递归,首先定义一个指针变量p,如果p!='\0',我们就把p+1作为参数调用本函数,直到p为0.

cpp 复制代码
int my_strlen(char* p)
{
	if (*p != '\0')
	{
		return 1 + my_strlen(p + 1);//每次调用p+1指向下一个元素
	}
	else
	{
		return 0;//结束递归
	}
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);//计算arr字符串的长度
	printf("%d\n", len);
	return 0;
}

三 、strcpy函数(字符串拷贝)

3.1 用法

  • 声明:char *strcpy(char *dest, const char *src),dest -- 指向用于存储复制内容的目标数组,src -- 要复制的字符串。
  • 作用:把 src 所指向的字符串复制到 dest。需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。
  • 返回值:该函数返回一个指向最终的目标字符串 dest 的指针。

3.2 实例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char str1[20] = "abcdef";
	char str2[] = "kpl";
	strcpy(str1, str2);
	printf("%s", str1);
	return 0;
}

运行结果是:kpl

我们看到strcpy函数在这里的作用就是把str2的内容拷贝到了str1中。

注意事项:

  1. 源字符串必须以 '\0' 结束。(没有'\0'strcpy函数就不知道什么时候拷贝结束了)
  2. 会将源字符串中的 '\0' 拷贝到目标空间。
  3. 目标空间必须足够大,以确保能存放源字符串。(防止造成越界访问)
  4. 目标空间必须可变。(目标空间不能被const修饰

3.3 模拟实现

思路:我们想要将src的内容拷贝进des中,首先src的内容不能被改变,且保证都不是空指针。

利用循环把str2的内容拷贝到字符串str1中,这里简化了代码,把++放到了赋值语句中,这里需要注意我们要把++放到dest以及src的后面,这样保证先正常赋值第一个字符,再向后推移继续拷贝,直到赋值完'\0',条件为假,跳出循环。

cpp 复制代码
#include<stdio.h>
#include<string.h>
char* ma_strcpy(char* dest,const char* src)
{
    char* temp = dest;
    while(*dest++ =*src++)
    {
        ;
    }
    return temp;
}
int main()
{
    char str1[] = "abcdef";
    char str2[] = "kpl";
    char* ret = my_strcpy(str1,str2);
    printf("%s", ret);
    return 0;
}

四、strcat函数(字符串连接)

4.1 用法

声明:char *strcat(char *dest, const char *src)

  • dest -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串。
  • src -- 指向要追加的字符串,该字符串不会覆盖目标字符串。

作用:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。

返回值:该函数返回一个指向最终的目标字符串 dest 的指针。

4.2 实例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char str1[20] = "abcd";
	char str2[] = "efgh";
	char* ret = strcat(str1, str2);
	printf("%s", ret);
	return 0;
}

输出结果:abcdefgh

我们看到strcat函数就是把str2的内容追加到了str1的末尾,从str1的'\0'开始追加字符串str2的内容。

4.3 模拟实现

思路:

利用while循环把dest指针指向字符串末尾的'\0',因为第二个字符串追加到目标字符串是从目标字符串的'\0'开始追加的。

利用循环从dest的末尾位置开始,把src对应的地址内容赋值给目标字符串,两个地址都是后置++,先正常把str2的第一个字符赋值给str1,再++向后推移继续将str2中的字符赋值给目标字符串。

代码实现:

cpp 复制代码
char* my_strcat(char* dest, const char* src)//防止src的值被改变
{
	assert(dest && src);//不能为空指针
	char* ret = dest;
	while (*dest)//使dest指向末尾
	{
		dest++;
	}
	while (*dest++ = *src++)//循环赋值
	{
		;
	}
	return ret;
}
int main()
{
	char dest[20] = "hello ";
	char src[20] = "world!";
	my_strcat(dest, src);
	printf("%s", dest);
	return 0;
}

五、strcmp函数(字符串比较)

5.1 用法

  1. 声明:int strcmp(const char* str1,const char*str2)
  • str1 -- 要进行比较的第一个字符串。
  • str2 -- 要进行比较的第二个字符串。
  1. 作用:strcmp() 会根据 ASCII 编码依次比较 str1 和 str2 的每一个字符,直到出现不到的字符,或者到达字符串末尾(遇见'\0')

  2. 返回值:

  • 如果返回值小于 0,则表示 str1 小于 str2。
  • 如果返回值大于 0,则表示 str1 大于 str2。
  • 如果返回值等于 0,则表示 str1 等于 str2。

5.2 实例

strcmp用于比较字符串,并返回>0,==0,<0的值,让我们看看他的具体使用吧

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char str1[] = "abcd";
	char str2[] = "abc";
	int ret = strcmp(str1, str2);
	printf("%d", ret);
	return 0;
}

输出结果为1

如果:

cpp 复制代码
char str1[] = "abc";
char str2[] = "abc";

输出结果为0

如果:

cpp 复制代码
char str1[] = "abc";
char str2[] = "abcd";

输出结果为-1

5.3 模拟实现

思路:从两个字符串的首元素开始比较,先判断字符串str1是不是空如果是空直接就返回0,如果两个字符相等,两个字符串首地址进行++操作后移继续比较,比到最后一个字符串如果还是相等返回最后两个字符对应ASSIC码值相减后的结果,因为两个字符串相等所以结果就是 0 。

如果在比较过程中就发现了两组字符存在大小差异,即结束判断,返回两个字符对应ASSIC相减的结果,>0就是str1大于str2,<0就是str1小于str2。

代码示例如下:

cpp 复制代码
#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
//经const修饰让*str1与*str2无法改变
{
	assert(str1 && str2);//判断str1和str2是否为空指针
	//空指针直接报错,头文件<assert.h>
	while (*str1== *str2)
	{
		if (*str1 == '\0')
		{
			return 0;
		}
		str1++;
		str2++;
	}
	return *str1 - *str2;
}
int main()
{
	char str1[] = "abcd";
	char str2[] = "Abcd";
	int ret = my_strcmp(str1, str2);
	printf("%d\n", ret);
	return 0;
}

六、strncpy函数

6.1 用法

声明:char *strncpy(char *dest, const char *src, size_t n)

  • dest -- 指向用于存储复制内容的目标数组。
  • src -- 要复制的字符串。
  • n -- 要从源中复制的字符数。

作用: 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用'\0'填充。

6.2 实例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char str1[20] = "abcd";
	char str2[] = "kpl";
	char* ret = strncpy(str1, str2, 2);
	printf("%s", ret);
	return 0;
}

输出结果为:kpcd

我们看到我们在strncpy函数中,前两个参数是字符串的地址,第三个参数我们写的是 2 ,所以就是把字符串str2的前两个字符拷贝到str1中。

6.3 模拟实现

思路:这里的模拟实现只有在循环的位置(①和②的位置)进行了稍微的改变,把我们的需要进行拷贝的元素个数作为循环的变量, 拷贝几个字符,循环几遍就可以了,其他代码和strcpy的编写是一样的。

代码示例如下:

cpp 复制代码
char* my_strncpy(char* dest, const char* src, int n)
{
	assert(dest && src);
	char* ret = dest;    //将dest首地址储存在ret中,方便返回
	while (*src && n)
	{
		*dest = *src;
		dest++;
		src++;
		n--;
	}
	if (n != 0)//n>src
	{
		while (n)
		{
			*dest = '\0';
			dest++;
			n--;
		}
	}
	
	return ret;  //返回数组的首地址
}

七、strncat函数

7.1 用法

声明:char *strncat(char *dest, const char *src, size_t n)

  • dest -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
  • src -- 要追加的字符串。
  • n -- 要连接的最大字符数。

用法:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。

如果n<src的长度,将source指向字符串的前n个字符追加到dest指向的字符串末尾,再追加⼀个 \0 字符。

如果n>=src,只会将字符串中到\0 的内容追加到dest指向的字符串末尾

7.2 实例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char str1[20] = "abcd";
	char str2[] = "efgh";
	char* ret = strncat(str1, str2, 2);
	printf("%s", ret);
	return 0;
}

输出结果为abcdef

我们上图看到的代码在strncat中前两个参数是字符串的地址,第三个参数是拷贝的字符个数,这里写的是2所以把str2前两个字符拷贝到str1中

**注意:**如果n<src时,要在末尾自动补充'\0'。

7.3 模拟实现

代码示例如下:

cpp 复制代码
#include<assert.h>
char* my_strncat(char* dest, const char* src, int n)
{
	char* ret = dest;  //将dest首地址储存在ret中
	assert(dest&&src);  //保证dest、src非空
	while (*dest != '\0')//找到dest结尾的'\0'
	{
		dest++;
	}
	while (n && (*dest++ = *src++))//把src里的字符一个个放入dest后
	{
		n--;   //循环跳出条件
	}
	if(n==0)
	{
		*dest = '\0'; //如果n<src
	}
	return ret; //返回dest字符串起始地址
}

八、strncmp函数

8.1 用法

声明:int strncmp(const char *str1, const char *str2, size_t n)

  • str1 -- 要进行比较的第一个字符串。
  • str2 -- 要进行比较的第二个字符串。
  • n -- 要比较的最大字符数。

作用: 把 str1 和 str2 进行比较,最多比较前 n 个字符

返回值:

如果返回值 < 0,则表示 str1 小于 str2。

如果返回值 > 0,则表示 str1 大于 str2。

如果返回值 = 0,则表示 str1 等于 str2。

8.2 实例

cpp 复制代码
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "abcdef";
	char arr2[20] = "abcddd";
	int ret1 = strncmp(arr1, arr2, 3);//比较前三个字符
	int ret2 = strncmp(arr1, arr2, 8);//即使给出的num太大,遇见'\0'也会停止
	int ret3 = strncmp(arr1, arr2, 6);//比较全部字符
	printf("%d %d %d\n", ret1, ret2,ret3);
	return 0;
}

输出结果为:0 1 1。

8.3 模拟实现

代码示例如下:

cpp 复制代码
#include <stdio.h>
#include<assert.h>
int my_strncmp(const char* str1, const char* str2, size_t n)
{
	assert(str1 && str2);
	while (n-- && *str1 == *str2)//任意一个条件不满足就跳出循环
	{
		if (*str1 =='\0')
		{
			return 0;
		}
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

九、strchr函数(字符串查找字符函数)

9.1 用法

声明:char *strchr(const char *str, int c)

  • str -- 要查找的字符串。
  • c -- 要查找的字符。

作用:在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。

返回值:如果在字符串 str 中找到字符 c,则函数返回指向该字符的指针,如果未找到该字符则返回 NULL。

9.2 实例

cpp 复制代码
#include<string.h>
int main()
{
	char arr[20] = "hello betty";
	char* p = strchr(arr,'y');
	if (*p == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了\n");
	}
	return 0;
}

9.3 模拟实现

思路:一样先排查空指针,然后循环寻找,如果寻找到,直接返回其地址。找不到就返回空指针NULL

代码实现:

cpp 复制代码
char* my_strchr(const char* str, int c)
{
	assert(str);//排查空指针
	while (*str)
	{
		if (*str == c)
		{
			return str;//找到返回其地址
		}
		str++;
	}
	return NULL;//找不到返回空指针
}
int main()
{
	char arr[20] = "hello betty";
	char* p =my_strchr(arr,'y');
	if (*p == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了\n");
	}
	return 0;
}

十、strstr函数(字符串查找小字符串函数)

10.1 用法

声明:char *strstr(const char *haystack, const char *needle)

  • haystack -- 要被检索的 C 字符串。
  • needle -- 在 haystack 字符串内要搜索的小字符串。

作用:在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 '\0'。

返回值:该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 空指针(NULL)

10.2 实例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char str1[] = "abcdefdgh";
	char str2[] = "d";
	char* ret = strstr(str1, str2);
	printf("%s", ret);
	return 0;
}

输出结果为defdgh

这里我们看到strstr中接收的是我们两个字符串的地址,定义了一个字符指针变量ret去接收函数返回的地址,我们看到str2中只有一个字符d,我们在str1进行查找看是否也有字符串d,明显我们看到str1中第四个字符是d,所以停止查找返回第一次见到字符d所对应的地址,最后打印出来的是str1中字符d第一次出现后的地址所对应内容。

注意str2一定要是str1的子字符串(连续),这样才可以查找,否则是找不到的,查找不到就会返回空指针。

10.3 模拟实现

思路:首先用是用s1,s2指向两个字符串的首元素,用p记录str1中开始比较的元素的位置,方便重新开始比较。然后循环比较,如果*s1!=*s2,或者遇见'\0',就跳出循环,判断,如果是s2为'\0',说明配对成功,s1为'\0',则说明后续长度不够,匹配失败啦,除开以上情况,就让p++,重复上述流程,直到*p=='\0'

在acbcef中找acb

代码实现如下:

cpp 复制代码
#include <stdio.h>
char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);//防止空指针
	const char* s1 = str1;
	const char* s2 = str2;
	const char* p = str1;//记录初始位置
	while (*p)
	{
		s1 = p;//从记录位置开始比较
		s2 =str2;
		while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')//配对成功
		{
			return (char*)p;//p原本被const修饰
 
		}
		else if (*s1 == '\0' && s2 != '\0')//s1后续字符少于s2
		{
			return NULL;
		}
		p++;//记录下一个位置
	}
	return NULL;
}
int main()
{
	char haystack[20] = "hello betty";
	char needle[10] = "betty";
	char* ret =my_strstr(haystack, needle);
	if (ret == NULL)
	{
		printf("未找到\n");
	}
	else
	{
		printf("找到啦,子字符串是:%s\n", ret);
	}
	return 0;
}

十一、strtok函数 (字符串分割函数)

11.1 用法

声明:char *strtok(char *str, const char *delim)

  • str -- 要被分解成一组小字符串的字符串。第一次调用 strtok() 时,这个参数应该是你想要分割的字符串。随后的调用应该将此参数设置为NULL,以便继续从上次的位置分割。
  • delim -- 包含分隔符的 C 字符串。

作用:strtok() 用于将字符串分割成一系列的子串

返回值:该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。

11.2 实例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char str1[] = "by@bite.cn";
	char str2[200] = { 0 };
	char sep[] = "@.";
	strcpy(str2, str1);
	char* ret = NULL;
	for (ret = strtok(str2, sep); ret != NULL; ret = strtok(NULL, sep))
	{
		printf("%s ", ret);
	}
}

注意事项:

  1. sep参数是个字符串,定义了用作分隔符的字符集合
  2. 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。
  3. strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
  4. strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
  5. strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
  6. 如果字符串中不存在更多的标记,则返回 NULL 指针。

十二、strerror(错误信息报告)

12.1 用法

声明:char *strerror(int errnum)

  • errnum -- 错误号,通常是 errno。

作用:从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。strerror 生成的错误字符串取决于开发平台和编译器。

返回值:该函数返回一个指向错误字符串的指针,该错误字符串描述了错误 errnum。

12.2 实例

参考以下代码:

cpp 复制代码
#include<string.h>
#include<errno.h>//错误码的头文件
int main()
{
	//每一个错误码对应一个错误信息
	printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	return 0;
}

输出:

No error (没有错误)

Operation not permitted (操作不允许)

No such file or directory (没有这样的文件)

No such process (没有这样的进程)

十三、memcpy函数(内存拷贝函数)(内存不重叠)

13.1 为什么要内存造作函数

上方我们的拷贝函数strcpy和strncpy都是专门拷贝字符串的函数,所以只能拷贝字符串。除了字符函数和字符串函数,<string.h>中还有一类内存操作函数,如memset(),memcmp()等函数,他们在功能和某些字符串函数很像,但作用范围更广,除了作用于字符串外,还可以作用于int ,double等内置类型,但因为是以字节为单位改变,所以限制也很大。

13.2 用法

声明:void *memcpy(void *str1, const void *str2, size_t n)

  • str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。
  • n -- 要被复制的字节数。

作用:从存储区 str2 复制 n 个字节到存储区 str1。

返回值:该函数返回一个指向目标存储区 str1 的指针。

13.3 实例

参考代码如下:

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[100] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[100] = { 0 };
	int i = 0;
	memcpy(arr2, arr1, 10 * sizeof(int));
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}
	return 0;
}

输出结果为:1 2 3 4 5 6 7 8 9 10

我们看到这里就是把arr1中的10个元素拷贝到了arr2中,memcpy的三个参数分别是两个数组的首地址,和数组arr1的待拷贝元素个数的大小(单位:字节)

注意事项:

  1. 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
  2. 这个函数在遇到 '\0' 的时候并不会停下来。
  3. 如果source和destination有任何的重叠,复制的结果都是未定义的。

13.4 模拟实现

思路:

  1. 定义一个void*类型的指针用来存放目标字符串,便于一会拷贝完成后,返回其地址
  2. 以元素的大小作为循环的条件,由于我们不知道元素的具体类型,所以我们采用最细致的方法进行元素的访问,每次-1个字节去访问元素的每一位字节
  3. 我们知道char*类型一次可以访问1个字节,这是最细致的访问方法,所以我们把我们的目标字符串和待拷贝的字符串地址都强制类型转换为char*类型,这样便于我们遍历整个元素,这样一个字节一个字节的把str2的内容拷贝给str1
  4. 我们把dest和src强制类型转换为char*类型后,每+1就是跳过一个字节,这样保证每一位都可以进行交换,保证元素交换成功

代码实现如下:

cpp 复制代码
void* my_memcpy(void* dest, const void* src, size_t n)
{
	assert(dest && src);//防止空指针
	void* ret = dest;
	while (n--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}
  • 我们在本程序中是把两个没有内存重叠的两个数组进行的相互拷贝,但是在内存出现重叠的情况时,memcpy函数在某些编译器下是无法实现的,所以我们在下面引入memmove函数。

十四、memmove函数(内存拷贝函数)(内存重叠)

14.1 用法

声明:void *memmove(void *str1, const void *str2, size_t n)

  • str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。
  • n -- 要被复制的字节数。

作用:从 str2 复制 n 个字符到 str1,但是在重叠内存块这方面,memmove() 是比 memcpy() 更安全的方法。如果目标区域和源区域有重叠的话,memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和 memcpy() 函数功能相同。

返回值:该函数返回一个指向目标存储区 str1 的指针。

14.2 实例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main() 
{
    char str[] = "Hello, World!";
    printf("Original string: %s\n", str);
    // 将字符串前6个字符移动到字符串的末尾
    memmove(str, str + 7, 6);
    printf("Modified string: %s\n", str);
    return 0;
}

输出结果:

Original string: Hello, World!

Modified string: World! World!

注意事项:

  1. 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
  2. 如果源空间和目标空间出现重叠,就得使用memmove函数处理。

14.3 模拟实现

思路:

  1. 定义一个void*类型的指针变量用来存放目标字符串的地址,便于一会进行完拷贝操作后,返回拷贝后的目标字符串

  2. 以字节的大小去作为循环变量,我们用最细致的方法,一个字节一个字节的进行拷贝,这样保证可以每一种数据类型都可以拷贝过去

3.我们的拷贝有两种情况,

  • 第一种情况:当dest的地址小于src的地址,证明我们需要把src的内容向左拷贝,为了可以保证拷贝的成功,我们的src需要从小到大去进行拷贝。我们把dest和src分别强制类型转换成char*,这样每+1就跳过一个字节,这样的拷贝方式是最细致的方法,适用于每一种数据类型,每一次跳过一个字节,最后跳过总的count字节数即可完成拷贝操作。
  • 第二种情况当dest的地址大于src的地址,我们把前面的元素向后拷贝,所以是把元素向右拷贝,这时为了保证地址重叠部分不被先行改变,所以先拷贝最后一个元素,倒着进行赋值,这样可以保证拷贝的数据准确不出错误。

因为我们情况二的拷贝需要倒着赋值,先赋值高地址的内容,所以我们先加上总字节数这样可以保证拷贝操作从最后一个元素进行,如上图的代码,我们需要进行拷贝的元素是4个整型元素,所以字节数是16个字节,因为进行了后置--操作,所以这时候的count已经是15,地址src和地址dest都是对应的拷贝位置的首地址,所以首地址+15对应的位置都是末元素的位置。

代码实现如下:

cpp 复制代码
void* my_memmove(void* dest, const void* src, size_t n)
{
	assert(dest && src);//防止空指针
	void* ret = dest;
	if (dest <= src)//dest在src左侧
	{
		while (n--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else//dest在src的右侧
	{
		dest = (char*)dest + n - 1;//指向末尾
		src = (char*)src + n - 1;//指向末尾
		while (n--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest - 1;
			src = (char*)src - 1;
		}
	}
	return ret;
}

十五、memcmp函数(内存比较函数)

15.1 用法

声明:int memcmp(const void *str1, const void *str2, size_t n)

  • str1 -- 指向内存块的指针。
  • str2 -- 指向内存块的指针。
  • n -- 要被比较的字节数。

作用:把存储区 str1 和存储区 str2 的前 n 个字节进行比较。

返回值:

  • 如果返回值 < 0,则表示 str1 小于 str2。
  • 如果返回值 > 0,则表示 str1 大于 str2。
  • 如果返回值 = 0,则表示 str1 等于 str2。

15.2 实例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[] = { 1,3,2,4,5,6,7,8,9,10 };
	int ret = memcmp(arr1, arr2, 16);
	printf("%d", ret);
}

输出结果为-1

我们看到这里返回的数值是-1所以我们可以得到数组arr2是比数组arr1要大的,因为我们看到数组arr2[1]的元素要比arr1[1]的元素要大,这样终止了我们继续向后的比较,直接返回了一个小于0的数字。

15.3 模拟实现

思路:

  1. 定义两个void*指针存放需要进行比较的两个变量地址(这里由于不知道变量的类型,所以采用void*类型指针接收),以及一个无符号整型的count接收的需要进行比较的元素总大小(单位:字节)
  2. 利用总字节数作为循环的变量,循环完整个字节数即完成了所有元素的比较
  3. 以最细致的方法进行比较,把元素的地址类型转换为char*类型,char*类型每次可以访问一个字节,这样我们可以把元素进行逐一字节的比较
  4. 逐一比较每一个字节所对应的地址元素是不是相等,如果相等两个指针分别加1继续推移向下一个字节进行比较,如果两个元素存在大小的差异,直接返回这两个元素对应assic差异

代码实现如下:

cpp 复制代码
#include<stdio.h>
#include<assert.h>
int my_memcmp(const void* str1, const void* str2, size_t n)
{
	assert(str1 && str2);//
	char* p1 = (char*)str1;
	char* p2 = (char*)str2;
	while (n--&&(*p1==*p2))
	{
		if (*p1 == '\0')
		{
			return 0;
		}
		p1++;
		p2++;
	}
	return *p1 - *p2;
}

15.4 strcmp,strncmp,memcmp之间的区别

  • memcmp是比较两个存储空间的前n个字节,即使字符串已经结束,仍然要比较剩余的空间,直到比较完n个字节。
  • strcmp比较的是两个字符串,任一字符串结束,则比较结束。
  • strncmp在strcmp的基础上增加比较个数,其结束条件包括任一字符串结束和比较完n个字节。
  • strcmp比较的字符串,而memcmp比较的是内存块,strcmp需要时刻检查是否遇到了字符串结束的 \0 字符,而memcmp则完全不用担心这个问题,所以memcmp的效率要高于strcmp

十六、memset函数(内存设置函数)

16.1 用法

声明:void *memcpy(void *str1, const void *str2, size_t n)

  • str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。
  • n -- 要被复制的字节数。

作用:从存储区 str2 复制 n 个字节到存储区 str1。

返回值:该函数返回一个指向目标存储区 str1 的指针。

16.2 示例

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
	char str[] = "hello world";
	memset(str, 'x', 6);//以字节为单位
	printf(str);
	return 0;
}

输出结果:xxxxxxworld

cpp 复制代码
int main()
{
	int arr[4] = { 1,2,3,4 };
	memset(arr, 1, sizeof(arr));
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

输出结果:16843009 16843009 16843009 16843009

16.3 模拟实现

思路:

  1. 判断dest地址的有效性
  2. 定义一个void*指针用来存放目标元素的地址方便一会返回其地址,由于不知道具体进行设置的元素类型,这里采用void*指针接收(void*指针可以接收任意类型的指针变量)
  3. 以总字节的大小作为循环的变量,循环完整个元素字节后就可以对整个元素都进行了设置
  4. 我们的机器都是小端存储的,所以我们对变量c进行一个强制类型的转换把变量c的地址强制类型转换为char*类型访问变量c的一个字节拿到的就是变量的有效位02,再把02初始化给目标元素,同样把目标元素也进行强制类型转换,把每一字节都进行初始化,每次向后+1直到循环了总字节的个数

代码实现如下:

cpp 复制代码
void* my_memmove(void* dest, const void* src, size_t n)
{
	assert(dest && src);//防止空指针
	void* ret = dest;
	if (dest <= src)//dest在src左侧
	{
		while (n--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else//dest在src的右侧
	{
		dest = (char*)dest + n - 1;//指向末尾
		src = (char*)src + n - 1;//指向末尾
		while (n--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest - 1;
			src = (char*)src - 1;
		}
	}
	return ret;
}
相关推荐
Darkwanderor3 小时前
插入排序——希尔排序
c语言·算法·排序算法·希尔排序
沐泽Mu4 小时前
嵌入式学习-C嘎嘎-Day04
c语言·开发语言·c++·学习
挥剑决浮云 -5 小时前
Linux 网络编程
linux·运维·c语言·网络·笔记
析木不会编程5 小时前
【数据结构】【线性表】循环链表(附C语言源码)
c语言·数据结构·链表
写bug的小屁孩5 小时前
基于HTTP编写ping操作
服务器·c语言·网络·c++·网络协议·http·qt6.3
奈葵7 小时前
C语言字符函数和字符串函数
c语言·开发语言
OKkankan7 小时前
单链表算法题(数据结构)
c语言·数据结构·数据库·c++·算法
芒 种7 小时前
深入理解指针
c语言
羊小猪~~7 小时前
C/C++语言基础--initializer_list表达式、tuple元组、pair对组简介
c语言·开发语言·c++·vscode·list·c++20·visual studio
t5y228 小时前
【C语言】Union
c语言·开发语言·算法