11.字符串&内存函数【C语言】【1.9w字长文总结】

0.前言

C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。
字符串常量 适用于那些对它不做修改的字符串函数.

1.函数介绍

strlen

#include<stdio.h>
#include<assert.h>
int my_strlen(const char* str)//不希望arr内容被修改
{
	assert(str != NULL);//断言
	//char* end = str;//把安全的str交给了安全的end,会报警告,end也要加const
	const char* end = str;
	while (*end != '\0')
	{
		end++;
	}
	return end - str;//指针-指针得到的是元素个数
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

库函数里的strlen返回值是size_t,也就是unsigned int

image-20220119151438781

size_t也有其缺点

#include<stdio.h>
#include<string.h>
int main()
{
	if (strlen("abc") - strlen("abcdef") < 0)
	{
		printf("1\n");
	}
	else
	{
		printf("2\n");//会输出2,size_t返回的始终是正数
	}
	return 0;
}

//解决方案:
	1.把strlen返回值强制类型转化为int
	2.或者先用strlen计算出结果,再拿结果做运算

注意:参数指向的字符串必须要以 ‘\0’ 结束。

长度不受限制的字符串函数

strcpy

char* strcpy(char * destination, const char * source );

Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).

Return Value

Each of these functions returns the destination string. No return value is reserved to indicate an error.
  • 拷贝字符串
  • 源字符串必须以 ‘\0’ 结束。
  • 会将源字符串中的 ‘\0’ 拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可修改。不能是常量字符串

image-20220225120330589

printf遇到\0也会停止,也不打印\0

模拟实现
#include <stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
    //*src不能被修改
{
	assert(dest && src);
	char* ret = dest;
	while (*dest++ = *src++)
	{
		;//先拷贝后++
		//既拷贝了\0,又让while停止了
	}
	return ret;
}
int main()
{
	char arr1[20] = { "xxxxxxxxxxxxx" };
	char arr2[] = { "hello" };
	printf("%s\n", my_strcpy(arr1, arr2));//链式访问
	return 0;
}
关于指针:
  1. 指针不知道赋值为什么,就赋值为NULL
  2. 指针使用完后,要赋值为NULL
库函数源代码:
#include <string.h>
#ifdef _PIC16
far char *
strcpy(far char *to, register const char *from)
#else  /* _PIC16 */
char *
strcpy(char *to, register const char *from)
#endif /* _PIC16 */
{

#ifdef _PIC16
	register far char *cp;
#else  /* _PIC16 */
	register char *cp;
#endif /* _PIC16 */

	cp = to;
	while (*cp++ = *from++)
		continue;
	return to;
}

strcat

字符串连接
把源头追加到目标

char*strcat ( char* destination, const char*source );

Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null character is included at the end of the new string formed by the concatenation of both in destination.
    
Return Value

Each of these functions returns the destination string (strDestination). No return value is reserved to indicate an error.
  • 源字符串必须以 ‘\0’ 结束。
  • 目标空间必须有足够的大,能容纳下源字符串的内容。
  • 目标空间必须可修改。
  • ‘\0’也会拷过去,原先目标空间的\0要被覆盖
模拟实现strcat
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest && src);//判断是否为空指针
	//1.先找到目标空间的\0
	while (*dest)
	{
		dest++;
	}

	//2.追加内容到目标空间
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[30] = "hello";
	char arr2[] = " world";
	printf("%s\n", my_strcat(arr1, arr2));
	return 0;
}
库函数源代码:
char * __cdecl strcat (char * dst, const char * src)
{
        char * cp = dst;
        while( *cp )
                cp++;    /* find end of dst */
        while((*cp++ = *src++) != '\0') ;      /* Copy src to end of dst */
        return( dst );        /* return dst */
}
//__cdecl是一种函数调用约定

strcmp

int strcmp( const char *string1, const char *string2 );

Return Value
< 0 string1 less than string2 
0 string1 identical to string2 
> 0 string1 greater than string2 

如果直接用==比较字符串

