目录
前言
一、上文补充
strerror
注意要点
字符分类函数
字符转换函数
二、内存操作函数
memcpy
注意要点
模拟实现
特殊情况
memmove
注意要点
模拟实现
memcmp
注意要点
memset
注意要点
前面,我们较为详细地讲述了,对字符进行操作的字符串函数。相信大家对字符串的拷贝、追加和比较函数以及字符串中的查找函数有了一定的认识。那么,问题来了,如果一个数组的元素,不是字符,而是其他各种类型,那该怎样进行拷贝和比较等操作呢?接下来我们就来探索,c语言中的内存操作函数。复习字符串函数请跳伞
c语言基础知识——字符串和内存函数(上)
其实上一篇博客所写的字符串函数中,除了对字符串进行操作的函数外,还有报告错误信息、对字符进行操作的函数。
char * strerror ( int errnum );
- 这个函数是错误信息报告函数,参数是一个整形,0、1、2、3每个数字对应一个错误信息。
- 该函数返回一个指针,指向对应的错误信息字符串。
例如:
#include
#include
int main()
{
printf("错误信息为:%s\n", strerror(0));
printf("错误信息为:%s\n", strerror(1));
printf("错误信息为:%s\n", strerror(2));
printf("错误信息为:%s\n", strerror(3));
printf("错误信息为:%s\n", strerror(4));
return 0;
}
打印的结果如下:
实际使用的时候肯定不会这么用滴,那啥时候能用上呢?该怎么用呢?我们来看看下面这段代码:
等一下,看之前,先做一些铺垫吧!因为这段代码涉及到文件操作部分。我就简略说一下吧。
首先我们定义一个“文件指针”指向一个名为“test.txt”的文件,接着用fopen函数打开。
结果本地没有这个文件,打不开,我们就要打印错误信息。那错误信息从哪里来呢?
原来,c语言的库函数在调用失败的时候,会将一个错误码(数字)存放在一个叫errno的变量中。
联想到strerror的参数是一个整形,……这下大家应该猜到该怎么使用了吧?只需把errno当作参数传给strerror函数,就可以得到想要的错误信息啦!
#include
#include
#include //必须包含的头文件
int main()
{
FILE* pFile;
pFile = fopen("test.txt", "r");//fopen调用失败返回NULL
if (pFile == NULL)
printf("错误信息为: %s\n", strerror(errno));//errno:是最新出现的错误码,
//再出现其他类型错误就被改成其他值了
return 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
|
任何可打印字符,包括图形字符和空白字
|
int tolower ( int c ); //将大写变为小写返回int toupper ( int c );//将小写变成大写返回
这些函数基本上就是判断一些字符是否是想要的字符,如果是,返回真,否则返回假。比较简单。也不需要记,用到的时候查找一下即可。
void * memcpy ( void * destination, const void * source, size_t num );
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到 '\0' 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
前两个很容易理解,因为传入参数类型未知,只能事先将需要拷贝的字节数传参进去,从而进行拷贝操作,而且只会按照字节数拷贝,不会管是否越界。
第三个要点是什么意思呢?我们先来模拟实现一下这个函数,自然就明白了。
#include
void* my_memcpy(void* dest, const void* str, size_t num)
{
assert(dest && str);
void* tmp = dest;
while (num--)
{
*((char*)dest + num) = *((char*)str + num);
}
return tmp;
}
思路是这样的:
- 首先判断传过来的参数是否为空指针,如果是,就报错。
- 接着,用临时指针变量tmp把目的地址存起来,因为到最后要返回这个地址。
- 然后,进入循环,每次循环都拷贝一个字节的内容,拷贝完使num自减,下次循环就可以拷贝前一个字节的内容。
- 最后,返回目的指针。
那么如果两个指针指向的字符串有重叠,会发生什么呢?
假如有一个数组,其元素为 1,2,3,4,5,6,7,8,9,0。我们想要把3,4,5,6拷贝放到1,2,3,4位置,理想结果是3,4,5,6,5,6,7,8,9,0。那我们用代码实现一下:
#include
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,0 };
memcpy(arr1, arr1+2, 16);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
}
如果使用标准c语言的编译器,运行结果是 5 6 5 6 5 6 7 8 9 0,这是为什么呢?
原来,当我们将6拷贝放到4的位置时,4这个位置就会被覆盖为6,原来的4就丢失了,后面拷贝4,3时,遇到的元素就是6,5,所以不能正确拷贝。(注意,vs编译器对该函数进行了改良,结果与理想结果一样。)
void * memmove ( void * destination , const void * source , size_t num );
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
也就是说,memmove函数与memcpy函数一样,唯一区别就是memmove可以实现重叠目标空间的拷贝!!!
那要怎么实现呢?还记得刚刚那个案例吗?一个数组,其元素为 1,2,3,4,5,6,7,8,9,0。我们想要把3,4,5,6拷贝放到1,2,3,4位置,理想结果是3,4,5,6,5,6,7,8,9,0。出错的原因在于,当6拷贝到4这个位置时,4会被覆盖。(当5拷贝到3这个位置时,3会被覆盖。)那我们先拷贝3和4不就得了吗?反正前面的1和2覆盖掉没啥影响。嗷,原来只需把从后往前拷贝编程从前往后拷贝就完了。先别急,再想想,真的是这样吗?
如果要把1,2,3,4拷贝放到3,4,5,6位置呢?是不是又需要从后向前拷贝了?
嗷~到这里,大家应该明白了,如果目的指针指向的位置在源指针前面,就要从前往后拷贝。先拷贝3,再拷贝4,这样才不会使3,4被5,6提前覆盖。
如果目的指针指向的位置在源指针后面,就要从后往前拷贝。先拷贝4,再拷贝3,这样才不会使4,3被2,1提前覆盖。
明白了这个原理,我们就可以实操来实现一下这个函数啦:
#include
void* my_memmove(void* dest,const void* str,int num)
{
void* tmp = dest;
if (dest > str)
{
while (num--)
{
((char*)dest)[num] = ((char*)str)[num];
}
}
else
{
while (num--)
{
*((char*)dest)-- = *((char*)dest)--;
}
}
return tmp;
}
int memcmp ( const void * ptr1 , const void * ptr2 , size_t num );
- 比较从ptr1和ptr2指针开始的num个字节
- 返回值如下:
和strcmp类似,只不过添加了要比较的字节数。
模拟实现比较简单,大家自己动手试试吧!
void * memset (void * ptr,int value,size_t num ) ;
- 将 ptr 指向的内存块的第一个字节数设置为指定值。
- num是要修改的字节数。
- 每个字节的内存都会被修改为指定值。
一般用于初始化,直接将指针所指向的内存全部初始化成0。但是注意第三点,如果num是指针后内存的大小,value是1,那么后面这些内存中每个字节的内存的内容都会被改为1,而不是将每个元素改为1,所以不能这样初始化。 又因为这个特性,大小端不同的环境有时也会带来不同的结果。
好啦,字符串和内存函数到这里就介绍完了。同志们有没有什么问题呢?欢迎在评论区留言。