字符函数以及内存函数讲解

前面已经讲过关于字符串函数strerror的使用,与该字符串函数功能类似的函数还有:perror 函数:

void perror ( const char * str );

该函数是用来打印错误信息的,该函数直接对错误信息进行操作,,而前面讲过的strerror函数,是先把错误码转化为错误信息,它不限制必须要进行打印错误信息,可以打印,也可以不打印,他只负责错误码到错误信息的转换过程。

 该函数的使用避免了将错误码转为错误信息的过程,直接将错误码对应的错误信息打印在屏幕上,不需要自己进行转化操作,除此之外,还可以在错误信息前面加上自定义内容, 该函数的作用包括两个部分,即,首先把错误码转自动转为错误信息,然后再打印出来自定义的信息以及错误信息,该函数需要引头文件#include,该函数与字符串函数strerror相比之下较为简单;

C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。

常量字符串适用于那些对它不做修改的字符串函数.

字符分类函数:

这些函数主要是用来操作字符的,而不是操作字符串的,针对字符而言,比如函数iscntrl来说,主要作用就是来判断一个字符是否是控制字符,如果是控制字符则返回真,如果不是控制字符则返回假,再如函数isspace而言,判断一个字符是否是空白字符,空白字符的分类见上图所示,如果该字符是空白字符,则返回真,若非空白字符,则返回假等等;

比如:

isdigit函数的使用:

判断一个字符是否是数字型字符:

int isdigit ( int c );

使用该函数传参某个字符时,实际上传过去的是该字符对应的ASCII码值,返回类型是int整型:

该函数需要引用头文件#include,,如果该字符是数字型字符时,则返回真,返回的是一个非0的int整型,该int整型数字不一定是int整型数字1,如果不是数字型字符,则返回假,即,int整型数字0;

islower函数的使用:

判断一个字符是否是小写字母a-z:

int islower ( int c );

当使用该函数传参某一个字符时,实际上传过去的是该字符对应的ASCII码值,返回类型是int整型:

该函数需要引用头文件#include,,如果该字符是小写字母a-z,那么返回值就为真,返回的是一个非0的int整型,不一定是int整型数字1,如果该字母不是小写字母a-z,那么返回值即为假,返回的是一个int整型0;

若不是小写字母a-z:

 若是小写字母a-z:

字符转换函数:

比如,把小写转为大写,或者把大写转为小写,均可以通过字符转换函数来实现:

int tolower ( int c ) —— 转小写;

int toupper ( int c ) —— 转大写;

仅适用于字母型字符 大小写 之间的转换,,因为只有字母型字符才存在大小写之分,这两个函数都需要引头文件:#include;

首先,当使用这两个函数进行传参时,由于,这两个函数只适用于字母型字符,,传参时,本质上传过去的是该字符对应的ASCII码值,,比如,要将字符A通过函数tolower来转化为字符a,,即转小写,,传参时本质上传过去的是字符A的ASCII码值,,如果通过转小写tolower函数的话,就会把该大写字符对应的ASCII码值转为该大写字符对应的小写字符的ASCII码值,然后再把此时小写字符的ASCII码值返回,所以,得到的就是字符a的ASCII码值:

 通过查表的,int整型97即为字符a对应的ASCII码值,,然后再以%c打印,就可以打印出字符a,如果,把一个小写字母型字符再通过tolower函数转小写,此时,返回的还是小写字符的ASCII码值,然后再以%c进行打印即可,,同理可知,小写转大写的原理是一样得,

 小写转大写同理可得:

 例题:

内存函数:

memcpy

函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。

这个函数在遇到 '\0' 的时候并不会停下来。

如果source和destination有任何的重叠,复制的结果都是未定义的。

我们之前学的字符串拷贝存在一定的缺陷,strcpy只能拷贝字符数组或者是字符串,不能拷贝整型数组,在传参的时候会出现问题,所以不可以拷贝整型数组中的内容。其次就是在拷贝字符串的时候,遇到 \0 就停止了,比如要拷贝整型数组 int arr[10]={1,2,3,4,5,6,7,8,9,10},,如果使用strcpy进行拷贝整型数组的话,如果是小端存储,那么该数组在内存中的存储即为:01 00 00 00 02 00 00 00 03 00.....,,当使用strcpy拷贝整型数组时,只能一个字节一个字节的去拷贝,,只能把整型数组首元素中的四个字节中的第一个字节 拷贝过去, 然后遇到 \0 就停止了,,所以也是无法使用strcpy对整型数组进行拷贝的,但是,如果现在需要对一个整型数组中的内容进行拷贝,应该怎么做呢?

