C语言字符串函数,字符函数,内存操作函数

news2024/9/20 5:58:24

提示: 本篇文章涉及到以下内容:
求字符串长度
strlen
长度不受限制的字符串函数(被VS认为不安全,就像scanf)–>非法也要完成任务
strcpy 拷贝(将原字符串内容和\0全拷贝过去)
strcat 追加(先找到目标空间中的\0,然后把原字符串中的内容直到\0全拷贝过去,原字符串中的\0也会被追加过去)
strcmp 字符串内容比较
长度受限制的字符串函数介绍
strncpy 拷贝(字符不够,0来凑)
strncat 追加(追加不够,不追了)
strncmp 字符串比较(可控制比较字符的个数)
字符串查找
strstr
strtok
错误信息报告
strerror
字符分类函数
iscntrl
isspace
isdigit
isxdigit
islower
isupper
isalpha
isalnum
ispunct
isgraph
isprint
字符转换函数
tolower
toupper
内存操作函数
memcpy
memmove
memset
memcmp


文章目录

  • 1.strlen 求字符串的长度
    • 注意要点:
    • 三种方法模拟实现strlen函数
      • (1)计数器
      • (2)递归
      • (3)指针-指针
  • 2.strcpy 字符串拷贝
    • 注意要点
    • 正确示范
    • 错误示范--->目标空间必须是可以修改的
    • strcpy的模拟实现
  • 3.strcat 字符串追加
    • 注意要点
    • 简单的应用
    • 模拟实现strcat
  • 4.strcmp 字符串内容比较
    • 模拟实现strcmp
  • 5.strncpy 字符串拷贝( 控制拷贝个数)
    • 注意要点
    • 简单应用
  • 6.strncat 追加(控制追加几个字符)
    • 简单应用
  • 7. strncmp 字符串比较(控制比较的字符个数)
    • 注意要点
    • strcat_s函数
  • 8.strstr 在一个字符串中找另一个字符串第一次出现的位置
    • 简单应用
    • 模拟实现strstr
  • 9.strtok 切割有标记符的字符串
    • 注意要点
    • 使用场景
    • 简单应用
    • 错误示范
  • 10.strerror 把错误码翻译成错误信息,返回的是错误信息的起始地址
    • 注意要点
    • 简单应用
  • 11.字符分类函数
    • 字符转换函数的简单使用
  • 12.memcpy 内存拷贝(仅两个不重叠的内存空间可用)
    • 注意要点
    • 简单应用
      • 将arr1中的前五个元素拷贝到arr2中
      • 将arr1中的前3个元素拷贝到arr2中
    • 模拟实现memecpy
      • 想将1,2,3,4,5拷贝到3,4,5,6,7的位置
  • 13.memmove 内存拷贝(两个重叠内存和不重叠内存都可以使用)
    • 注意要点
    • 模拟实现memmove
  • 14.memcmp 内存比较
    • 注意要点
    • 简单应用
  • 15.memset 内存设置函数
    • 简单应用
  • 总结

1.strlen 求字符串的长度

头文件 #include<string.h>
在这里插入图片描述

注意要点:

(1)字符串已经以\0作为结束标志,strlen函数返回的是在字符串中\0前面出现的字符个数(不包含\0)

char arr[]="abc\0def";
int len=strlen(arr);
printf("%d\n",len);  //3

(2)函数指向的字符串必须要以\0结束

char arr[3] = { 'a','b','c' };
	int len = strlen(arr);
	printf("%d\n", len);   //随机值

(3)注意函数的返回值为size_t,实物符号的(易错)

const char* str1 = "abcdef";
	const char* str2 = "bcd";
	if (strlen(str2) - strlen(str1) > 0) {
		printf("str2>str1\n");
	}
	else {
		printf("str2<str1\n");
	}
	return 0;

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

明明看着str2的长度比str1的长度短,为什么这里是str2>str1呢?
因为strlen函数的返回类型是size_t(无符号整型)
按说是3-6=-3,但是在内存中把-3这个结果当做无符号整型,也就是默认它为正数,正数的原码也就是它的补码,所以直接把它的补码当做原码打印出来(正数的原码反码补码全都一样,如果是负数要先转换成原码打印出来),所以结果一定是个>0的数字

三种方法模拟实现strlen函数

(1)计数器

//1.计数器
#include<assert.h>
#include<stdio.h>
#include<string.h>