if("abc" == "abq");
这里比较的a的地址和另一个a的地址,肯定不相等
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abc";
	char arr2[] = "abd";
	int ret = strcmp(arr1, arr2);
	printf("%d\n", ret);//-1
	return 0;
}
模拟实现
#include<stdio.h>
#include<string.h>
#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	//实际上比较的是ASCII值大小
	if (*str1 > *str2)
		return 1;
	else
		return -1;
}
int main()
{
	char arr1[] = "abc";
	char arr2[] = "abd";
	int ret1 = strcmp(arr1, arr2);
	int ret2 = my_strcmp(arr1, arr2);
	printf("%d\n", ret1);//-1
	printf("%d\n", ret2);//-1
	return 0;
}

改进一下:

if (*str1 > *str2)
		return 1;
	else
		return -1;

改成
return *str1 - *str2;

库函数源代码:

int __cdecl strcmp(
	const char* src,
	const char* dst
)
{
	int ret = 0;

	while ((ret = *(unsigned char*)src - *(unsigned char*)dst) == 0 && *dst)
	{
		++src, ++dst;
	}

	return ((-ret) < 0) - (ret < 0); // (if positive) - (if negative) generates branchless code
}

长度受限制的字符串函数

strncpy

char *strncpy( char *strDest, const char *strSource, size_t count );

count
Number of characters to be copied
    
Return Value

Each of these functions returns strDest. No return value is reserved to indicate an error.

如果源数据不够count的个数,多余的会用\0替代

image-20220119165010573

int main()
{
	char arr1[] = "xxxxxxxxxx";
	char arr2[] = "hello world";
	strncpy(arr1, arr2, 5);
	printf("%s\n", arr1);//helloxxxxx
	return 0;
}
int main()
{
	char arr1[] = "xxxxxxxxxx";
	char arr2[] = "he";
	strncpy(arr1, arr2, 5);
	printf("%s\n", arr1);//he
	return 0;
}
模拟实现
char* my_strncpy(char* dest, const char* src, size_t count)
{
	assert(dest && src);
	int i = 0;
	for ( i = 0; src[i] && (i < count); i++)//src里面的\0不能拷进去
	{
		dest[i] = src[i];
	}
    
	//如果src的数据个数小于count
	if (i < count)
	{
		dest[i] = 0;
	}
	return dest;
}
int main()
{
	char arr1[] = "xxxxxxxxxx";
	char arr2[] = "hel";
	my_strncpy(arr1, arr2, 5);
	printf("%s\n", arr1);//hel
	return 0;
}
库函数源码
读取编码 ISO-8859-1470 B
#include	<string.h>

#ifdef _PIC16
far char *
strncpy(register far char * to, register const char * from, register size_t size)
#else /* _PIC16 */
char *
strncpy(register char * to, register const char * from, register size_t size)
#endif /* _PIC16 */
{

#ifdef _PIC16
	register far char *	cp;
#else /* _PIC16 */
	register char *	cp;
#endif /* _PIC16 */

	cp = to;
	while(size) {
		size--;
		if(!(*cp++ = *from++))
			break;
	}
	while(size--)
		*cp++ = 0;
	return to;
}

strncat

char *strncat( char *strDest, const char *strSource, size_t count );

追加完之后会主动放一个\0在后面的

int main()
{
	char arr1[20] = "helloxxxxxxx";
	char arr2[] = "hello";
	strncat(arr1, arr2, 5);
	printf("%s\n", arr1);//helloxxxxxxxhello
	return 0;
}

从\0开始追加,并把开始的那个\0覆盖掉

int main()
{
	char arr1[20] = "hello\0xxxxxx";
	char arr2[] = "hello";
	strncat(arr1, arr2, 5);
	printf("%s\n", arr1);//hellohello
	return 0;
}

如果追加的个数大于元数据元素个数