所以现在就需要该内存函数,针对内存块进行拷贝,只关注内存块,不关注该内存块中放的是什么类型的数据,所以使用memcpy就可以对任意类型的内容进行拷贝了:

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

其中,destination和source都是指针,即,无具体类型的指针,其中,我们把源头中的内容拷贝到目的地,,那么,源头中的内容不会发生改变,所以,使用const进行修饰,放在*的左边,修饰的是* source,,即,source所指的内容不会发生改变,但是,目的地的内容是会发生改变的,,然后,size_t num指的是拷贝过去的内容的大小,单位是字节, 由于大小不可能是负数,所以使用无符号的size_t进行修饰,其中,size_t 等价于 unsigned int ,,其中,memcpy函数需要引头文件#include<string>;

void* 类型的指针,是不可以直接解引用和++或者--操作的。

库函数memcpy的模拟实现:

拓展:

如果想把数组arr1中的前5个数据,1,2,3,4,5 仍然放在数组arr1中3,4,5,6,7 的位置上,最终想得到:1,2,1,2,3,4,5,8,9,10 应该怎么做呢?

则应该怎么传参呢?

 我们发现,和我们预期的结果不太一样,,这是什么原因造成的呢?

这是因为我们使用memcpy对源头拷贝到目的地时出现了重叠部分,即上上图中的数据 3,4,5 这是我们的重叠部分,如果存在重叠部分话的,那么拷贝就会出现问题,为什么呢?

这是因为,当我们把源头拷贝到目的地时,由于出现了重叠部分,即,源头数据拷贝到目的地时就会把我们源头数据的内容进行了修改:

当我们把src中的1拷贝到dest中的3上的时候,dest上3就会被改为1,当把src中的2拷贝到dest中的4上的时候,dest上的4就会被改为2,,接下来再进行拷贝时,按说应该把src中的3拷贝到dest中的5上面,,但是,现在src中的3已经被修改为1,所以就相当于是,把1拷贝到了dest中的5上,同理,dest中的6就会被修改为2,dest中的7就会被修改为1,所以,打印出来的结果即为:1,2,1,2,1,8,9,10,,这就出现了错误,所以,当存在重叠的时候并且再按照从前往后的顺序拷贝的时候,,就会把重叠部分的内容改掉,进而导致出现错误,所以:

1. 标准下的memcpy函数应该拷贝不重叠的内存,就比如,把arr1的内容拷贝到arr2里面,这是该函数可以实现的,或者是拷贝重叠内存并且也需要从前往后拷贝的情况。

2. memmove函数可以处理任何重叠部分的情况。

memmove

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

比较memcpy可知,memmove函数与memcpy函数的参数部分是一样的,所以可以直接使用,

此时,就能达到我们想要的结果,所以,,memmove确实能够解决任何内存重叠的情况,就比如上题中的从后往前拷贝的情况。

memmove的模拟实现:

该函数是如何实现能够拷贝重叠部分的内存呢?

我们发现,,如果还按照从前往后的顺序进行拷贝的话,就会把重叠部分内容改变掉,此时重叠部分就是数据3,4,5,所以我们可以使用从后往前的顺序进行拷贝,先对src中的3,4,5进行拷贝,这样就不用担心重叠部分被修改从而拷贝的内容被修改的情况了,所以,当对源数据进行拷贝的时候,,既可以从前向后拷贝,也可以从后向前拷贝,两种情况都是可以的,上图只能从后往前拷贝才不会出错,如果对调上图中的src和dest的话,,要想不会出错就只能采用从前往后的顺序进行拷贝。

因此可以得出一个结论,,当拷贝源数据时,,如果存在重叠部分的内存时,,要先从src中的重叠的部分所在的那一侧向src中不是重叠部分的那一侧进行拷贝。

模拟实现代码:

模拟实现代码方法二:

上面我们已经知道,,当我们使用my_mymcpy模拟实现的时候,,如果出现了重叠部分且需要从后往前拷贝的情况时,,拷贝后的结果是错误的,,但是,如果我直接使用VS下的mymcpy库函数对重叠部分进行拷贝,应该会怎么样呢?

