字符处理函数
函数 | 参数为真条件 |
---|---|
iscntrl |
任何控制字符 |
isspace |
空白字符(包括空格' ' ,换页'\f' ,回车'\r' ,换行'\n' ,制表符'\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 |
任何可打印字符,包括图形字符和空白字符 |
- 换行符
\n
和回车符\r
的区别:
换行符另起一行,光标来到行首。回车符不另起新行,光标来到行首,如果再输入内容就把原内容覆盖掉了。
字符转换函数:
int tolower(int c);
int toupper(int c);
函数参数和返回值都是int
类型是因为C
语言都默认使用了ASCII
的编码方式,用ASCII
码表示了一些特定的字符。这里使用int
也可以通过返回-1
来标记无效值。
内存操作函数
memcpy
[memcpy
的官方文档]:http://www.cplusplus.com/reference/cstring/memcpy/?kw=memcpy
函数的作用是:【从source的内存位置开始向后拷贝num个字节的数据到destination的内存位置】。
void *memcpy(void *destination, const void *source, size_t num);
- 这里的参数
num
的单位是字节。 - 函数参数和返回值都是
void*
类型,说明拷贝的大小不固定,要视最后的num
参数而定,函数最后返回了destination
。 - 因为此函数不是字符串的库函数,所以不仅可以拷贝字符数组,还可以拷贝整数数组等,所以在遇到
'\0'
的时候并不会停下来。 - 如果
source
和destination
有任何的重叠,复制的结果都是未定义的,绝对不允许缓冲区重叠。
模拟实现
void *Memcpy(void * dest, const void * src, size_t num) {
assert(dest != NULL);
assert(src != NULL);
char *pdest = (char*)dest;
char *psrc = (char*)src;
for (size_t i = 0; i < num; ++i) {
pdest[i] = psrc[i];
}
return dest;
}
- 在
for
循环中不可以像之前strncpy
一样输入dest[i] = src[i];
因为void*
类型参数不可以解引用以及加减一个整数,这一语句就相当于进行解引用与整数加减。
所以采取的解决方案是:对void*
类型参数强转为char*
类型参数,再进行处理。 - 这里对
void*
类型参数进行char*
的强制类型转换,是想让系统按照字节来拷贝,如果强转为int*
传入参数不够一个int
参数的字节数,那么就无法正常处理了,所以还是按照挨个字节进行处理最为科学。 - 程序还存在一个隐含的风险:缓冲区重合问题。就是上面所说的
source
和destination
有重叠现象,复制的结果是未定义的。
什么是
缓冲区重合
?
memcpy
的原理就是按照src
的内存内容向dest
内存逐个进行拷贝,如果出现上图中的缓冲区重叠现象,就会影响到dest
内容的拷贝,造成与src
内容不同的拷贝错误。
【如图就是最后一个字符的拷贝错误】
缓冲区重叠问题发生原因:dest指针处于src缓冲区之内,这是标准库认为的。所以像下图中的情况就不属于缓冲区重叠现象:
从图中可以看到dest
指针位于src
缓冲区之外,虽然拷贝影响了源内存的内容,但是根据库函数的定义,它是不属于缓冲区重叠的。
所以就引出了可以解决缓冲区重合问题的允许缓冲区重合的函数memmove
。
memmove
[memmove
的官方文档]:http://www.cplusplus.com/reference/cstring/memmove/?kw=memmove
函数的作用与memcpy
相同,都是【按字节拷贝内存空间中的内容】。
void *memmove ( void *destination, const void *source, size_t num );
- 和
memcpy
的差别就是memmove
函数处理的源内存块和目标内存块是允许重叠的。如果源空间和目标空间出现重叠,就得使用memmove
函数处理。
模拟实现
void *Memmove(void * dest, const void * src, size_t num) {
assert(dest != NULL);
assert(src != NULL);
char *pdest = (char*)dest;
char *psrc = (char*)src;
if (pdest >= psrc && pdest <= psrc + num) { //是标准库认为的缓冲区重合行为
//解决方案:从后往前拷贝
for (int64_t i = num - 1; i >= 0; --i) {
pdest[i] = psrc[i];
}
}
else {
for (size_t i = 0; i < num; ++i) {
//不属于缓冲区重合,就按照原来的memcpy方式进行拷贝
pdest[i] = psrc[i];
}
}
return dest;
}
- 从后往前的拷贝方式避免了源内存顺序拷贝所存在的问题。
- 第一个
for
语句中使用int64_t
的数据类型,而不使用size_t
,解决了 2 个问题:
i>=0
恒成立。
如果使用size_t
,那么循环对参数进行修改,到达0
时,再进行--
操作,就会变成一个很大的数,因为内存中负数是按照补码的形式存放的,所以看似是减为了一个负数而跳出循环,实则恒为一个正数,是死循环现象。使用有符号的int64_t
类型就可以避免无符号类型带来的问题。- 如果
num
传入为0
,那么参数就从很大的数开始运算。
同样是无符号类型的弊端,传入参数为0
时,在第一个for
循环中需要对num
进行减一操作,所以还是变成了一个非常大的数,一定不会满足题意。同样选择有符号的int64_t
类型进行规避。
memcmp
[memcmp
的官方文档]:http://www.cplusplus.com/reference/cstring/memcmp/?kw=memcmp
函数的作用及参数基本与strncmp
完全相同,都是进行内容比较。
memcmp
是【比较两内存空间中的内容】,strncmp
是比较两字符串。
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
因为与strncmp
函数几乎完全相同,所以不进行赘述了,读者可以通过上一篇字符串处理函数博客中查看strncmp
函数的模拟实现过程。
- 小结
本博客所讲的内存操作函数和字符串相关函数最大的区别就是与‘\0’
无关,只与内存字节数有关,使用时一定要注意参数的单位是字节。
对于大量的字符处理函数不必强制记忆,只需有一个知晓函数存在的程度即可,使用时可以查阅相关文档直接使用。