int main()
{
	char arr1[20] = "hello\0xxxxxx";
	char arr2[] = "hello";
	strncat(arr1, arr2, 7);
	printf("%s\n", arr1);//hellohello
	return 0;
}
//也是只追加到\0为止,把源数据追加完了就不再继续追加了
模拟实现
char* my_strncat(char* dest, const char* src, size_t count)
{
	char* ret = dest;
	assert(dest && src);
	//先找到目标空间\0
	while (*dest)
	{
		dest++;
	}
	//追加内容
	int i = 0;
	for (i = 0; src[i] && (i < count); i++)
	{
		dest[i] = src[i];
	}
	dest[i] = 0;//追加完补上\0
	return ret;
}
int main()
{
	char arr1[20] = "helloxxxxxxx";
	char arr2[] = "hello";
	my_strncat(arr1, arr2, 3);
	printf("%s\n", arr1);//helloxxxxxxxhello
	return 0;
}
库函数源码
读取编码 ISO-8859-1470 B

#include	<string.h>

#ifdef _PIC16
far char *
strncat(register far char * to, register const char * from, register size_t size)
#else /* _PIC16 */
char *
strncat(register char * to, register const char * from, register size_t size)
#endif /* _PIC16 */
{

#ifdef _PIC16
	register far char *	cp;
#else /* _PIC16 */
	register char *	cp;
#endif /* _PIC16 */

	cp = to;
	while(*cp)
		cp++;
	while(size && (*cp++ = *from++))
		size--;
	if(size == 0)
		*cp = 0;
	return to;
}

strncmp

int strncmp( const char *string1, const char *string2, size_t count );
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcqqqq";
	int ret = strncmp(arr1, arr2, 4);//d小于q,应该返回的是<0的数
	printf("%d\n", ret);//-1
	return 0;
}

strstr

字符串查找函数
返回第一次找到的起始地址,找不到返回NULL

char *strstr( const char *string, const char *strCharSet );

Find a substring.
    
Return Value

Each of these functions returns a pointer to the first occurrence of strCharSet in string, or NULL if strCharSet does not appear in string. If strCharSet points to a string of zero length, the function returns string.//返回第一次找到的地址

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abcdefabcdef";
	char arr2[] = "bcd";
	char* ret = strstr(arr1, arr2);
	if (NULL == ret)
	{
		printf("找不到\n");
	}
	else
	{
		printf("%s\n", ret);//bcdefabcdef
	}
	return 0;
}
模拟实现strstr

abbbcdefbbcdef
bbc
第一次匹配失败时,得回到str的第二个b上重新开始,但是由于如果直接让str和substr移动的话,就找不到一开始的起始位置了,因此用s1,s2来替代
每一次查找失败,需要下次开始时多移动一步,就需要有个cur来++
动手画一下图就好理解了

image-20220119200616893

//3指针
char* my_strstr(const char* str, const char* substr)
{
	const char* s1 = str;
	const char* s2 = substr;//记录好起始位置
	const char* cur = str;
	assert(str && substr);
	if (*substr == '\0')//空串没法查找
	{
		return (char*)str;
        //str本身是安全的指针,有const修饰,如果不强制类型转换,返回成char*这个不安全指针会报警告
	}
	while (*cur != '\0')
	{
		s1 = cur;
		s2 = substr;
		while (*s1 != '\0' && *s2 != '\0' && *s1==*s2)//优先级 ==  >  !=  >  &&
      //可以优化为 while (*s1 && *s2 && *s1==*s2 )
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')//匹配成功
		{
			return (char*)cur;
		}
		cur++;//*s1 != *s2时让cur++,并让s1,s2重新开始
	}
	return NULL;//cur从头走到尾了都找不到
}
int main()
{
	char arr1[] = "abbbcdefbbcdef";
	char arr2[] = "bbc";
	char* ret = my_strstr(arr1, arr2);
	if (NULL == ret)
	{
		printf("找不到\n");
	}
	else
	{
		printf("%s\n", ret);//bbcdefbbcdef
	}
	return 0;
}

缺点:算法效率较低,可以用KMP算法实现

库函数源代码
#include<string.h>
 char*