我们发现,即使存在重叠部分且需要从后往前拷贝的时候,,但是结果却是正确的,,这是为什么呢?,,难道我们对标准下的mymcpy函数的模拟实现产生错误了吗?,,其实不是,对于标准下的memcpy函数来说,他确实能够用来拷贝不重叠的部分的内存或者是只能用来拷贝重叠内存且需要从前往后拷贝的情况,现在上图我们看到的结果是因为这是在VS环境下才可以跑过的,是因为VS编译器对其进行了优化,,但只能说明VS编译器很强大,但是对于标准下的库函数mymcpy来说,他没有这个功能,他不能拷贝重叠内存且需要从后往前拷贝的情况,所以,我们对该标准下的库函数的模拟实现也没有做错,只是VS编译器比较强大而已。

对于在VS编译器下使用该库函数,即VS下的memcpy,他是按照库函数memmove函数来实现的,但是标准下的库函数memcpy只需要满足能够拷贝不重叠内存和重叠内存且需要从前往后拷贝的情况即可,,这只是在VS编译器下,VS下的库函数memcpy能够拷贝重叠内存且从后往前拷贝的情况和不重叠内存,,但是不能说明在别的编译器下库函数memcpy也能如此,因为,只有在VS下的库函数memcpy被按照库函数memmove来实现的,在其他编译器下,该编译器下的库函数memcpy并没有按照库函数memmove去实现,有可能就是根据标准下的库函数memcpy去实现的,所以,此时再去拷贝重叠部分且从后向前拷贝的情况时,有可能就会出现错误了。

总结:

首选要清楚,一共分为:标准下的库函数memcpy,,,my_memcpy,,,VS下的库函数memcpy,,,库函数memmove,,,my_memmove这5种,,其中,标准下的库函数memcpy和my_memcpy功能是一致的,,他们不能说完全处理不了重叠内存,因为我们知道,对于标准下的库函数memcpy和my_mymcpy,他们只能从前往后进行拷贝,如果遇到重叠内存需要从后往前进行拷贝才能满足要求的时候此时标准下的库函数memcpy和my_mymcpy就不能满足使用要求了,但是,如果遇到重叠内存仍是需要从前往后进行拷贝的时候,此时标准下的库函数memcpy和my_mymcpy仍可以用来处理该重叠内存,比如:

 这就说明了,当遇到重叠内存且需要从前往后拷贝的时候,使用标准下的库函数memcpy和my_mymcpy仍可以用来处理该重叠内存的。

对于不重叠内存,我们知道,既可以使用从前往后拷贝,也可以使用从后往前拷贝,所以,标准下的库函数memcpy和my_mymcpy是可以用来处理不重叠内存的。

对于库函数memmove和my_memmove函数来说,他们可以从前往后拷贝,也可以从后往前拷贝,所以,他们可以处理全部任何的重叠内存,因为,重叠内存如果想被正确处理的话,可能会使用到从前往后,也可能会用到从后往前,但不管什么情况,,库函数memmove和my_memmove函数都可以满足要求,所以说,库函数memmove和my_memmove函数可以用来处理任何重叠的内存,对于不重叠内存来说,我们知道,既可以使用从前往后拷贝,也可以使用从后往前拷贝,所以,库函数memmove和my_memmove函数是可以来处理不重叠内存的。

对于VS下的库函数memcpy的具体实现过程是按照库函数memmove和my_memmove函数来实现的,所以可以把VS下的库函数memcpy与库函数memmove和my_memmove函数归为一类。

我们不考虑特殊的情况,即在VS下的库函数memcpy,,因为他不通用,,对于标准下的库函数memcpy和my_memcpy库函数memmove和my_memmove函数来说,他们都可以处理不重叠的内存的情况,,但是,对于标准下的库函数memcpy和my_memcpy和库函数来说,它只能够处理一部分重叠内存的情况,而对于库函数memmove和my_memmove函数来说,却能够处理任何重叠的情况,,所以,在以后的使用中,,我们还是把不重叠部分的内存交给memcpy去拷贝,把重叠部分的内存交给memmove去拷贝。

例题:

memmcmp

内存比较函数:用来比较两块内存中的数据是否一样,

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

其中,num的单位是字节,最多可以比较num个字节,如果在此之前,已经比较出来了结果,即两者不相同的时候,那么此次比较就结束了。

