大家好,我是苏貝,本篇博客带大家了解字符函数和内存操作函数,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
目录
- 一.字符函数
- 1.1 字符分类函数
- 1.2 字符转换函数
- 二.内存操作函数
- 2.1 memcpy
- 2.2 memmove
- 2.3 memset
- 2.4 memcmp
一.字符函数
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不是小写字母,返回0int 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进制数字,返回0int 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("%c\n", x);x = tolower(x);printf("%c\n", 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.这个函数在遇到 ‘\0’ 的时候并不会停下来。
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*4int 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 00int arr2[] = { 1,2,0x11223304 };//01 00 00 00 02 00 00 00 04 33 22 11int ret = memcmp(arr1, arr2, 9);printf("%d", ret);return 0;
}
用memcmp函数时,切记不要拿数组中的元素直接比较,我们要看它们各自的存储情况。本题两个数组在前8个字节都相等,看第9个字节,03<04,所以前面<后面,返回一个负数
好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️