【C语言进阶】常见的字符串函数和内存函数详解

文章目录

  • 前言
  • 一、求字符串长度函数
    • 1.strlen()
  • 二、长度不受限制的字符串函数
    • 1.strcpy()
    • 2.strcat()
    • 3.strcmp()
  • 三、长度受限制的字符串函数
    • 1.strncpy()
    • 2.strncat()
    • 3.strncmp()
  • 四、字符串查找函数
    • 1.strstr()
    • 2.strtok()
    • 3.strerror()
    • 4.perror()
  • 五、字符函数
    • 1.字符分类函数
    • 2.字符转换函数
  • 六、内存操作函数
    • 1.memcpy()
    • 2.memmove()
    • 3.memcmp()
    • 4.memset()

前言

  • C语言本身是没有字符串类型的,字符串通常放在 常量字符串 中或者 字符数组 中。那C语言中是如何对字符和字符串进行操作的呢?这得学习本章要介绍的字符串函数。
  • 字符串常量 适用于那些对它不做修改的字符串函数。
  • 字符串函数和内存操作函数是面试中极为常见的一类题,我们不仅要学习它,还应该会模拟实现,这也是本章的内容。

如:腾讯面试题(下面会学习)

Memcpy和memmove的区别;


一、求字符串长度函数

1.strlen()

头文件:

size_t strlen(const char* str);//求字符串长度函数

说明:计算字符串 str的长度,直到空结束字符('\0'),但不包括空结束字符('\0')。
参数str:要计算长度的字符串。
返回值:返回字符串的长度,类型为无符号(unsigned)

实例演示:

int main()
{
	char arr1[] = "abcd";
	char arr2[] = { 'a', 'b', 'c','d' };
	printf("%d\n", strlen(arr1));
	printf("%d\n", strlen(arr2));
	return 0;
}

答案:
【C语言进阶】常见的字符串函数和内存函数详解_第1张图片
解释:

  • arr1数组在内存中存储的是 a b c d \0;arr2数组在内存中存储的是 a b c d。两者区别在于有无 \0
  • strlen()函数在计算字符串长度时,直到 \0 (不计 \0 )
    所以arr1的长度是4;而arr2,由于\0的位置未知,则打印出一个随机值

❗❗易错点
代码1:

int main()
{
	if (strlen("abc") - strlen("abcde") > 0)
		printf(">\n");
	else
		printf("<\n");
	printf("%d\n", 3 - 5);
	printf("%u\n", 3 - 5);
	return 0;
}

答案:
【C语言进阶】常见的字符串函数和内存函数详解_第2张图片

小朋友你是否有很多问号:这这这不是3 - 5 = -2嘛!?这不是 < 0吗?【C语言进阶】常见的字符串函数和内存函数详解_第3张图片

分析:
strlen()函数返回无符号整型数,3 - 5 底层下以补码来计算:

3-5等价于3+(-5)
00000000 00000000 00000000 00000011 -   3的补码
11111111 11111111 11111111 11111011 -  -5的补码
11111111 11111111 11111111 11111110 - 3-5的补码
11111111 11111111 11111111 11111110 -  -2的补码
0x ff       ff       ff       fe       -216进制
  • 无符号数 - 无符号数还是无符号数
  • 0xfffffffe用带符号整数的意义来解释是-2;用无符号数的意义来解释是一个很大的数字4294967294
  • 若想得到正常逻辑的结果,需要对其强制类型转换:(int)strlen("abc") - (int)strlen("abcde")

代码2:

int main()
{
	int a = 10;
	printf("%d", strlen(a));
	return 0;
}

【C语言进阶】常见的字符串函数和内存函数详解_第4张图片
分析:
strlen()实参和形参类型不兼容,在此会把整型变量a看作为地址,会造成非法访问。正确的是实参为字符串常量。