要注意:对于之前的字符比较函数strncmp来说,他可以比较两个字符数组,也可以比较两个字符串,因为字符比较是以字节为单位进行比较的,所以strncmp函数的第三个参数num的单位也是字节,对于该函数,也是最多可以比较num个字节,如果在此之前,进行比较的两者已经出现了不同的情况或者某一方或双方都遇到了字符 \0 的话,就会停止了,不再往下继续进行比较了,具体请见笔记,但是这里的memcmp内存函数,它也是最多可以比较num个字节,,如果在num个字节之前出现了两者不同的情况,比较就不再进行了,,但是不同的是,如果是字符进行比较,字符数组或字符串,,即使是字符比较遇到了字符 \0 也是不会停止的,,,这一点和strncmp函数是不同的,,memcmp内存函数会提前停止的情况只有一种,即,要么你在num个字节前面已经出现了不同的情况,要么就直接把num个字节全部比较完才可以,,还要知道,只有比较字符的时候,在strncmp函数里面,遇到字符 \0 会停止比较,,,在内存函数memcmp里面比较除字符类型以外的其他类型的时候,就更没有某种结束的标志了。

比如:

 由图可知,当使用memcmp内存函数进行对字符的比较的时候,即使遇到了字符 \0 也是不会停止的,如果停止的话,会返回0,但是现在返回的是-1,,说明,遇到了字符 \0 是不会停止的,我们要求比较5个字节,前4个都相同,且遇到字符 \0 不停止,,所以,要比较第5个字节,发现,c<d

,所以返回一个小于0的数,系统会默认该小于0的数规定为-1。

由结果可知,和之前的strcmp函数类似,相等就返回0,如果arr1>arr2,则返回一个正数,一般是1,,若小于,则返回一个负数,一般是 -1。

 返回值:

 当:

此时,num=16,即最多比较16个byte,对于内存函数memcmp来说的话,要比较的字节个数小于num个数的时候能够停止再比较的情况只有一种,就是前面已经出现了不同的情况,此时,要比较16个字节,相当于是要比较4个int整型, 而对于数组arr2来说,他里面只有3个int整型, 即12byte,但是,两个数组的前12byte都是相等的,所以还要继续把剩下的4byte比较完才可以,,要知道,数组是在内存中某一块区域上进行开辟的,上面的数组arr1,arr2中的内容只有我们能看到的这些,,他们所占的内存只是整个内存空间的一部分,现在要比较16个字节的话,对于aerr2来说,该数组一共才有12byte,不够用,所以当比较16个字节的时候,后4个字节的话,会出来arr2数组,来比较在大内存块里面, 且在arr2数组外面的后面的4个字节,来进行比较,但是由于arr2外面,大内存中存储的内容是不确定的,,所以对于arr2来说,要比较的后4个byte,是不确定的,随机的,所以拿着这4个不确定的字节去和arr1里面后4byte进行比较的时候,得出来的结果就是一个随机值,不知道谁大谁小,所以此时的-1就是一个随机值,,对于内存函数memcmp来说,只有在num个字节之前出现了不同才会停止,其他情况一律不停止,直到把num个字节全部比较完比才可以,不是说在比较完arr2中的3以后,就停止比较了,这是不对的。

再如:此时的结果1也是一个随机值。

拓展:

对于使用strlen函数求字符串长度的时候,他会以字符 \0 为停止的标志,计算出字符 \0 之前有多少个字符,并把该数值返回来,但是上图是字符数组,他里面只有我们能够看到的这些数据,发现不存在字符\0,,其次,因为数组是在整个内存中间开辟空间,所以该数组是整个内存空间的一个子集,当使用strlen函数求其字符串长度的时候,传过来的是数组首元素的地址,他会顺着往后找字符 \0 ,直到遍历完整个数组都没有发现字符 \0,,但是没有遇到 \0 它不会停止,,他就会出去该字符数组来到该字符数组后面,且在整个内存空间中的位置,一直往后找字符 \0 ,但是在此时的内存空间中,里面的数据是未知的,不知道字符\0会出现在哪里,但是其中是一定有字符\0的,只是不知道其出现的位置,,所以,strlen会一直往后找,直到找到第一次出现的字符\0的位置,然后返回一个数据,,所以该数据是一个随机值,每一次运行代码都是是随机值,,因为每一次运行代码的时候,整个内存空间中的数据是会发生变化的,所以返回来的就是一个随机值。

例题:

 memset

内存设置函数:num的单位是byte,该函数是以字节为单位来设置内存的,如果想把一个整型数组的每一个元素设置成1的话,不能使用memset函数,但是如果是一个字符数组的话,就可以使用,整型数组的话就通过遍历来一个个往里放置元素即可。

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

即,把指针变量ptr所指向的那块空间前num个字节的内容设置成指定的value值,比如:

今天的分享到此结束,谢谢大家,记得点赞哦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脱缰的野驴、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值