strstr(register const char* s1, register const char* s2)
{
	while(s1 && *s1) {
		if(strncmp(s1, s2, strlen(s2)) == 0)
			return ( char*)s1;
		s1 = strchr(s1+1, *s2);
	}
	return NULL;
}

KMP算法

一种改进的字符串匹配算法,核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的

1.为什么主串i不回退

image-20220119204151159

2.j的回退位置

image-20220119204215644

目的:i不回退,j回退到一个特定的位置

image-20220119204538586

问题:假设有这么一个串,怎么能确定这个j回退的位置呢

因为i不回退,所以要尽量在主串中找到和子串匹配的一部分串

所以第一次j回退到2的位置

next数组:保存子串某个位置匹配失败后,回退的位置

image-20220119205215930

求next数组:

如果找不到2个相等的真子串,那么此时next里面放的就是0

image-20220119205841832

image-20220119210320278

找到的话,next数组里放的就是那两个串单独的长度

a b a b c a b c d a b c d e

-1 0 0 1 2 0 1 2 0 0 1 2 0 0

image-20220119211324740

一次最多增加1

假设next[i] = k 推出公式:

image-20220119211937908

又长度相等,即k-1-0 = i-1-x
则x = i-k

如果p[i] == p[k],中间的是p数组,8下标是a,3下标也是a
给左边加上p[k],给右边加上p[i]

image-20220119212307418

那么在next[i] = k的基础上且p[i] == p[k],推出了,next[i+1] = k+1

如果p[i] != p[k]呢?
那就一直回退,直到找到p[i] == p[k],就能利用公式next[i+1] = k+1

image-20220119213054998

此时next[6] = 1

KMP实现

#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>

