1. 函数原型
size_t strlen ( const char * str );
2. 函数解读
原文链接
解读完函数后,我们来用一用它,求一下下面这个字符串的长度
int main(void)
{
char arr[] = "abcdef";
int len = strlen(arr);
printf("len = %d\n", len);
return 0;
}
strlen()
来说,计算的是从字符串开头到字符串末尾的\0
为止一共有多少字符,那数一下就可以知道有6个4. 注意事项
接下去我们来说说有关这个函数的一些注意事项
① 参数指向的字符串必须要以 ‘\0’ 结束
\0
的\0
的,这才印证了为什么上面这个很奇怪的数,对于这个字符数组,它在内存中的布局是这样的:[][][][][a][b][c][][][]
,编译器为它在内存中随机分配了一块空间,既然没有\0
的话,它在内存中前后有什么东西就是不确定的,是随机的,我们去计算它的地址并不存在什么意义② 注意函数的返回值为size_t,是无符号的( 易错 )
int main(void)
{
size_t t;
if (strlen("abc") - strlen("abcdef") > 0)
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
\0
之前的字符个数,那么if()条件中即为3 - 6 = -3 < 0
,那一定会进入第二个分支,打印出来的结果就是【<=】,但为什么最后的结果是【>】呢?
此时,请你再回到第二小节,看一下strlen()函数的返回值,为size_t
5. 模拟实现
接下去的话我们就来模拟实现这个strlen()函数,这里我介绍三种方法
方法1:计数器
size_t my_strlen1(const char* str)
{
int count = 0;
while (*str)
{
str++;
count++;
}
return count;
}
方法2:递归
/*
* a b c d e f \0
* 1 + b c d e f \0
* 1 + 1 + c d e f \0
* 1 + 1 + 1 + d e f \0
* 1 + 1 + 1 + 1 + e f \0
* 1 + 1 + 1 + 1 + 1 + f \0
* 1 + 1 + 1 + 1 + 1 + 1 + \0
*/
size_t my_strlen2(const char* str)
{
if (*str == '\0')
return 0;
return 1 + my_strlen2(str + 1);
}
方法3:指针相减【计算的就是二者之间相差的元素个数】
\0
时,将两个地址一减最后的结果便是字符串的长度size_t my_strlen3(const char* str)
{
const char* tmp = str;
while (*tmp)
{
tmp++;
}
return tmp - str;
}
1. 函数原型
char * strcpy ( char * destination, const char * source );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,将一个字符串拷贝到另一个字符串中
char name[20] = { 0 };
strcpy(name, "zhangsan");
printf("%s\n", name);
strcpy()
就可以将“zhangsan”拷贝到这个字符数组中4. 注意事项
接下去我们来说说有关这个函数的一些注意事项
① 源字符串必须以 ‘\0’ 结束,因为源字符串中的 ‘\0’ 会被拷贝到目标空间
*
char str1[] = "**************";
char str2[] = "hello world";
strcpy(str1, str2);
printf("%s\n", str1);
hello world***
,但是d
后面并没有任何东西,原因其实就在于字符串最后面的\0
,str2里面存放的是个字符串,最后面是带有\0
的, 通过strcpy()进行拷贝的时候,会将末尾的\0
也一起拷贝过去%s
打印字符串的时候也是以末尾的\0
作为结束的标志,因此打印到此处就结束了,不会再打印后面的***
\0
,会发生什么呢?我们运行起来看看\0
,所以在拷贝的时候编译器完全不知道什么时候停下来,所以在一直拷贝的过程中就会发生【越界访问】的问题
——> 所以需要拷贝的原字符串一定要以\0
结尾,否则会出现问题
② 目标空间必须足够大,以确保能存放源字符串
abcdef
拷贝到空间只有3的字符数组str1中去,会发生什么呢?char str1[3] = { 0 };
char str2[] = "abcdef";
strcpy(str1, str2);
printf("%s\n", str1);
abcdef
——> 所以我们在拷贝字符串的时候也要考虑到目标字符串的空间是否足够容纳原字符串
③ 目标空间必须可变
abcdef
中【a】的首元素地址,我们知道对于一个字符串来说为一个常量,是不可修改的,所以定义指针p最标准的写法还是const char* p = "abcdef"
,这是一个常量指针,表示指针p所指向的那块空间中的内存是不可修改的,因此将"bit"
拷贝过去的话便是非法的char* p = "abcdef";
char* str = "bit";
strcpy(p, str);
printf("%s\n", p);
5. 模拟实现
接下去的话我们就来模拟实现这个strcpy()函数,细致讲解可以看看模拟实现库函数strcpy之梅开n度
assert()
断言一下,不过别忘了包含头文件哦!因为在字符拷贝的过程中指向目标空间的dest
指针会偏移到最末尾\0
的位置,但我们最后要返回拷贝完后指向目标空间起始地址的指针,因此在一开始要先做一个保存才行*dest++ = *src++
的不断进行,最终源头中的\0
会被拷贝到目标空间中,那此时就作为这个while判别式的条件,为0即终止循环,此时src中的所有内容都拷贝过来了,返回我们一开始保存的目标空间的起始地址即可char* m_strcpy(char* dest, const char* src)
{
assert(dest && src);
char* begin = dest; //保存一下目标空间字符串的起始地址
while (*dest++ = *src++)
{
;
}
return begin;
}
1. 函数原型
char * strcat ( char * destination, const char * source );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,拼接一下两个字符串
char arr1[20] = "hello ";
printf("%s\n", strcat(arr1, "world"));
world
就是从arr1的\0
处开始拼接的,而且也会将自己的\0
拷贝过去4. 注意事项
接下去我们来说说有关这个函数的一些注意事项,与
strcpy
类似
① 源字符串必须以 ‘\0’ 结束
\0
的,在拷贝的过程中就会出现问题char arr1[] = "hello \0********";
char arr2[] = { 'a', 'b', 'c' };
printf("%s\n", strcat(arr1, arr2));
\0
,所以在打印的时候编译器就会一直去寻找\0
继而导致访问冲突的问题② 目标空间必须有足够的大,能容纳下源字符串的内容
char arr1[3] = { 0 };
char arr2[] = "abcdef";
printf("%s\n", strcat(arr1, arr2));
③ 目标空间必须可修改
char* p = "abcdef";
char arr2[] = "ghijkl";
printf("%s\n", strcat(p, arr2));
④ 不可以给自己做追加
char arr1[20] = "abcdef";
printf("%s\n", strcat(arr1, arr1));
\0
位置开始拼接的,也就是说这个\0
会被覆盖掉,那么在想要追加自己原本的\0
时,却找不到了,即自己在给自己追加的时候会把自己的内容破坏,使得自己在停下来的时候没有\0
了5. 模拟实现
接下去的话我们就来模拟实现这个strcat()函数,和strcpy()很类似
\0
的位置开始的,因此我们在模拟实现的时候就要先去找到目标字符串中的\0
才行,保存一下【dest】就可以出发了,一直寻找直到找到\0
为止停下来strcpy()
一样了,把源字符串拷贝到目标字符串的\0
处char* m_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest; //保存一下目标字符串的起始地址
//1.寻找目标字符串中的\0
while (*dest != '\0')
{
dest++;
}
//2.从目标字符串的\0开始拷贝源字符串
while (*dest++ = *src++)
{
;
}
return ret;
}
1. 函数原型
int strcmp ( const char * str1, const char * str2 );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,比较一下两个字符串
int main(void)
{
char arr1[] = "zhangsanfeng";
char arr2[] = "zhangsanfeng";
int ret = strcmp(arr1, arr2);
if (ret == 1)
printf(">\n");
else if(ret == -1)
printf("<\n");
else
printf("==\n");
return 0;
}
4. 模拟实现
接下去的话我们就来模拟实现这个strcmp()这函数
*str1
和*str2
,若是它们相同的话就一直++,若是不相同的话便跳出循环继续比较谁大谁小,那么判断二者完全相同的逻辑就只能写在循环内部了,判断*str == '\0'
就可以看出它是不是走到了字符串的末尾,而且还没有跳出循环,此时就可以return 0;int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0; //二者相同且为'\0',return 0
str1++;
str2++; //否则向后继续查找
}
if (*str1 < *str2)
return -1;
else
return 1;
}
*str1
和*str2
我们都知道是两个字符了,直接相减判断其ASCLL码即可return *str1 - *str2;
讲完了长度不受限制的字符串函数,接下去我们再来说说长度受限制的字符串函数,和上面的一组函数很像,可以指定长度大小的字符串进行操作
那有同学就问了:既然有了一组这样的函数了,为什么还要再大费周章地搞出来一组呢?
strcpy()
的时候说到在拷贝的时候目标字符串要有足够大的空间来容纳源字符串吗?但是你仔细去观察的话是可以发现,虽然目标空间有时候放不下,但是编译器还是把它拷贝过去了,然后才报出来Error❌其实对于上面的这种越界写入是很危险的事情,正常来说编译器应该要爆出错误,而不是只警告一下,原因就在于我在首部加上了这句
#define _CRT_SECURE_NO_WARNINGS 1
要知道,C语言很早就被设计出来了,多多少少存在着一些缺陷和不完整性,那我们也不能去怪设计语言的人,毕竟【人有失手,马有失蹄】,【人非圣贤,孰能无过】呢!
那接下去呢就让我们来看看下面的这几组函数
1. 函数原型
char * strncpy ( char * destination, const char * source, size_t num );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,把arr2中的n个字符拷贝到arr1中
int main(void)
{
char arr1[10] = { 0 };
char arr2[] = "hello world";
strncpy(arr1, arr2, 5);
printf("%s\n", arr1);
return 0;
}
\0
到底有没有过去,我们将目标字符串做一个修改\0
,这样就凑足了5个\0
4. 模拟实现
接下去的话我们就来模拟实现这个strncpy()函数
num--
,直到num个字符拷贝完为止。num > 原字符串的长度
,此时就需要再做【补充\0的工作】,不过while循环中的条件要写--num
,否则的话就会多进入一次,那后面就会多出一个\0
char* my_strncpy(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* start = dest;
while (num && (*dest++ = *src++))
{
num--;
}
//若是跳出循环后num > 0,表示num > 原字符串的长度
if (num)
{
while (--num)
{
*dest++ = '\0'; //再补充num个'\0'
}
}
return start;
}
1. 函数原型
char * strncat ( char * destination, const char * source, size_t num );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,拼接一下指定的字符个数
int main(void)
{
char arr1[20] = "hello ";
char arr2[] = "world wide web";
strncat(arr1, arr2, 5);
printf("%s\n", arr1);
return 0;
}
\0
有没有过去,我们将目标字符串做个修改,自己手动加上一个\0
,然后在后面加上******
,继续通过调试来观察可以发现,源字符串中的\0
是会被拷贝过去的还是一样,对于
strncat()
来说也会出现需要拷贝的字符个数 > 源字符串原先的个数,那此时也会和strncpy()
一样在后面补充\0
吗?我们继续通过调试来看看
hello
加上8个最后的arr1长度应该为13,即数组下标12的地方为\0
,但是在调试看来却不是这样,d
的后面还是只有一个\0
,编译器并没有做过多的补充,那么这也就印证了我们原先解读函数时说的那些东西5. 模拟实现
接下去的话我们就来模拟实现这个strncat()函数
strcat()
一样,让【dest】先移动到\0
的位置,然后第二块逻辑,就是从从\0
的位置开始拷贝src中的num个字符\0
的逻辑必须放在一起,即从源头拷贝过来\0
的那一瞬间就立马返回,因为*dest++
这是一个后置++,当这句代码执行完后dest又会往后进行偏移,此时就不对了,要在拷贝到\0
立马返回当前目标字符串的起始地址
\0
的话就需要自己手动去加上了,保证一个字符串的完整性,最后也是一样返回目标字符串的起始地址char* my_strncat(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* start = dest;
//1.首先让dest先移动到\0的位置
while (*dest != '\0')
{
dest++;
}
//2.从\0开始拷贝src中的num个字符
while (num--)
{
if((*dest++ = *src++) == '\0')
return start; //碰到\0直接返回,不再补充\0
}
*dest = '\0'; //最后在目标字符串的末尾处添上\0
return start;
}
1. 函数原型
int strncmp ( const char * str1, const char * str2, size_t num );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,比较一下两个字符串中固定字符的大小
int main(void)
{
char arr1[] = "abcdef";
char arr2[] = "abcz";
int ret = strncmp(arr1, arr2, 3);
if (ret == 0) {
printf("==\n");
}
else if(ret < 0) {
printf("<\n");
}
else{
printf(">\n");
}
return 0;
}
abc
与abc
是相同的abcd
是小于abcz
的abcz
换成abcc
后,结果又会有所不同
不过呢,要注意这里的返回值ret,不可以用== 1
或== -1
这样去判断
strcmp()
的话就可以知道它返回的只是>/== 0
的数字,而不是具体的数值,因此我们不能将值写死,否则在其他编译器例如gcc上就跑不过去了4 模拟实现
对于strncmp()的实现比较复杂,这里就不做详解了,有兴趣的同学可以自行阅读一下库里提供的源码
int __cdecl strncmp
(
const char *first,
const char *last,
size_t count
)
{
size_t x = 0;
if (!count)
{
return 0;
}
/*
* This explicit guard needed to deal correctly with boundary
* cases: strings shorter than 4 bytes and strings longer than
* UINT_MAX-4 bytes .
*/
if( count >= 4 )
{
/* unroll by four */
for (; x < count-4; x+=4)
{
first+=4;
last +=4;
if (*(first-4) == 0 || *(first-4) != *(last-4))
{
return(*(unsigned char *)(first-4) - *(unsigned char *)(last-4));
}
if (*(first-3) == 0 || *(first-3) != *(last-3))
{
return(*(unsigned char *)(first-3) - *(unsigned char *)(last-3));
}
if (*(first-2) == 0 || *(first-2) != *(last-2))
{
return(*(unsigned char *)(first-2) - *(unsigned char *)(last-2));
}
if (*(first-1) == 0 || *(first-1) != *(last-1))
{
return(*(unsigned char *)(first-1) - *(unsigned char *)(last-1));
}
}
}
/* residual loop */
for (; x < count; x++)
{
if (*first == 0 || *first != *last)
{
return(*(unsigned char *)first - *(unsigned char *)last);
}
first+=1;
last+=1;
}
return 0;
}
1. 函数原型
const char * strstr ( const char * str1, const char * str2 );
char * strstr ( char * str1, const char * str2 );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,看一下str2这个子串在str1中
int main(void)
{
char str1[] = "abcdefabcdef";
char str2[] = "def";
char* substr = strstr(str1, str2);
printf("%s\n", substr);
return 0;
}
def
在主串abcdefabcdef
中出现的第一个位置,我们使用%s
去打印的话就会从这个位置开始往后打印后面的字符串5. 模拟实现
接下去的话我们就来模拟实现这个strstr()函数,比较复杂一些,要集中注意力哦!
情况①:匹配一次就成功
b b b
,但是我们要匹配的子串是b b c
,所以在匹配到第三个b的时候就需要进行重新匹配p + 1
的位置即可,因为从【p】的位置开始已经不可以匹配成功了,具体地我在下面讲述代码的时候细说首先给出整体代码可以先看看
const char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* s1 = str1;
const char* s2 = str2;
const char* p = str1;
while (*p)
{
s1 = p;
s2 = str2;
while (s1 != '\0' && s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return p; //此时p的位置即为子串s2在s1中出现的第一个位置
}
p++;
}
return NULL; //若是主串遍历完了还是没有找到子串,表明其不在主串中,返回NULL
}
细说一下:
const char* s1 = str1;
const char* s2 = str2;
const char* p = str1;
*s1
和*s2
z中的存放的字符相同的话,就继续往后查找,但是呢它们不能一直无休止地往后查找,总有停下来的时候,那也就是当指针所指向的内容为\0
时,就需要跳出循环while (s1 != '\0' && s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
p++
即可,然后回到循环判断*p
是否为\0
,若还没有碰到主串末尾的话,就需要更新s1
和s2
的位置,继续进行匹配的逻辑p++;
s1 = p;
s2 = str2;
*s2 == '\0'
的话,此时就表示子串已经匹配完成了,都到达末尾了,那么这个时候我们应该返回【子串在主串中出现的第一个位置】,这也是strstr()
的本质,那么这个位置在哪里呢?因为我们是哪p
去记录位置的,那就可以说在主串中从指针p所指向的这个位置开始直到*s2
到末尾时,即为匹配成功子串的一个位置if (*s2 == '\0')
{
return p; //此时p的位置即为子串s2在s1中出现的第一个位置
}
匹配过程解说:
看完匹配的过程相信你对strstr()这个函数应该非常清楚了,但其实它的效率并不是很高,在我们看来它只是一个【暴搜】的过程,若是想要追求更加高效的匹配过程,可以看看KMP算法
1. 函数原型
char * strtok ( char * str, const char * delimiters );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,求一下下面这个字符串的长度
int main()
{
char sep[] = "@.";
char email[30] = "[email protected]";
char* ret = strtok(email, sep);
if (ret != NULL)
printf("%s\n", ret);
ret = strtok(NULL, sep);
if (ret != NULL)
printf("%s\n", ret);
ret = strtok(NULL, sep);
if (ret != NULL)
printf("%s\n", ret);
return 0;
}
seq
分割字符数组,来确定要以何种字符来进行分割,这里我采用的是@
和.
,那么在这个函数执行的时候,就会根据这两个字符来进行分割email
字符串,但第二、三次传递的都是NULL,如果你有认真阅读过这个函数,就知道为什么了我这样做了,
被保存的位置开始
,查找下一个标记If a token is found, a pointer to the beginning of the token.Otherwise, a null pointer.
所以它是有可能返回一个空指针的,对于一个空指针来说,我们就无需去打印了代码优化:
因为strtok函数会改变被操作的字符串,所以我们一般不会对原字符串进行操作,而会去选择临时拷贝一份
strcpy
,此时再去操作的话原字符串就不会被修改了char cp[30];
strcpy(cp, email); //临时拷贝一份
char* ret = strtok(cp, sep);
if (ret != NULL)
printf("%s\n", ret);
ret = strtok(NULL, sep);
if (ret != NULL)
printf("%s\n", ret);
ret = strtok(NULL, sep);
if (ret != NULL)
printf("%s\n", ret);
但你是否觉得上面这样判断一次打印一次很麻烦,这种代码要是给你上司看到的话指不定会被骂成什么样,我们不要写重复的逻辑,尽量将其进行封装,那对于上面的重复工作,其实我们可以使用【循环】来做一个优化
strtok()
的时候也是只在第一次传递字符串给第一个参数,后面的话就都传递NULL了ret
去接收每一次分割后的返回值然后去打印,那么最后的话当分割到字符串结尾的时候没有了就会返回NULL,那此时我们将其作为结束条件来判断即可for (ret = strtok(cp, sep); ret != NULL; ret = strtok(NULL, sep))
{
printf("%s\n", ret);
}
完整代码如下:
int main()
{
char sep[] = "@.";
char email[30] = "[email protected]";
char cp[30];
strcpy(cp, email); //临时拷贝一份
char* ret = NULL;
for (ret = strtok(cp, sep); ret != NULL; ret = strtok(NULL, sep))
{
printf("%s\n", ret);
}
return 0;
}
1. 函数原型
char * strerror ( int errnum );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,求一下下面这个字符串的长度
int main(void)
{
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));
printf("%s\n", strerror(5));
return 0;
}
当然这个函数不是这么用的,我们可以在实际的场景中来试试,比方说这里要打开一个文件,那么打开文件的话就一定存在打开失败的情况,此时我们就可以使用
strerror()
去给出一些错误信息
#include
这个头文件才可以int main()
{
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
printf("%s\n", strerror(errno));
return 1;
}
else {
printf("文件打开正常\n");
}
return 0;
}
test.text
的文本文件,然后通过fopen()
函数去打开它,如果不清楚这个函数的话可以看看C语言文件操作指南strerror(erron)
这个函数去打印一些相关的错误信息这个函数了解一下即可,不用过于深究
下面给出一起有关字符操作的函数,它们都可以在cplusplus这个网站中搜到,感兴趣的同学可以去多了解一点
函数 | 如果他的参数符合下列条件就返回真 |
---|---|
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 | 任何可打印字符,包括图形字符和空白字符 |
isupper()
判断是否为大写字母,以及tolower()
将大写字母转为小写#include
#include
int main()
{
int i = 0;
char str[] = "Test String.\n";
char c;
while (str[i])
{
c = str[i];
if (isupper(c))
c = tolower(c);
putchar(c);
i++;
}
return 0;
}
1. 函数原型
void * memcpy ( void * destination, const void * source, size_t num );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,求一下下面这个字符串的长度
memcpy()
传入的前两个参数就是目的地址和源地址,最后一个参数的话就是要拷贝的字节数,记住,这里是【字节数】而不是【元素个数】,所以可以看到我是用sizeof(int)
首先求出了数组中每个元素的字节数,然后在乘上数组元素个数,就是整个数组所占的字节数int main(void)
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
int arr2[10] = { 0 };
memcpy(arr2, arr1, sizeof(int) * sz);
return 0;
}
memcpy()
也可以拷贝浮点型的数据,上去仔细看看原函数就可以知道目标地址和原地址的类型都是void*
,表明它们可以接收任意类型的地址,即可以拷贝任意类型的数据int main(void)
{
float arr1[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
float arr2[5] = { 0 };
memcpy(arr2, arr1, sizeof(int) * sz);
return 0;
}
5. 模拟实现
接下去的话我们就来模拟实现这个memcpy()函数
void* my_memcpy(void* dest, const void* src, int num)
{
assert(dest && src);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
strcpy()
的时候是直接用【*dest = *src】的,但是这里的话我们不能这么去操作,上面讲到过两个目标指针和源指针都是void*
类型的,这种指针类型是不可以直接进行解引用的,而是要在内部对其进行强制类型转换int*
、float*
、double*
吗?不,这些都不可以,设想我们传入的字节数是28,那使用int*
类型的指针去拷贝确实可以做到,但若是我传入的总字节数为27呢?不是一个4字节或者8字节的整数倍,那要怎么去拷贝呢?char*
,无论你要我拷多少字节的数据,反正我解引用每次只能拷贝1个字节的数据,那么就一个个拷过去就行了,虽然效率上来说是低了一些,但是容错率下降了,就不会出现什么大问题char*
类型即可,便可以一次访问4个字节,但是这里尽量不要直接写成(char*)dest++
,因为这里面涉及到【隐式类型转换】,在中间会产生一个临时对象,我们对临时对象去++的话并没有什么意义,所以这里还是规规矩矩地写就行dest = (char*)dest + 1;
src = (char*)src + 1;
看到上面这样一个个拷贝过去太累了,如果我不想拷贝所有的数据,而是只拷贝一半的数据呢?这可以不可以做到
int main(void)
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 20);
for (int i = 0; i < 10; ++i)
{
printf("%d ", arr2[i]);
}
return 0;
}
不过我觉得,从一个数组拷贝到另外一个数组太麻烦了,可以直接在自己本身上进行操作吗?
int main(void)
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr1 + 2, arr1, 20);
for (int i = 0; i < 10; ++i)
{
printf("%d ", arr1[i]);
}
return 0;
}
那该怎么办呀有什么其他办法吗?
memcpy()
来说,它只负责拷贝两块独立空间中的数据,但是对于一个数组的元素,它们都是连续存放的,若是擅自去进行拷贝的话会造成覆盖的情况,此时我们可以使用memmove()
这个函数,它可以用来专门拷贝重叠内存的数据1. 函数原型
void * memmove ( void * destination, const void * source, size_t num );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,来移动一下两个重叠的内存块
int main(void)
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20);
for (int i = 0; i < 10; ++i)
{
printf("%d ", arr1[i]);
}
return 0;
}
5. 模拟实现
接下去的话我们就来模拟实现这个memmove()函数
流程图示:
分析:
dest
来说,一个是在src前面,需要从前往后进行拷贝,一个是在src
后面,需要从后往前进行拷贝,还有一个便是两块内存空间不会进行覆盖, 但还是存在与一个连续的空间即数组中,这个时候无论是【从前往后】还是【从后往前】都是可以的,那这样分成三个区域太麻烦了,这里我推荐分成两块区域,通过地址的大小进行比较
dest < src
时,我们从前往后进行逐一字节的拷贝dest >= src
时,我们从后往前进行逐一字节的拷贝void* my_mommove(void* dest, const void* src, size_t num)
{
assert(dest && src);
char* start = dest;
if (dest < src)
{
//memcpy()的拷贝逻辑
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else //dest >= src
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return start;
}
dest
和src
强转为char*
类型的地址即可,此时再加上一个【num】便可以偏移到指定的位置处,随着【num】的不断变化,就可以将数据从后往前进行一一拷贝while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
1. 函数原型
void * memset ( void * ptr, int value, size_t num );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,初始化一下arr这个数组
int main(void)
{
int arr[10];
int sz = sizeof(arr) / sizeof(arr[0]);
memset(arr, 0, sizeof(int) * sz);
return 0;
}
4. 注意事项
但是可别高兴得太早,若是我现在想要将数组中的数据都初始化成【1】呢,此时还能成功吗?
memset()
的特性,是以字节为单位去进行一个初始化,那就可以看出问题出在哪里了接下去就是本文的最后一个内部函数 ——
memcmp()
1. 函数原型
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
2. 函数解读
原文链接
3. 功能演示
解读完函数后,我们来用一用它,比较一下两个内存块中的num个字节
int main(void)
{
int arr1[10] = { 1,2,3,4,5 };
int arr2[10] = { 1,3,2 };
int ret = memcmp(arr1, arr2, 12);
printf("%d\n", ret);
return 0;
}
strcmp()
一样,为< 0、= 0或者> 0的数值01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
01 00 00 00 03 00 00 00 02 00 00 00
memcmp()
可是一个字节一个字节进行比较,那么此时当他们比较到【02】和【03】的时候就已经不相等了,因为前一个小于后一个,所以便会返回 < 0的数字最后来总结一下本文所学习的内容
一、字符串函数
‘\0’
结束,因为源字符串中的 ‘\0’ 会被拷贝到目标空间‘\0’
结束\0
\0
,\0
二、字符串查找函数
三、错误信息报告函数
errno
这个存放全局错误码的变量四、字符操作函数
五、内存操作函数
以上就是本文要介绍的所有内容,对于这些函数大家可以自行到库中去阅读,如有解读错误敬请指出,感谢您的阅读