【C语言】字符函数和内存操作函数

大家好,我是苏貝,本篇博客带大家了解字符函数和内存操作函数,如果你觉得我写的还不错的话,可以给我一个赞?吗,感谢❤️
在这里插入图片描述


一.字符函数

1.1 字符分类函数

下面函数的头文件都是<ctype.h>

函数 如果他的参数符合下列条件就返回真即非0,不符合则返回0
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 任何可打印字符,包括图形字符和空白字符

其实字符分类函数很简单,下面就挑两个作为范例
范例1:

int main()
{
	int ret1 = islower('X');//X不是小写字母,返回0
	int ret2 = islower('f');//f是小写字母,返回非0的数
	printf("%d %d", ret1, ret2);
	return 0;
}

在这里插入图片描述

范例2:

int main()
{
	int ret1 = isxdigit('a');//a是16进制数字
	int ret2 = isxdigit('z');//z不是16进制数字,返回0
	int ret3 = isxdigit('4');//4是16进制数字
	printf("%d %d %d", ret1, ret2, ret3);
	return 0;
}

在这里插入图片描述

1.2 字符转换函数

字符转换函数只有两个,分别是tolower和toupper,它们的函数原型为:

int tolower ( int c ); //将大写字母转化为小写字母
int toupper ( int c );//将小写字母转化为大写字母

其中,参数的类型为int,是将字符的ASCII码值传过去,返回的也是字符的ASCII码值,所有返回类型也是int

范例1:

int main()
{
	int x = toupper('a');
	printf("%cn", x);
	x = tolower(x);
	printf("%cn", x);
	return 0;
}

在这里插入图片描述

范例2:
将数组arr中的字符全都变成小写字母

int main()
{
	char arr[] = "ABcdeFGhIjk";
	char* p = arr;
	while (*p)
	{
		if (isupper(*p))
		{
			*p = tolower(*p);
		}
		p++;
	}
	printf("%s", arr);

	return 0;
}

在这里插入图片描述


二.内存操作函数

2.1 memcpy

①函数介绍

void * memcpy ( void * destination, const void * source, size_t num );

memcpy函数的功能:从source的位置开始向后复制num个字节的数据到destination的内存位置。返回值:目标空间的起始位置。这与strcpy函数的功能很相似,那为什么我们已经有了strcpy函数还要设计memcpy函数呢?因为strcmp函数只能拷贝字符串,而内存空间可不是只有字符的,所有我们需要能拷贝非字符类型的函数,memcpy函数应运而生

细节:destination和source的类型都为void * :因为设计该函数的程序员不知道用户想要拷贝的类型,所有用void * 来接收所有类型的指针。source被const修饰:源内存块的内容不会被修改。num是指num个字节

注意:
1.这个函数在遇到 ‘’ 的时候并不会停下来。
2.如果source和destination有任何的重叠,复制的结果都是未定义的

范例:
把arr2中的前5个整型的数据拷贝放在arr1中

int main()
{
	int arr1[10] = { 0 };
	int arr2[] = { 1,2,3,4,5 };
	memcpy(arr1, arr2, 20);//20:5*sizeof(int)=5*4
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

在这里插入图片描述

②模拟实现

模拟函数的三个参数没有改变,依旧是只需要源内存块的起始地址和目标空间的起始地址以及要拷贝的字节数。先对dest和src断言,避免它们为空指针。用while循环num次,当num==0时退出循环。接下来就是将src的内容拷贝到dest中。因为src和dest指针都是void * 类型的,所有需要先强制类型转化为char * ,拷贝一次后,两指针都要指向下一个字节,因为强制类型转化是暂时的,所有完成赋值语句后,两指针还是void * 类型,所有为了指向下一个字节,我们需要再对它们强制类型转化为char * 类型,写成dest=(char*)dest + 1;那可以写成(char*)dest++;吗?不能,因为++的优先级高于强制类型转化。那可以写成++(char*)dest;吗?最好不要,有些编译器会报错

void* my_memcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}

③我们是否可以用所写的模拟函数让下面代码的数组arr从第5个元素开始往后的5个字符作为源内存块,将第3个元素开始往后的5个字符作为目标内存块,从source的位置开始向后复制num个字节的数据到destination的内存位置呢?即将数组arr变为{ 1,2,3,4,3,4,5,6,7,10}

void* my_memcpy(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	assert(dest && src);
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return ret;
}

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