void GetNext(const char* sub, int* next, int lenSub)
{
	next[0] = -1;
	if (lenSub == 1)//只有1个元素,next数组只能赋值1个
	{
		return;
	}
	next[1] = 0;
	int i = 2;
	int k = 0;//i前一项的k
	//注意手算next数组与用代码算next数组区别,i还没求,得先求i-1
	//手算:p[i] == p[k] --》next[i+1] = k+1
	//代码算:p[i-1] == p[k] --》next[i] = k+1
	while (i<lenSub)
	{
		if (k == -1 || sub[i-1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else//不相等,k需要往回退到next对应的下标处,再看p[i-1] == p[k]是否成立
			//如果k一直回退,退到-1,此时k越界了,且说明中间是找不到2段相等的子串,即next[i]=0
		{
			k = next[k];
		}
	}
}


int KMP(const char* str, const char* substr, int pos)
{
	assert(str && substr);
	int lenStr = strlen(str);
	int lenSub = strlen(substr);
	if (lenStr == 0 || lenSub == 0)
	{
		return -1;
	}
	if (pos < 0 || pos >= lenStr)
	{
		return -1;
	}

	int* next = (int*)malloc(sizeof(int)*lenSub);//开辟相应大小的next数组
	assert(next != NULL);

	GetNext(substr, next, lenSub);

	int i = pos;//遍历主串
	int j = 0;//遍历子串

	while (i < lenStr && j < lenSub)
	{
		if (j == -1 || str[i] == substr[j])
		{
			i++;
			j++;
		}
		//注意,如果第一个字符就匹配失败,j会回到-1,会产生数组越界
		//此时j恰好需要++回到0,i也应该指向下一个
		else
		{
			j = next[j];//不相等回退到next数组中相应的j下标
		}
	}
	free(next);//next置空
	if (j >= lenSub)
	{
		return i - j;//找到了
	}
	return -1;//遍历完之后都找不到
}


int main()
{
	printf("%d\n", KMP("ababcabcdabcde", "abcd", 0));//5
	printf("%d\n", KMP("ababcabcdabcde", "abcdf", 0));//-1 找不到
	printf("%d\n", KMP("ababcabcdabcde", "ab", 0));//0 一开始就有
	return 0;
}

代码算next数组

image-20220119231925970

k回退到-1

image-20220119232020163

next数组优化

0	1	2	3	4	5	6	7	8

a	a	a	a	a	a	a	a	b

-1	0	1	2	3	4	5	6	7

假设在5下标匹配失败,就要回到4位置,再回到3位置,一直回到0位置
为何不一步回到0位置呢?前面的字符都一样,第5个不匹配,前面肯定也都不匹配

nextval数组:

0	1	2	3	4	5	6	7	8

a	a	a	a	a	a	a	a	b

-1	0	1	2	3	4	5	6	7	--next值

-1	-1	-1	-1	-1	-1	-1	-1	7	 --nextval值

1.回退到的位置和当前字符一样,就写回退到那个位置的next值
2.如果回退到的位置和当前字符不一样,就写当前字符原来的next值

image-20220119233837083

选项答案第一个next值是从0开始的,所以需要+1

strtok

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

非常烂的使用方法

#include<stdio.h>
#include<string.h>
int main()
{
	const char* p = "@.";//这个无需顺序区分
	char arr[] = "yzq2076188013@qq.com";
	char buff[50] = { 0 };
	strcpy(buff, arr);
	char* str = strtok(buff, p);//yzq2076188013
	printf("%s\n", str);
	str = strtok(NULL, p);//qq
	printf("%s\n", str);
	str = strtok(NULL, p);// com
	printf("%s\n", str);
	return 0;
}

优雅使用

//巧妙使用for循环
int main()
{
	const char* p = "@.";//这个无需顺序区分
	char arr[] = "yzq2076188013@qq.com";
	char buff[50] = { 0 };
	strcpy(buff, arr);
	char* str = NULL;
	for (str = strtok(buff, p); str != NULL; str = strtok(NULL, p))
	{
		printf("%s\n",str);
	}
	return 0;
}

strerror

错误码

c语言中规定了一些信息
错误码 - 错误信息
0 - "No Error"
1 - 
2 - 
3 - 
...
 strerror  可以把错误码翻译成错误信息
int main()
{
	for (size_t i = 0; i < 10; i++)
	{
		printf("%s\n", strerror(i));
	}
	return 0;
}
No error
Operation not permitted
No such file or directory
No such process
Interrupted function call
Input/output error
No such device or address
Arg list too long
Exec format error
Bad file descriptor

fopen

FILE *fopen( const char *filename, const char *mode );

Each of these functions returns a pointer to the open file. A null pointer value indicates an error. 

c语言可以操作文件
打开文件 fopen
当库函数使用的时候,发生错误会把errno这个全局的错误变量设置为本次执行库函数产生的错误码
errno是c语言提供的一个全局变量,可以直接使用,放在errno.h文件中

//打开文件
FILE* pf = fopen("tesst.txt","r");//注意是""
if (pf == nullptr)
{
    //printf("fopen fail\n");
    printf("%s\n",strerror(errno));//No such file or directory
    exit(-1);
}

字符分类函数

函数如果他的参数符合下列条件就返回真
iscntrl任何控制字符
isspace空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’
isdigit十进制数字 0~9
isxdigit十六进制数字,包括所有十进制数字,小写字母a - f,大写字母A - F
islower小写字母a~z
isupper大写字母A~Z
isalpha字母a - z或A - Z
isalnum字母或者数字,a - z,A - Z,0 - 9
ispunct标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph任何图形字符
isprint任何可打印字符,包括图形字符和空白字符
#include<stdio.h>
#include<ctype.h>
int main()
{
	char ch = 'w';
	if (isspace(ch))
	{
		//空白字符返回非0
		printf("%d\n", isspace(ch));
	}
	else
	{
		printf("%d\n", isspace(ch));//0
		//非空白字符返回0
	}
	return 0;
}
#include<stdio.h>
#include<ctype.h>
int main()
{

    char ch = '0';
    if(ch >= '0' && ch <= '9')        
    {
        //...
    }
    if (isdigit(ch))
    {
        //...这样代码才更加统一
    }
    return 0;
}

字符转换

<stdlib.h> and <ctype.h>
int tolower ( int c );
int toupper ( int c );
#include<stdio.h>
#include<ctype.h>
int main()
{
	char ch = 0;
	ch = getchar();
	if (islower(ch))
	{
		ch = toupper(ch);//小写转大写
	}
	else
	{
		ch = tolower(ch);
	}
	printf("%c\n", ch);
	return 0;
}

2.内存函数

memcpy

内存拷贝函数
void *memcpy( void *dest, const void *src, size_t count );
void*可以接收任意类型的数据
    
memcpy returns the value of dest
count
Number of bytes to copy

#include<stdio.h>
#include<string.h>
int main()
{
    char arr1[] = "abcdef";
    char arr2[] = { 0 };
    strcpy(arr2,arr1);//拷贝字符串的
    int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr4[5] = { 0 };
    memcpy(arr4, arr3, 20);
    for (size_t i = 0; i < 5; i++)
    {
        printf("%d ", arr4[i]);//1 2 3 4 5
    }
    return 0;
}

模拟实现

#include<stdio.h>
#include<memory.h>
#include<string.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
    void* ret = dest;
    assert(dest && src);
    while (num--)
    {
        *(char*)dest = *(char*)src;//强制类型转换只是临时的,并不改变dest的类型
        dest = (char*)dest + 1;;//void*不能直接++
        src = (char*)src + 1;
    }
    return ret;
}
int main()
{
   
    int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr4[5] = { 0 };
    my_memcpy(arr4, arr3+5, 5*sizeof(arr3[0]));
    for (size_t i = 0; i < 5; i++)
    {
        printf("%d ", arr4[i]);//
    }
    return 0;
}

缺陷:

#include<stdio.h>
#include<memory.h>
#include<string.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
    void* ret = dest;
    assert(dest && src);
    while (num--)
    {
        *(char*)dest = *(char*)src;//强制类型转换只是临时的,并不改变dest的类型
        dest = (char*)dest + 1;;//void*不能直接++
        src = (char*)src + 1;
    }
    return ret;
}
void test1()
{
    //将arr3中的12345拷贝放到34567中
    int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr4[5] = { 0 };
    my_memcpy(arr3+2, arr3, 5 * sizeof(arr3[0]));
    for (size_t i = 0; i < 10; i++)
    {
        printf("%d ", arr3[i]);//结果竟然是1 2 1 2 1 2 1 8 9 10?
    }
}
int main()
{
   
    test1();
    return 0;
}
//因为是一个字节一个字节拷贝,先把1 2拷到3 4里面了 继续从3里面拷贝时,取出来的就还是1了