//这里将my_strlen自定义函数的返回类型定义为int,当遇到strlen("abdcf")-strlen("shfudgfu")这种情况计算结果时不容易出错
int my_strlen(const char* str) {  //求长度时只需要遍历字符串,不需要修改它,所以用const来保护指针所指向的内容
	int count = 0;
	assert(str);	//担心str是空指针,用assert()来断言一下str是不是空指针,保证指针的有效性
	while (*str != '\0') {
		count++;
		str++;
	}
	return count;
}
int main() {
	char arr[] = "bit";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

在这里插入图片描述

(2)递归

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

int main() {
	char arr[] = "bit";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

在这里插入图片描述

(3)指针-指针

int my_strlen(char* s) {
	char* p = s;
	while (*p != '\0') {
		p++;
	}
	return p - s;
}
int main() {
	char arr[] = "bit";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

在这里插入图片描述

2.strcpy 字符串拷贝

头文件#include<string.h>
在这里插入图片描述

注意要点

(1)源字符串必须以\0结束

比如char arr1[ ]=“abc\0def”;
将arr1拷贝到一个数组里面的时候,拷贝的是abc

(2)会将源字符串中的\0拷贝到目标空间

比如char arr2[3]={‘a’,‘b’,‘c’}; 无法正确拷贝

(3)目标空间必须足够大,以确保能存放源字符串(不够大时事实上也可以拷贝,运行之后程序会崩溃,非法也要完成任务)
(4)目标空间必须可变
(注意数组可以被改变,变量也可以被改变)

不能写成
char arr3[20]=arr1;//error
也不能写成
arr4=arr5;//error
注意是把数组的内容放到地址所指向的空间里面去,而不是放地址

正确示范

#include<stdio.h>
#include<string.h>
int main() {
	char arr1[] = "abcdef";
	char arr2[20] = {0};
	strcpy(arr2, arr1);
	printf("%s\n", arr2);

	return 0;

}

在这里插入图片描述

错误示范—>目标空间必须是可以修改的

int main() {
	char* p = "abcdefghi";  //p指向的是常量字符串,不能被修改
	char arr2[20] = "hehe";
	strcpy(p, arr2);
	printf("%s\n", p);

	return 0;
}

在这里插入图片描述

strcpy的模拟实现

模拟实现的功能:
(1)希望源头拷贝到目的地,目的地发生变化,感知它的变化,应返回目标空间的起始地址
(2)目的地里面数据发生变化,源头里数据不发生变化,所以用const保护起来

#include<assert.h>
char* my_strcpy(char* dest, const char* src) {
	char* ret = dest;         //在最开始的时候把目的地址保存起来
	assert(dest && src);	//断言保证这两个指针的有效性
	while (*dest++ == *src++) {       // \0的ASCII值是0,就不执行*dest=*src这条指令了
		;
	}
	return ret;
}
int main() {
	char arr1[] ="hehe";
	char arr2[20] = {0}; 
	my_strcpy(arr2,arr1);
	printf("%s\n", arr2);	//printf("%s\n", my_strcpy(arr2,arr1););

	return 0;

}

3.strcat 字符串追加

在这里插入图片描述
追加的时候是在目的地\0处,把\0覆盖,源头中的\0也会被追加过去–>所以目标空间内必须有\0(知道从哪里开始追加

注意要点

(1)源字符串必须以\0结束
(2)目标空间必须足够大,能容纳下源字符串的内容
(3)目标空间必须可修改
(4)字符串自己给自己追加,用strcat不适合

简单的应用

int main()
{
	char arr1[20] = "hello";
	char arr2[] = "world";
	strcat(arr1, arr2);
	printf("%s\n", arr1);

	return 0;
}

在这里插入图片描述

模拟实现strcat

my_strcat 实现思路:
先找到目的空间的第一个\0,然后拷贝字符串

#include<assert.h>
char* my_strcat(char* dest, const char* src) {
	assert(dest && src);        //assert断言保证两个指针的有效性
	char* ret = dest;			//最开始把目的地址保存起来
	while (*dest != '\0') {
		dest++;
	}
	while (*dest++ = *src++) {	//拷贝
		;
	}
	return ret;
}

主函数:(一个字符串在另一个字符串后面追加,追加成功)

int main() {
	char arr1[20] = "hello";
	char arr2[] = "world";
	my_strcat(arr1, arr2);
	printf("%s\n", arr1);

	return 0;
}

在这里插入图片描述
主函数:(自己给自己追加,追加失败)

int main() {
	char arr1[20] = "bit";
	my_strcat(arr1, arr1);
	printf("%s\n", arr1);

	return 0;
}  //error

在自己给自己追加的时候用strcat是有问题的,因为在追加的时候原字符串中的\0被覆盖,指针在原字符串中找不到\0,会一直陷入死循环追加,停不下来

4.strcmp 字符串内容比较

在这里插入图片描述
注意比较两个字符串的内容时不能用==,应使用strcmp

if(“abcdef”==“bcdefg”) //这里比较的是两个字符串首字符的地址,并不是字符串的内容

标准规定
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字

所以strcmp函数返回的结果要用有符号整型int来接收

如何判断两个字符串?
比的是对应位置上的ASCII值大小如果发现有一个不相等的,就可以比较出来了

a < b (a的ASCII值小于b的ASCII值)

模拟实现strcmp

#include<assert.h>
int my_strcmp(const char* str1, const char* str2) {
	assert(str1&&str2);	//保证两个指针非空
	while (*str1 == *str2) {		//2个字符串相等的情况在while循环里面
		if (*str1 == '\0') {
			return 0;
		}
		str1++;
		str2++;
	}
	if (*str1 > *str2)
		return 1;
	else
		return -1;

	//上面的if...else...太啰嗦,可以改为return *str1-*str2;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "acdf";
	int ret = my_strcmp(arr1, arr2);
	printf("%d\n", ret);

	return 0;
}

在这里插入图片描述
注意:
不要固定思维,函数返回的是大于0,小于0,0的数字,strcmp并不是只返回1,-1,0

5.strncpy 字符串拷贝( 控制拷贝个数)

(字符不够,0来凑)

在这里插入图片描述

注意要点

(1)拷贝num个字符从原字符串到目标空间
(2)如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后面追加0.直到num个

简单应用

int main() {
	char arr1[] = "abcdef";
	char arr2[20] = "xxxx";
	strncpy(arr2, arr1, 3);
	printf("%s\n", arr2);

	return 0;
}

在这里插入图片描述

6.strncat 追加(控制追加几个字符)

追加不够个数,就不追加了
可以自己给自己追加
在这里插入图片描述

简单应用

int main() {
	char arr1[20] = "hello";
	char arr2[] = "abcdef";
	strncat(arr1, arr2, 3);//将arr2中的3个字符追加到arr1后面
	printf("%s\n", arr1);

	return 0;
}

在这里插入图片描述

int main() {
	char arr1[20] = "hello \0xxxxxxxx";
	char arr2[] = "abcdef";
	strncat(arr1, arr2, 8); 
	printf("%s\n", arr1);
	return 0;
}

追加不够个数就不追加了

在这里插入图片描述

7. strncmp 字符串比较(控制比较的字符个数)

在这里插入图片描述
如果num大于要比较的2个字符串的长度,实际上根本不用比到num个就比出大小了

注意要点

比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完
在这里插入图片描述
在这里插入图片描述

strcat_s函数

定义
strcat_s(char* destination,size_t num,const char* source)

8.strstr 在一个字符串中找另一个字符串第一次出现的位置

在这里插入图片描述
在str1字符串中找str2字符串出现的位置
如果在str1中没有找到str,那么返回的就是空指针

简单应用

int main(){
	char arr1[] = "abcdef";
	char arr2[] = "bcd";
	char* p = strstr(arr1, arr2);
	if (p == NULL) {
		printf("找不到\n");
	}
	else {
		printf("%s\n", p);
	}
	
	return 0;
}

在这里插入图片描述

模拟实现strstr

char* my_strstr(const char* str1, const char* str2) {	//在查找的过程中不会修改str1,str2字符串,用const保护起来
	char* s1 = NULL;//用来维护遍历第一个字符串
	char* s2 = NULL;//用来维护遍历第二个字符串
	char* cp = (char*)str1;  //cp用来记录每次开始匹配的位置
							//str1是const修饰的指针`,是一个相对安全的指针.cp是char*类型,将相对安全的指针交给不安全的指针,权限被放大,编译器报警告,因此这里需要将str1进行强制类型转换一下
	while (*cp) {		//*cp指向被遍历的串,当*cp不是\0说明这个串还没完
		s1 = cp;		//将开始匹配的位置交给s1,让s1从这里开始遍历
		s2 = (char*)str2;	//将arr2的起始地址交给s2;str2这里也需要进行强制类型转换
		while (*s1 && *s2 && *s1 == *s2) {//当*s1指向\0会停下来,*s2指向\0也会停下来
										 //s1,s2指向的内容相等那么遍历指针同时++
			s1++;
			s2++;
		}
		if (*s2 == '\0') {		//当*s2指向\0说明子串匹配完
			return cp;			//返回最开始匹配的位置
		}
		cp++;	//s1内容和s2内容不相等,说明当前位置匹配是失败的,cp++,从下一个位置再开始匹配
	}
	//上面的循环结束说明第一个字符串里面的字符全都匹配了一遍也没有找到第二个字符串,那么此时结果就是找不到
	return NULL;		//如果在循环中始终没有找到子串,没有返回cp,return空指针说明找不到
}
int main() {
	char arr1[] = "abcdef";
	char arr2[] = "bcd";
	char* p = my_strstr(arr1, arr2);
	if (p == NULL) {
		printf("找不到\n");
	}
	else {
		printf("%s\n", p);
	}

	return 0;
}

在这里插入图片描述
思路分析:
在这里插入图片描述

(1)
1次匹配成功的情况:
首先看str2指向的内容和str1指向的内容是否相等,发现不相等,被查找的字符串str1中的指针++,继续查找,相等的情况下,两个指针同时往后走
(2)
多次匹配成功的情况:
在这种情况下,当第一个b匹配相等时,指针各自往后走.此时不相等,但是不意味着在后面就找不到arr2,而是说明从arr1中当前指针所在位置找不到arr2,但并不代表他的后面没有.所以应该从arr1当前指针所在位置的下一个位置处开始再匹配.arr1中的指针应回到刚才开始匹配的位置,arr2中的指针应回到arr2字符串的起始位置.
注意点(要记住的位置)
(1)arr1:要记住上一次是从哪里开始匹配的,如果匹配不相等,应从上一次开始匹配的位置的下一个位置开始匹配 -->设置cp指针记住这个位置
(2)arr2:当指针不断往后走应该记住这个字符串的起始位置,一旦匹配失败了,让它直接返回起始位置重新开始匹配 -->str2就是arr2字符串的起始位置,将它的地址赋值给遍历指针s2

9.strtok 切割有标记符的字符串

在这里插入图片描述

注意要点

(1)sep参数是个字符串,定义了用作分隔符的字符集合
(2)第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分隔的标记
(3)strtok函数找到str中的下一个标记,并将其用\0结尾(也就是说将标记符改为\0),返回一个指向这个标记的指针
(注意:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改)
(4)第一次调用:
strtok函数的第一个参数不为NULL,函数将找到str中的第一个标记,strtok函数将保存它在字符串中的位置
(5)第二次到第n次调用:
strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
(注意:NULL空指针要包含头文件#include<stdio.h>)
(6)最后一次调用的时候,字符串中不存在更多的标记,则返回NULL指针
(7)strtok函数特殊的地方在于:
它具有记忆的功能.调用之后还记得原字符串位置等信息

使用场景

例如:
(1)IP地址:192.168.10.5
能否将192和168和10和5这四个部分提取出来呢?
(2)邮箱:zhuimin@yeah.net
这里的@ 和. 叫做分隔符.
能否将被分割开的三部分提取出来呢?

简单应用

 int main() {
	char  arr[] = "zhuimin@yeah.net";
	char buf[30] = { 0 };
	strcpy(buf, arr);  //拷贝一份

	const char* p = "@."; //标记符集合的字符串

	char* str = strtok(buf, p);        //第一次分割:在buf中找到p中的第一个标记符,将其修改为\0,保存并记住这个标记符的位置,函数返回标记段的起始位置
	printf("%s\n", str);

	str = strtok(NULL, p);			 //第二次分割:strtok从第一次切割时保存的标记符位置处查找下一个标记
	printf("%s\n", str);

	str = strtok(NULL, p);			//第三次分割:strtok从第二次分割后保存的位置处查找下一个标记,发现此时字符串中没有更多的标记,结果返回空指针
	printf("%s\n", str);

	return 0;

}

在这里插入图片描述
如果是n次,也要写n次调用函数太麻烦了
优化一下

int main() {
	char  arr[] = "zhuimin@yeah.net";
	char buf[30] = { 0 };
	strcpy(buf, arr);  //拷贝一份

	const char* p = "@."; //标记符集合的字符串
	char* str = NULL;
	for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p)) {
		printf("%s\n", str);
	}
	return 0;

}

在这里插入图片描述

错误示范

在这里插入图片描述
因为这里的buf是常量字符串,有const修饰不能被修改,所以调用strtok时会出错.

10.strerror 把错误码翻译成错误信息,返回的是错误信息的起始地址

注意要点

其实:
(1)C语言的库函数在调用失败的时候,会将一个错误码存放在一个叫errno的变量中,当我们想知道调用哪个函数的时候发生了什么错误信息,就可以将errno中的错误码翻译成错误信息
(2)调用多个库函数的时候,大家是共用errno的,要及时去观察errno,下一次调用它的值会被修改
比如说:
库函数1调用失败//errno:3
库函数2调用成功//errno:0

简单应用

int main() {
	char* p = strerror(0);
	printf("%s\n", p);

	p = strerror(1);
	printf("%s\n", p);

	p = strerror(2);
	printf("%s\n", p);

	p = strerror(3);
	printf("%s\n", p);

	p = strerror(4);
	printf("%s\n", p);

	return 0;
}

在这里插入图片描述

int main() {
	//打开文件
	//打开文件的时候,如果文件的打开方式是"r"  (读)
	//文件存在则打开成功,文件不存在则打开失败
	//打开失败的时候,会返回NULL

	FILE* pf = fopen("text.txt", "r");
	if (pf == NULL) {
		printf("打开文件失败,原因是%s\n", strerror(errno));
		return 1;
	}

	//读写文件
	//....

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

注意:新建一个文本文档时看一下有没有有开启文件扩展名,又饿能创建成test.txt.txt
在这里插入图片描述

11.字符分类函数

头文件 <ctype.h>

函数 判断一个字符是不是
iscntrl 任何控制字符
isspace 空白字符:空格’ ‘;换页’\f’;换行’\n’;回车’\r’,制表符’\t’或者垂直制表符’\v’
isdigit 十进制数字’0’~‘9’(是数字字符的话,返回非0数字,不是数字字符,返回0)
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 任何可打印字符,包括图形字符和空白字符

字符转换
tolower 转换成小写字符
toupper 转换成大写字符

例如:
printf(“%c\n”,tolower(‘X’)); //x
printf(“%c\n”,toupper(‘x’)); //X

字符转换函数的简单使用

#include<stdio.h>
#include<ctype.h>
void test() {
	char arr[120] = { 0 };
	gets(arr);
	int i = 0;
	while (arr[i]){
		if (isupper(arr[i])) {
			arr[i] = tolower(arr[i]);
	}
		printf("%c", arr[i]);
		i++;
		}
}
int main() {
	test();

	return 0;

}

在这里插入图片描述

12.memcpy 内存拷贝(仅两个不重叠的内存空间可用)

在这里插入图片描述

注意要点

(1)函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置
(2)这个函数在遇到’\0’的时候并不会停下来
(3)如果source和destination有任何的重叠,复制的结果都是未定义的
(4)void* 通用类型指针,可以接受任意类型数据的地址,但是这种指针不能直接解引用和±操作

简单应用

将arr1中的前五个元素拷贝到arr2中

void test1() {
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[8] = { 0 };
	//将arr1中的前五个元素拷贝到arr2中
	//元素类型是int类型,五个元素总共是20字节
	memcpy(arr2, arr1, 20);
	int i = 0;
	while (arr2[i]){
		printf("%d ",arr2[i]);
		i++;
		}

}
int main() {
	test1();
	return 0;
}

在这里插入图片描述

将arr1中的前3个元素拷贝到arr2中

void test2() {
	float arr1[] = { 1.0f,2.0f,3.0f,4.0f,5.0f,6.0f,7.0f,8.0f,9.0f,10.0f };
	float arr2[8] = { 0 };
	//将arr1中的前3个元素拷贝到arr2中
	memcpy(arr2, arr1, 12);
	int i = 0;
	while (arr2[i]){
		printf("%f ",arr2[i]);
		i++;
		}
}
int main() {
	test2();
	return 0;
}

在这里插入图片描述

模拟实现memecpy

#include<assert.h>
void* my_memcpy(void* dst, const void* src, size_t count)
{
	void* ret = dst;  //临时变量保存dst地址
	assert(dst && src);  //断言两个指针不能为空指针
	while (count--) {   //每次处理一个字节,将指针强制类型转换成char*
		*(char*)dst = *(char*)src;  //一个字节一个字节的拷贝(注意:强制类型转换是临时的,转换完解引用dst还是void*类型
		dst = (char*)dst + 1;  //跳过一个字节后的地址给dst,dst是void类型,可以接收
		src = (char*)src + 1;
	}
	return ret;
}

在这里插入图片描述

想将1,2,3,4,5拷贝到3,4,5,6,7的位置

#include<assert.h>
void* my_memcpy(void* dst, const void* src, size_t count)
{
	void* ret = dst;  //临时变量保存dst地址
	assert(dst && src);  //断言两个指针不能为空指针
	while (count--) {   //每次处理一个字节,将指针强制类型转换成char*
		*(char*)dst = *(char*)src;  //一个字节一个字节的拷贝(注意:强制类型转换是临时的,转换完解引用dst还是void*类型
		dst = (char*)dst + 1;  //跳过一个字节后的地址给dst,dst是void类型,可以接收
		src = (char*)src + 1;
	}
	return ret;
}
void test3() {
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memcpy(arr1 + 2, arr1, 20);  //想要把1,2,3,4,5拷贝到3,4,5,6,7的位置
	int i = 0; for (i = 0; i < 10; i++) {
		printf("%d ", arr1[i]);
	}
}
int main() {
	test3();
	return 0;
}

预想效果: 1 2 1 2 3 4 5 8 9 10
实际效果: 1 2 1 2 1 2 1 8 9 10
在这里插入图片描述
为什么会是现在这样的效果呢?
在拷贝3到5的位置时的时候,其实原来3位置已经被之前拷贝的数据覆盖修改过了,变成1了
在这里插入图片描述

所以,我们发现在内存重叠的时候,使用memcpy会出现意想不到的效果

13.memmove 内存拷贝(两个重叠内存和不重叠内存都可以使用)

在这里插入图片描述

注意要点

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

memmove的功能包含memcpy
总的来说,mommove的功能100分,memcpy功能有60分
但是在vs编译器上二者都做到了100分的程度(也就是说在vs上二者功能一致)

模拟实现memmove

实现思路
(1)首先我们应该清楚:
在这里插入图片描述
(2)然后我们固定源数据位置,根据上面的图讨论dst在不同位置时应该是从前往后拷贝还是从后往前拷贝?
在这里插入图片描述

(3)分情况讨论:
方案A:
1,3区域:前–>后拷贝
2区域:后–>前拷贝
方案B:
1区域:前–>后拷贝
2,3区域:后–>前拷贝

上面两个方案,显然方案B比较简单
方案B代码规划:
if(dst<src)
{
前–>后拷贝; //==偷懒小技巧:my_memcpy模拟函数就是从前往后拷贝,可以把代码直接拿过来使用
}
else{
后–>前拷贝;
}

(4)代码实现

void my_memmove(void* dst,void* src,size_t num) {
	void* ret = dst;
	assert(dst && src);
	if (dst < src) {  //从前往后拷贝
		while (num--) {
			*(char*)dst = *(char*)src;
			dst = (char*)dst+1;
			src = (char*)src+1;
		}
	}
	else {			//从后往前拷贝
		while (num--) {
			*((char*)dst + num) = *((char*)src + num);
		}
	}
	return ret;
}

在这里插入图片描述

14.memcmp 内存比较

(任意给两块内存,就可以比较这两块内存中放的数据是否一样)
在这里插入图片描述

注意要点

(1)比较从ptr1和ptr2指针开始的num个字节
(2)返回值如下:
ptr1指针处的数据<ptr2指针处的数据 返回<0的数字
ptr1指针处的数据=ptr2指针处的数据 返回0
ptr1指针处的数据>ptr2指针处的数据 返回>0的数字

简单应用

void test5() {
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,2,3,4,6 };
	int ret1 = memcmp(arr1, arr2, 16);
	int ret2 = memcmp(arr1, arr2, 17);
	printf("%d\n", ret1);
	printf("%d\n", ret2);
}
int main() {
	test5();
	return 0;
}

在这里插入图片描述
在这里插入图片描述

15.memset 内存设置函数

在这里插入图片描述

把ptr指向的后面num个字节每个的内存块设置为value

简单应用

//将hello改为xxxxx
void* test6() {
	char arr[] = "hello world";
	memset(arr, 'x', 5);
	printf("%s\n", arr);
}
int main() {
	test6();
	return 0;
}

在这里插入图片描述

//将数组的10个元素全改为1
void* test7() {
	int arr[10] = { 0 };
	memset(arr, 1, sizeof(arr));
	int i = 0;
	for (i = 0; i < 10; i++) {
		printf("%s\n", arr[i]);
	}
}
int main() {
	test7();
	return 0;
}

这种写法是错误的,无法将数组的每个元素设置为1
因为arr中每个元素是整型(4字节),将每个字节的数据改为01,该元素(int类型)不是1

在这里插入图片描述
验证一下
(以%p打印2进制数字,8个一组;
以%x打印2进制数字,二进制数字前面的0不打印)
在这里插入图片描述
在这里插入图片描述


总结

本篇内容就介绍到这里啦,如果对大家有帮助的话,记得点赞收藏博客,关注后续的C语言学习内容哦~😉😉
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/466830.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【RPA开发】lxml 库之 etree 使用详解

通过 requests.get 方法获得 html 源代码后&#xff0c;可以通过 etree 进行解析&#xff0c;进而从源代码中提取关键信息。etree 同 Beautiful Soup 一样均可以解析 xml 和 html&#xff0c;两者不同之处在于&#xff1a;etree主要通过 xpath 进行定位&#xff0c;而 Beautifu…

基于Spring Boot+Vue 的校园健康系统设计与实现(附源码,文档)

一 简介 校园健康系统本质上是一个健康知识浏览和在线咨询的平台&#xff0c;从用户角度&#xff0c;系统包括大学生、医生和管理员。 二.主要技术 技术名作用Springboot后端框架Vue前端框架MySQL数据库 三 功能介绍 校园健康系统为用户提供医生预约服务&#xff0c;系统…

Clickhouse分布式表引擎(Distributed)查询核心原理解析

Clickhouse分布式表引擎&#xff08;Distributed&#xff09;查询核心原理解析 Clickhouse分布式表引擎&#xff08;Distributed&#xff09;写入核心原理解析Clickhouse分布式表引擎&#xff08;Distributed&#xff09;查询核心原理解析 与分布式数据写入时可以选择写分布式…

有哪家台灯好又便宜的适合学生党使用?真正合格的小学生台灯

都说眼睛是心灵的窗户&#xff0c;但是现在很多小朋友还没上初中&#xff0c;可能就早早的近视了。究其原因&#xff0c;除了和频繁观看电子屏幕密不可分之外&#xff0c;不良的用眼习惯也是一大关键。孩子写作业时不时揉眼睛的动作&#xff0c;其实只要时间一长&#xff0c;眼…

MYSQL prefer_order_index 的罪责

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

Linux运维之初识shell

一.补充知识点 1.系统定时任务 系统定时任务需要用到crontab命令&#xff0c;但是使用此命令有一个前提&#xff0c;即需要打开crond服务。为了不那么复杂&#xff0c;可以直接使用我之前学的systemctl命令重新启动crond服务。 语法&#xff1a;crontab [-e -l -r] 选项&am…

安装zsh-theme oh-my-zsh

安装zsh yum install zsh切换到zsh chsh -s /bin/zsh exec /bin/zsh重启并且查看 echo $SHELL//查看当前shell,如果显示/bin/zsh&#xff0c;则配置成功 安装oh my zsh sh -c "$(wget https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)"到…

浅谈Java线程

大家好&#xff0c;我是易安&#xff01;今天我们简单聊下Java线程这个话题。 在Java领域&#xff0c;实现并发程序的主要手段就是多线程。线程是操作系统里的一个概念&#xff0c;虽然各种不同的开发语言如Java、C#等都对其进行了封装&#xff0c;但是万变不离操作系统。Java语…

您的天气类APP会泄露隐私吗?

不知您是否有这样的习惯&#xff0c;在早上出门前、或是在规划次日的行程时&#xff0c;都会不自觉地掏出手机、点开天气类APP进行查看。此类APP有的是智能手机自带的&#xff0c;有的是从应用商店里下载并获取的第三方应用。无论是哪种&#xff0c;它们往往都有着一个共性&…

网络安全合规-汽车行业数据合规

个人信息&#xff0c;是指以电子或者其他方式记录的与已识别或者可识别的车主、驾驶人、乘车 人、车外人员等有关的各种信息&#xff0c;不包括匿名化处理后的信息。 敏感个人信息&#xff0c;是指一旦泄露或者非法使用&#xff0c;可能导致车主、驾驶人、乘车人、车外人员等受…

神策营销云时效性升级,秒级营销即刻开启

信息化时代&#xff0c;时效性成为企业营销与管理的重要竞争力之一。高时效营销能够帮助企业提高决策效率、降低成本&#xff0c;“争分夺秒”留住用户并给用户带来更好的体验&#xff0c;它是促成企业成功营销的关键。 为了帮助企业全面提升营销时效性&#xff0c;神策营销云即…

一次修改jar包中字节码文件内容的尝试

目录 背景解决办法确定修改位置得到字节码文件修改字节码文件组合jar包 背景 最近想实现按照分节符拆分doc / docx文档的功能&#xff0c;然后就找到了这篇文章Java 按节拆分 Word 文档&#xff0c;用的依赖是&#xff1a; <dependency><groupId>e-iceblue</g…

Arduno ESP8266接入OneNET实时显示DHT11数据

Arduno ESP8266接入OneNET实时显示DHT11数据 📌相关篇《OneNET云平台数据APP端查看说明》📍《Arduno ESP8266接入中移OneNet动态显示实时数据》✨上面一篇主要是验证数据上传可行性,这次采用DHT11温湿度传感器上传真实数据到云平台进行检测,同时使用SSD1306屏幕进行数据显…

简单聊聊目标检测新范式RT-DETR的骨干:HGNetv2

【前言】 本文版权属于GiantPandaCV&#xff0c;未经许可&#xff0c;请勿转账&#xff01; 前几天疯狂刷屏的RT-DETR赚足了眼球&#xff0c;在精度和速度上体现的优势和性价比远远高于YOLO&#xff0c;而今年ChatGPT、Sam的出现&#xff0c;也让一些吃瓜群众知乎CNN没有未来了…

第8章:树

1.树是什么 一种分层数据的抽象模型前端工作中常见的树包括&#xff1a;DOM树&#xff0c;级联选择(省市区)&#xff0c;树形控件&#xff0c;…javascript中没有树&#xff0c;但是可以用Object和Array构建树 4.树的常用操作&#xff1a;深度/广度优先遍历&#xff0c;先中后…

【传统方式部署zookeeper集群与迁移至k8s】

zookeeper简介&#xff1a; zk主要服务于分布式系统、配置管理、注册中心、集群管理等&#xff1b;为什么要迁移Zookeeper集群&#xff1b;存储kafka什么数据&#xff1a;kafka有多少节点、topic名称、协调kafka正常运行。ELKKafka收集k8s日志&#xff1b;一、传统方式部署zook…

浙江省区块链数字资产登记中心筹备会议顺利举行

4月25日下午&#xff0c;由浙江省区块链技术应用协会主办、西溪谷管委会、西湖区网联会协办的“浙江省区块链数字资产登记中心筹备会议”在西湖蚂蚁小镇多功能厅顺利举行。 出席本次筹备会议的有中国电子技术标准化研究院区块链研究室主任、IEEE 计算机 协会区块链和分布式记帐…

设计模式--桥接模式

传统方案解决手机操作问题分析 (1) 扩展性问题(类爆炸) 如果我们再增加手机的样式(全面屏) 就需要增加各个品牌手机的类 同样如果我们增加一个手机品牌 也要在各个手机样式类下增加 (2) 违反了单一职责原则 当我们增加手机样式时 要同时增加所有品牌的手机 增大了代码维护成本…

【STM32】基础知识 第九课 STM32启动

【STM32】基础知识 第九课 STM32启动 MAP 文件MAP 文件浅析MAP 文件组成atk_f103.map 文件 启动模式STM32 启动模式 (F1) STM32 启动过程启动文件介绍Reset_Handler 函数介绍堆栈简介 MAP 文件 MAP 文件是 MDK 编译代码后, 产生的集程序, 数据及 IO 空间的一种映射列表文件. 简…

谁是液冷行业真龙头?疯狂的液冷技术!

“人工智能领域AIGC”、“ChatGPT”、“数据特区”、“东数西算”、“数据中心”&#xff0c;可以说是2023年最热的概念&#xff0c;算力提升的背后&#xff0c;处理器的功耗越来越高&#xff0c;想发挥出处理器的最高性能&#xff0c;需要更高的散热效率。 算力井喷之下&…