答案是不能的,因为当程序想将5赋值给元素7时,5已经被3覆盖。同理,6已经被4覆盖,7已经被3覆盖
在这里插入图片描述
我们注意到这次的拷贝的源内存块和目标内存块是重叠的,而我们上面写的模拟函数的源内存块和目标内存块要求是不重叠的,所以使用失败。那我们如何解决源内存块和目标内存块重叠时成功拷贝呢?别急,有一个函数可以解决,那就是memmove函数

补充:其实有一些编译器的memcpy函数可以处理源内存块和目标内存块重叠的情况,但是并非所有,因此遇见两内存块重叠的情况时最好还是选择memmove函数


2.2 memmove

①函数介绍

void * memmove ( void * destination, const void * source, size_t num );

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

你瞧,memcpy解决不了的问题memmove轻松搞定

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

在这里插入图片描述

②模拟实现
以上面代码的arr数组举例,如果源内存块是从元素3开始的5个元素,目标内存块是从元素5开始的5个元素,此时如果要拷贝,为避免有些重要的值事先被覆盖,我们就要从后往前拷贝,即从源内存块的最后一个字节开始拷贝
在这里插入图片描述

如果源内存块是从元素3开始的5个元素,目标内存块是从元素1开始的5个元素,此时如果要拷贝我们就要从前往后拷贝
在这里插入图片描述

如果源内存块和目标内存块无重叠,那么拷贝既可以从前往后也可以从后往前
在这里插入图片描述

所以我们可以这样想:
1.dest<src时,从前往后拷贝
2.dest>src时,从后往前拷贝

dest<src时,与上面的memcpy模拟实现的思路相同,不再赘述。现在我们来思考如何从后往前拷贝。我们依旧用while循环,循环num次,当num==0时退出循环,while(num)。从后往前拷贝,就是将源内存块的最后一个字节拷贝到目标内存块的最后一个字节,再将源内存块的倒数第二个字节拷贝到目标内存块的倒数第二个字节……本题中,num=20,所以源内存块的最后一个字节的地址是(char*)src+19,目标内存块的最后一个字节的地址是(char*)dest+19。源内存块的倒数第二个字节的地址是(char*)src+18,目标内存块的倒数第二个字节的地址是(char*)dest+18。我们发现,第一次循环的时候19=num(20)-1,经历一次循环,num–=19;第二次循环的时候18=num(19)-1,经历一次循环,num–=18;那我们是不是在while循环的条件中写成while(num–)呢?这样的话每次循环源内存块字节的地址=(char*)src+num,所以写成如下写法

void* my_memmove(char* dest, char* src, size_t num)
{
	assert(dest && src);
	void* ret=dest;
	if (dest < src)
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return ret;
}

2.3 memset

memset是以字节为单位设置内存的。比如我可以让字符数组arr=“hello world"中从第3个元素开始的5个字符都变成字符’x’,即变成"hexxxxxorld”。

void * memset ( void * ptr, int value, size_t num );

范例1:

int main()
{
	char arr[] = "hello world";
	memset(arr + 2, 'x', 5);
	printf("%s", arr);
	return 0;
}

范例2:
下面代码的目的是用memset函数将arr数组中的元素全部改成1,可以做到吗?

int main()
{
	int arr[5] = { 0 };
	memset(arr, 1, 20);
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

如果你觉得可以的话,请看看最后的结果吧!为什么不是我们所想的全是1呢?

在这里插入图片描述
经过调试我们可以看到,调用完成memset后,数组arr的每个字节都由0变1,导致每个整型元素都变为0x01010101,所以不能用memset函数将arr数组中的元素全部改成1。所以memset更适用于字符数组
在这里插入图片描述

但是可以将数组中的每个元素都变为0,因为将每个字节都变为0即每个整形元素都为0

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

在这里插入图片描述


2.4 memcmp

int memcmp ( const void * ptr1,const void * ptr2,size_t num );

memcmp函数是比较从ptr1和ptr2指针开始的num个字节的大小,如果 * ptr1> * ptr2,返回一个正数;如果 * ptr1== * ptr2,返回0;如果 * ptr1< * ptr2,返回一个负数。依旧是比较以字节为单位进行比较

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	//假如是小端存储
	//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
	int arr2[] = { 1,2,0x11223304 };
	//01 00 00 00 02 00 00 00 04 33 22 11
	int ret = memcmp(arr1, arr2, 9);
	printf("%d", ret);
	return 0;
}

用memcmp函数时,切记不要拿数组中的元素直接比较,我们要看它们各自的存储情况。本题两个数组在前8个字节都相等,看第9个字节,03<04,所以前面<后面,返回一个负数
在这里插入图片描述


好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞?吗,感谢看到这里,我们下篇博客见❤️