优化:使用memmove函数,拷贝内存时,可以重叠

库函数源码

#include	<string.h>

#ifdef _PIC16
far void *
memcpy(far void * d1, const void * s1, register size_t n)
#else /*  _PIC16 */
void *
memcpy(void * d1, const void * s1, register size_t n)
#endif /* _PIC16 */
{

#ifdef _PIC16
	register far char *	d;
#else  /* _PIC16 */
	register char *		d;
#endif /* _PIC16 */
	register const char *	s;

	s = s1;
	d = d1;
	while(n--)
		*d++ = *s++;
	return d1;
}

memmove

拷贝内存时,可以重叠

void *memmove( void *dest, const void *src, size_t count );
void test1()
{
    //将arr3中的12345拷贝放到34567中
    int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr4[5] = { 0 };
   // my_memcpy(arr3 + 2, arr3, 5 * sizeof(arr3[0]));
    memmove(arr3+2, arr3, 5 * sizeof(arr3[0]));
    for (size_t i = 0; i < 10; i++)
    {
        printf("%d ", arr3[i]);//1 2 1 2 3 4 5 8 9 10
    }
}
int main()
{
    test1();
    return 0;
}

其实,C语言只要求

memcpy能拷贝不重叠的内存空间就可以了
memmove去处理那些重叠内存拷贝
memmove包含了memcpy的功能
但VS的memcpy也能处理重叠内存拷贝