总结:

  • 对于strlen(),返回值是无符号整型,返回的是字符串 \0 前的字符个数(不含 \0)
  • 对于传给strlen()的字符串,字符串应以 \0 结尾,否则会返回随机值(因为 \0 位置未知);实参应为字符串,实质传递的是字符串首字符的地址

⚡ 模拟实现strlen()函数:
有三种方法实现:

  1. 计数器
  2. 指针-指针
  3. 递归

首先,我们应该了解strlen()函数,才能模仿它写出来,照葫芦画瓢:

  • my_strlen()返回值是size_t
  • my_strlen()仅是求字符串长度,不可对字符串进行修改,因此形参用const来修饰
  • assert(str);对str进行断言,防止访问空指针造成安全问题(程序崩溃)

1️⃣计数器版本:

size_t my_strlen(const char* str)
{
	assert(str);
	size_t count = 0;
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}

2️⃣指针-指针的结果是两指针之间的元素个数:

size_t my_strlen(const char* str)
{
	assert(str);
	char* begin = str;
	while (*str != '\0')
	{
		str++;
	}
	return str - begin;
}

3️⃣递归版本:

size_t my_strlen(const char* str)
{
	assert(str);
	if (*str != '\0')
		return 1 + my_strlen(str + 1);
	else
		return 0;
}

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

1.strcpy()

头文件:

char* strcpy(char* dest, const char* src);//字符串拷贝函数

说明:把 src 所指向的字符串(含 '\0')复制到 dest ,第一个 '\0' 是拷贝的结束标志。
❗注意:如果目标数组dest不够大,而源字符串src的长度又太长,可能会造成缓冲溢出的情况。
参数dest:指向用于存储复制内容的目标数组;
参数src:要复制的字符串。
返回值 :返回一个指向最终的目标字符串dest的字符型指针。

代码演示:

int main()
{
	char arr[20] = { 0 };
	strcpy(arr, "hello csdn!\n");
	printf("%s", arr);
	return 0;
}

运行结果:
将源字符串拷贝到目标数组
在这里插入图片描述
注意事项:
①拷贝是以第一个\0为结束标志
②目标空间要足够大,以装得下源字符串
③目标空间可变

1️⃣拷贝是以第一个\0为结束标志

int main()
{
	char arr1[15] = "##########";
	char arr2[] = "ab\0cd\0ef\0";
	printf("%s", strcpy(arr1, arr2));//ab
	return 0;
}

【C语言进阶】常见的字符串函数和内存函数详解_第5张图片
2️⃣目标空间要足够大,以装得下源字符串

int main()
{
	char arr1[] = "#####";
	char arr2[] = "hhhhhhha";
	printf("%s", strcpy(arr1, arr2));
	return 0;
}

【C语言进阶】常见的字符串函数和内存函数详解_第6张图片
3️⃣目标空间可变

int main()
{
	char* p = "########";//"########"在常量区,不可修改
	//or const char p[] = "########";//const修饰,不可改变
	char arr2[] = "csdn";
	printf("%s", strcpy(p, arr2));
	return 0;
}

【C语言进阶】常见的字符串函数和内存函数详解_第7张图片


⚡模拟实现strcpy()函数:
所谓的拷贝,不就是将字符串里的字符一个个复制粘贴吗?别忘了这可是我们最拿手的操作【C语言进阶】常见的字符串函数和内存函数详解_第8张图片
我们知道:
strcpy()返回char* ;源字符串src不可修改;拷贝是以第一个\0为结束标志 。

char* my_strcpy(char* dest, const char* src)
{
	assert(dest && src);//断言,防止访问空指针
	char* begin = dest;//begin记录首字符的地址
	while (*dest++ = *src++)//后置++
	{
		;
	}
	return begin;
}
int main()
{
	char arr1[10] = { 0 };
	char arr2[] = "csdn!";
	printf("%s", my_strcpy(arr1, arr2));//csdn!
	return 0;
}

2.strcat()

头文件:

char* strcat(char* dest, const char* src);//字符串追加函数

说明:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。
参数:

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