void test1()
{
    //将arr3中的12345拷贝放到34567中
    int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr4[5] = { 0 };
   // my_memcpy(arr3 + 2, arr3, 5 * sizeof(arr3[0]));
   // memmove(arr3 + 2, arr3, 5 * sizeof(arr3[0]));
    memcpy(arr3+2, arr3, 5 * sizeof(arr3[0]));
    for (size_t i = 0; i < 10; i++)
    {
        printf("%d ", arr3[i]);//1 2 1 2 3 4 5 8 9 10
    }
}
int main()
{
   
    test1();
    return 0;
}

模拟实现

image-20220122235715376

//当dest src没有交集时,从前往后/从后往前拷都行
#include<stdio.h>
#include<string.h>
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
    void* ret = dest;
    assert(dest && src);

    //src从前向后拷贝
    if (dest < src)
    {
        while (num--)
        {
            *(char*)dest = *(char*)src;
            dest = (char*)dest + 1;
            src = (char*)src + 1;
        }
    }

    //src从后向前拷贝
    else
    {
        //要找到最后一个字节再开始拷贝,注意小端存储
        while (num--)
        {
            //20就变成19
            *((char*)dest + num) = *((char*)src + num);
        }
    }
    return ret;
}
void test1()
{
    //将arr3中的12345拷贝放到34567中
    int arr3[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr4[5] = { 0 };
    my_memmove(arr3+2, arr3, 5 * sizeof(arr3[0]));
    for (size_t i = 0; i < 10; i++)
    {
        printf("%d ", arr3[i]);//1 2 1 2 3 4 5 8 9 10
    }
}
int main()
{
   
    test1();
    return 0;
}

image-20220225141708898

库函数源码

#include	<string.h>

#ifdef _PIC16
far void *
memmove(far void * d1, const void * s1, register size_t n)
#else  /* _PIC16 */
void *
memmove(void * d1, const void * s1, register size_t n)
#endif /* _PIC16 */
{

#ifdef _PIC16
	register far char *	d;
#else /*  _PIC16 */
	register char *		d;
#endif /* _PIC16 */
	register const char *	s;

	s = s1;
	d = d1;
	
#if	defined(_PIC12) || defined(_PIC14) || defined(_PIC14E)
	if((unsigned short)s < (unsigned short)d && (unsigned short)s+n > (unsigned short)d) {
#else
	if(sizeof(s) == sizeof(d) && s < d && s+n > d) {		/* overlap? */
#endif
		s += n;
		d += n;
		do			/* n != 0 */
			*--d = *--s;
		while(--n);
	} else if(n)
		do
			*d++ = *s++;
		while(--n);
	return d1;
}

其实这些都只是VS提供的参考代码,真正调用的其实是静态库里面的函数

memset

设置内存函数,以字节为单位设置内存

void *memset( void *dest, int c, size_t count );
#include<stdio.h>
#include<memory.h>
int main()
{
	char arr[20] = { 0 };
	memset(arr, 'x', 10);//前10个字节被改成x
	for (size_t i = 0; i < 20; i++)
	{
		printf("%c ", arr[i]);//x x x x x x x x x x
	}
	return 0;
}

//针对整型数组也行,整型4个字节,memset是一个字节一个字节修改的,需要注意大小端
#include<stdio.h>
#include<memory.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memset(arr, 0, 10);
	for (size_t i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);//0 0 0 4 5 6 7 8 9 10
	}
	//01 00 00 00	02 00 00 00	 03 00 00 00 ...小端存储
	//00 00 00 00   00 00 00 00  00 00 00 00 ...
	return 0;
}

memcmp

内存比较

int memcmp( const void *buf1, const void *buf2, size_t count );
Return Value : Relationship of First count Bytes of buf1 and buf2 
< 0 				buf1 less than buf2 
0 					buf1 identical to buf2 
> 0 				buf1 greater than buf2 
#include<stdio.h>
#include<memory.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,2,3,4,5 };
	int ret = memcmp(arr1, arr2, 8);
	printf("%d\n", ret);//0	前8个字节都相等返回0
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值