返回值:返回一个指向最终的目标字符串 dest 的指针。
代码演示:

int main()
{
	char arr1[10] = "CSDN!";
	char arr2[] = "YES";
	strcat(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

运行结果:
在这里插入图片描述
从监视中,可以看到:strcat将源字符串(包括\0),追加至目标字符串的结尾并覆盖掉目标空间的\0
【C语言进阶】常见的字符串函数和内存函数详解_第9张图片

注意事项:
①源字符串必须以\0结尾
②源字符串不能追加给自己
③目标空间必须足够大,以确保能够存放源字符串 src
④目标空间必须可变,即目标空间 src 不可以被 const 修饰

1️⃣源字符串必须以\0结尾
【C语言进阶】常见的字符串函数和内存函数详解_第10张图片

分析:源字符串不以\0结尾,导致非法访问

2️⃣源字符串不能追加给自己
【C语言进阶】常见的字符串函数和内存函数详解_第11张图片
监视:
【C语言进阶】常见的字符串函数和内存函数详解_第12张图片
可以看出strcat在疯狂地呐喊这"CSDN!",直到断气!
emmm正经点,就是strcat会一直往arr1追加"CSDN!",直到越界非法访问。

老规矩,我们用图来解释:
【C语言进阶】常见的字符串函数和内存函数详解_第13张图片
arr1给自己追加字符串时,会将字符串中的\0覆盖掉(第一个!后的\0被覆盖),导致没有\0追加上去,造成死循环直到越界非法访问。请添加图片描述


⚡模拟实现strcat()函数:

char* my_strcat(char* dest, const char* src)
{
	assert(src && dest);
	char* begin = dest;//记录目标字符串的起始地址
	//找到dest中的\0
	while (*dest)
	{
		dest++;
	}
	//到此,dest指向\0,开始追加
	while (*dest++ = *src++)
	{
		;
	}
	return begin;
}
int main()
{
	char arr1[20] = "CSDN!";
	char arr2[] = "YES";
	my_strcat(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

运行结果:
在这里插入图片描述


3.strcmp()

头文件:

int strcmp(const char* str1, const char* str2);//字符串比较函数

说明:两个字符串自左向右逐个字符相比(按 ASCII 值大小相比较),从第一对字符比起,若相等则比较下一对,直到遇到不同的字符或 \0 才停止
参数:

  • str1:要进行比较的第一个字符串
  • str2:要进行比较的第二个字符串

返回值:

  • str1 小于 str2,返回负数
  • str1 大于 str2,返回整数
  • str1 等于 str2,返回 0

代码演示:

int main()
{
	char* p1 = "abcd";
	char* p2 = "abxy";
	printf("%d\n", strcmp(p1, p2));
	return 0;
}

运行结果:
在这里插入图片描述
分析: 比较c和x,c(ASCII) < x(ASCII),返回负数-1


注意事项:
①str1和str2都要以 \0 作为字符串的结尾,否则会造成越界非法访问。
②根据编译器的不同,返回的结果也不同。在 VS2019 中,大于返回 1,等于返回 0,小于返回 -1。但在 Linux-gcc 中,大于返回正数,等于返回0,小于返回负数。
因此有下面建议:

// 不推荐 ❌
    if(strcmp(p1, p2) == 1)
        printf("p1 > p2");
 	else if(strcmp(p1, p2 == 0))
        printf("p1 == p2");
	else if(strcmp(p1, p2) == -1)
        printf("p1 < p2"); 
// 推荐 ✅
    if(strcmp(p1, p2) > 0)
        printf("p1 > p2");
    else if(strcmp(p1, p2 == 0))
        printf("p1 == p2");
    else if(strcmp(p1, p2) < -1)
        printf("p1 < p2");

⚡ 模拟实现strcmp()函数:

int my_strcmp(const char* str1, const char* str2)//仅比较不修改,都用const
{
	assert(str1 && str2);//防止访问空指针
	while (*str1 == *str2)
	{
		if (*str1 == '\0')//遇 \0 则停止
			return 0;
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

三、长度受限制的字符串函数

1.strncpy()

头文件:

char* strncpy(char* dest, const char* src, size_t n);//指定长度的字符串拷贝函数

说明:strncpy函数把 src 所指向的字符串复制到 dest,最多复制 n 个字符(遇到中间的\0停止,会小于n)。

注意事项:
①当 src 的长度小于 n 时,dest 的剩余部分将用 \0 填充。
②目标空间必须足够大,以确保能够存放源字符串 src。
③目标空间必须可变,即目标空间 src 不可以被 const 修饰。

1️⃣当 src 的长度小于 n 时,dest 的剩余部分将用 \0 填充。

int main()
{
	char arr1[10] = "abcde";
	char arr2[] = "xyz";
	strncpy(arr1, arr2, 5);//3 < 5
	printf("%s\n", arr1);
	return 0;
}

【C语言进阶】常见的字符串函数和内存函数详解_第14张图片
2️⃣3️⃣略(上面已讲)


⚡模拟实现strncpy()函数:

char* my_strncpy(char* dest, const char* src, size_t n)
{
	assert(dest && src);
	char* begin = dest;
	while (n && (*dest = *src))
	{
		dest++;
		src++;
		n--;
	}
	while (n--)
	{
		*dest = '\0';
	}
	return begin;
}
int main()
{
	char arr1[10] = "abcde";
	char arr2[] = "xyz";
	my_strncpy(arr1, arr2, 5);//3 < 5
	printf("%s\n", arr1);
	return 0;
}

运行结果:
在这里插入图片描述


2.strncat()

头文件:

char* strncat(char* dest, const char* src, size_t n);//指定长度的字符串追加函数

说明:源字符串最多追加n个字符到目标空间。
代码演示:

int main()
{
	char arr1[] = "CSDN!\0xxxx";
	char arr2[] = "YES";
	strncat(arr1, arr2, 5);
	printf("%s\n", arr1);
	return 0;
}

①n大于源字符串,追加至源字符串的 \0 停止追加;
【C语言进阶】常见的字符串函数和内存函数详解_第15张图片

②n小于源字符串,追加完主动补 \0。

int main()
{
	char arr1[] = "CSDN!\0xxxx";
	char arr2[] = "YES";
	strncat(arr1, arr2, 1);
	printf("%s\n", arr1);
	return 0;
}

【C语言进阶】常见的字符串函数和内存函数详解_第16张图片
注意事项: 与strcat不同的是:字符串可通过strncat给自己追加,因为追加完会补 \0


⚡模拟实现strncat()函数:

char* my_strncat(char* dest, const char* src, size_t n)
{
	assert(dest && src);
	char* begin = dest;
	//找出dest的 \0
	while (*dest)
		dest++;
	//追加
	while (n && (*dest++ = *src++))
		n--;
	//n小于源字符串补0
	if (n == 0)
		*dest = '\0';
	return begin;
}

3.strncmp()

头文件:

int strncmp(const char* str1, const char* str2, size_t n);//指定长度的字符串比较函数

说明:把 str1 和 str2 进行比较(按 ASCII 值大小),最多比较前 n 个字符。

代码演示:

int main()
{
	char* p1 = "abcde";
	char* p2 = "abxyz";
	printf("%d\n", strncmp(p1, p2, 3));
	return 0;
}

运行结果:
在这里插入图片描述


⚡ 模拟实现strncmp()函数:

int my_strncmp(const char* str1, const char* str2, size_t n)
{
	assert(str1 && str2);
	while (n-- && (*str1 == *str2))
	{
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

四、字符串查找函数

1.strstr()

头文件:

char* strstr(const char* string, const char* substring);//字符串查找子串函数

说明:字符串 string 中查找第一次出现字符串 substring 的位置,不包含终止符 ‘\0’。该函数返回在 string 中第一次出现 substring 字符串的位置,若未找到则返回 NULL。


⚡ 模拟实现strstr()函数:

char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	if (*str2 == '\0')
	{
		return NULL;//子串为空,返回NULL
	}
	char* s1;
	char* s2;
	char* cp = str1;
	while (*cp != '\0')
	{
		s1 = cp;
		s2 = str2;
		while (*s1 == *s2 && *s1 != '\0' && *s2 != '\0')
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return cp;
		}
		cp++;
	}
	return NULL;
}

拓展:
KMP算法 - 在字符串中查找子字符串的一种算法


2.strtok()

头文件:

char* strtok(char* str, const char* delim);//分割字符串函数

说明:将字符串 str 分解为一组字符串,delim 为分隔符集合。
原理:找到分隔符,并用 ‘\0’ 代替,即字符串的结束符。
参数:

  • str:一个或多个分隔符标记组成的字符串。
  • delim:包含分隔符的 C 字符串。

解释分隔符和标记:
. | - @等等称为分隔符;
对于www.csdn.net,由分隔符 . 所分割出来的子字符串被称为标记,如www,csdn,net

返回值:从 str 开头开始的一个个被分割的串的指针,当没有被分割的串时则返回NULL。


注意事项:
①这是调用一次strtok的结果:
【C语言进阶】常见的字符串函数和内存函数详解_第17张图片
②想要完整分割字符串,则要配合循环使用。因为stryok函数会自己保存指针,所以在下一次调用时,只需要给它传一个NULL即可。
③strtok函数会修改源字符串。我们一般会对源字符串拷贝一份,并让拷贝出来的去被切割(让替罪羊去送死)。【C语言进阶】常见的字符串函数和内存函数详解_第18张图片


正确使用:

int main()
{
	char web[] = "www.csdn.com";
	char* delim = ".@";//分隔符集合
	for (char* p = strtok(web, delim); p != NULL; p = strtok(NULL, delim))
	{
		printf("%s\n", p);
	}

	//证明源字符串被修改
	for (int i = 0; i < sizeof(web)/sizeof(web[0]); i++)
	{
		printf("%c", web[i]);
	}
	return 0;
}

运行结果:
【C语言进阶】常见的字符串函数和内存函数详解_第19张图片


3.strerror()

头文件:

char* strerror(int errnum);//字符串报错函数

说明: 从内部数组中搜索错误号 errnum,返回一个指向错误消息字符串的指针。strerror 生成的错误字符串取决于开发平台和编译器。
参数:errnum – 错误号,通常是errno
使用:errno是一个全局的错误码变量,当C语言的库函数在执行过程中发生错误,就会把对于的错误码赋值到errno中,通过调用strerror函数就可以把错误信息打印出来。


注意事项: 使用errno需要呼叫头文件

#include 
#include 
#include 
int main()
{
	printf("%s\n", strerror(errno));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	return 0;
}

运行结果:
【C语言进阶】常见的字符串函数和内存函数详解_第20张图片

常使用的场景在文件操作(后面会学习,关注我不迷路~)

#include
#include
#include
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
		printf("%s\n", strerror(errno));
	else
		printf("open file successfully");
	fclose(pf);//关闭文件
	pf = NULL;
	return 0;
}

4.perror()

头文件:

void perror(const char* str);//输出报错信息函数

说明:perror()用来将上一个函数发生错误的原因输出到标准设备 (stderr) 。首先输出字符串 str,后跟一个冒号,然后是一个空格。

联系:此错误原因依照全局变量 error 的值来决定要输出的字符串,perror函数只是将你输入的一些信息和现在的error所对应的错误一起输出。

代码演示:

#include
#include
//不需要#include
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
		perror("fopen");
	else
		printf("open file successfully");
	fclose(pf);//关闭文件
	pf = NULL;
	return 0;
}

总结: 在C语言编程中,通常使用perror()来输出错误信息,用strerror(errno)来获取错误信息。error是错误码,不同的错误码对应这不同的错误信息。


五、字符函数

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 任何可打印字符,包括图像字符和空白字符

isupper使用:

#include 
#include 
int main()
{
	char ch1 = 'A';
	int ret = isupper(ch1); // 判断ch1是否为大写
	printf("%d\n", ret);//1
	return 0;
}

2.字符转换函数

字符转换函数

函数 功能
tolower 大写转小写
toupper 小写转大写

tolower函数使用:

#include 
#include 
int main()
{
	char ch = tolower('H'); // 大写转小写
	putchar(ch);//h
	return 0;
}

六、内存操作函数

1.memcpy()

头文件:

void* memcpy(void* str1, const void* str2, size_t n);//内存拷贝函数

说明:从源存储区 str2 复制 n 个字节到目标存储区 str1,并返回一个指向目标存储区 str1 的指针。

代码演示:

// 将字符串复制到数组 dest 中
int main()
{
	const char src[50] = "www.casdn.net";
	char dest[50];

	memcpy(dest, src, strlen(src) + 1);
	printf("dest = %s\n", dest);

	return(0);
}

注意事项:
①memcpy函数的参数1,参数2都是void* 型,void*又称通用类型。
②与strcpy函数不同,memcpy函数遇到 ‘\0’ 不会停止。
③若源空间和目标空间有重叠,则拷贝时会覆盖源字符串内容。

1️⃣memcpy函数的参数1,参数2都是void* 型,void*又称通用类型。
用结构体类型来测试

struct Stu
{
	char name[20];
	int age;
};

int main()
{
	struct Stu arr1[5] = { 0 };
	struct Stu arr2[] = { {"zhangsan", 69}, {"lisi", 18} };

	memcpy(arr1, arr2, sizeof(arr2));
	printf("name:%s\nage:%d\n", arr1[0].name, arr1[0].age);
	return 0;
}

运行结果:
在这里插入图片描述
2️⃣与strcpy函数不同,memcpy函数遇到 ‘\0’ 不会停止。

int main()
{
	char arr1[10] = { 0 };
	char arr2[] = "abc\0def";

	memcpy(arr1, arr2, 6);
	return 0;
}

【C语言进阶】常见的字符串函数和内存函数详解_第21张图片
memcpy没有停止标志,它会一个个字节搬运。【C语言进阶】常见的字符串函数和内存函数详解_第22张图片

3️⃣若源空间和目标空间有重叠,则拷贝时会覆盖源字符串内容。

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memcpy(arr + 2, arr, 5 * sizeof(int));
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

运行结果:
【C语言进阶】常见的字符串函数和内存函数详解_第23张图片
我们画图分析:
【C语言进阶】常见的字符串函数和内存函数详解_第24张图片
我们看到的结果是编译器对memcpy函数优化后的结果。且C标准并不要求memcpy完成发生内存重叠的内容拷贝,但编译器也可能对其进行优化。对内存重叠的内容进行拷贝时,可以使用memmove函数。


⚡ 模拟实现memcpy函数:

void* my_memcpy(void* dest, const void* src, size_t n)
{
	void* begin = dest;
	while (n--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return begin;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memcpy(arr + 2, arr, 5 * sizeof(int));
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

运行结果:
【C语言进阶】常见的字符串函数和内存函数详解_第25张图片
运行结果也验证了上图中memcpy函数实际上的结果。


2.memmove()

头文件:

void* memmove(void* str1, const void* str2, size_t n);//内存移动函数

说明:将 str2 源区域的前 n 个字节拷贝到 str1 目标区域中。
如果目标区域和源区域有重叠的话,memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,但复制后源区域的内容会被更改。

注意事项:
①与memcpy() 不同, memmove() 函数处理的源区域和目标区域是可以重叠的。

C语言标准要求:memcpy() 用来处理不重叠的内存拷贝,而 memmove() 用来处理重叠内存的拷贝。

我就纳闷了,memmove() 既可以拷贝不重叠的数据,又可以拷贝重叠的数据,那我要你memcpy() 有何用?【C语言进阶】常见的字符串函数和内存函数详解_第26张图片
代码演示:

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

运行结果:
在这里插入图片描述


⚡模拟实现memmove()函数:
1.首先要清楚,memmove()函数要在memcpy()函数的基础上考虑数据重叠的情况。
2.那数据重叠是有几种呢?该如何保护源串在覆盖之前不被修改?

我们用图说话:
【C语言进阶】常见的字符串函数和内存函数详解_第27张图片
画图后,思路就清晰很多了。

void* my_memmove(void* dest, const* src, size_t n)
{
	assert(dest && src);
	char* begin = dest;
	if (dest > src) 
	{
		//从后往前
		while (n--) 
		{
			*((char*)dest + n) = *((char*)src + n);
		}
	}
	else 
	{
		//从前往后
		while (n--) 
		{
			*(char*)dest = *(char*)src;
			(char*)dest += 1;
			(char*)src += 1;
		}
	}
	return begin;
}

验证:
①dest <= src

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

运行结果:
【C语言进阶】常见的字符串函数和内存函数详解_第28张图片

②dest > src

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

运行结果:
【C语言进阶】常见的字符串函数和内存函数详解_第29张图片


3.memcmp()

头文件:

int memcmp(const void *str1, const void *str2, size_t n);//内存比较函数

说明:将存储区 str1 和存储区 str2 的前 n 个字节进行比较。

返回值:

  • str1 小于 str2,返回负数
  • str1 大于 str2,返回正数
  • str1 等于 str2,返回 0

注意事项:
与strcmp() 不同,memcmp() 遇到 \0 不会停止比较。


⚡模拟实现memcmp()函数:

int my_memcmp(const void* str1, const void* str2, size_t n) 
{
	assert(str1 && str2);
	if (!n)
		return 0;
	while (--n && (*(char*)str1 == *(char*)str2)) 
	{
		(char*)str1 += 1;
		(char*)str2 += 1;
	}
	return *(unsigned char*)str1 - *(unsigned char*)str2;
	//unsigned char*保证运算结果的符号正确(指针 - 指针内容)
}
int main()
{
	char* arr1 = "\0abc";
	char* arr2 = "\0abx";
	int ret = my_memcmp(arr1, arr2, 4);
	printf("%d\n", ret);
	return 0;
}

运行结果:
在这里插入图片描述


4.memset()

头文件:

void* memset(void* str, int c, size_t n);//内存初始化函数

说明:复制字符 c(一个无符号字符)到 str 所指向的字符串的前 n 个字节。
注意事项:
参数c以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。

代码演示:

int main()
{
	int arr[10] = { 0 };//共40字节
	memset(arr, 1, 20); //将前20个字节全部设置为1
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

运行结果:
【C语言进阶】常见的字符串函数和内存函数详解_第30张图片


⚡模拟实现memset()函数:
思路:将c转化为无符号字符(1个字节)逐个拷贝。

void* my_memset(void* str, int c, int n) 
{
	void* begin = str;
	while (n--) 
	{
		(*(unsigned char*)str) = (unsigned char)c; 
		(unsigned char*)str += 1;
	}
	return begin;
}
int main()
{
	int arr[10] = { 0 };//共40字节
	my_memset(arr, 1, 20); //将前20个字节全部设置为1
	return 0;
}

运行结果:
【C语言进阶】常见的字符串函数和内存函数详解_第31张图片
写文和制图不易,恳请各位铁汁们点赞收藏评论加关注,你们的支持是我坚持的动力~
【C语言进阶】常见的字符串函数和内存函数详解_第32张图片
……未完待续

参考资料:
【C语言教程 | 菜鸟教程】

✒️ 笔者:陈汉新

更新: 2022.3.24

❌ 勘误: 暂无

声明:由于作者水平有限,本文错误之处在所难免,敬请读者指正!

你可能感兴趣的:(C语言进阶,c语言,c++)