字符函数和字符串函数
1、strlen函数
1.1 strlen函数功能介绍
strlen函数是求字符串长度的函数。
size_t strlen(const char* str);
strlen函数展示
#include <stdio.h> #include <string.h> int main() { char arr1[] = "abcdef";//这种写法在字符串结尾自动添上了'\0' char arr2[] = { 'a','b','c','d','e','f' };//这种写法求不了数组中储存的字符串长度,字符串结尾没有'\0',可以主动往里面放'\0' char arr3[20] = { 'a','b','c','d','e','f' }; //可以求字符串长度,因为给定了数组空间大小, //在存储未满时,后面自动填充了数字0,数字0对应空字符'\0' int len1 = strlen(arr1);//字符串以'\0'作为结束标志,strlen函数返回的是在字符串中\0前面出现的字符个数(不包含'\0') int len2 = strlen(arr2); int len3 = strlen(arr3); printf("%d\n", len1); printf("%d\n", len2); printf("%d\n", len3); if (strlen("abc") - strlen("qwerty") > 0) { printf(">\n");//strlen的返回值是无符号数,无符号数-无符号数还是无符号数,大于0 } else { printf("<=\n"); } return 0; }
打印结果
- 字符串以‘\0’作为结束标志,strlen函数返回的是在字符串中‘\0’前面出现的字符个数(不包含‘\0’)。
数组arr1与数组arr3都有‘\0’所以能计算出字符串长度是6,数组arr2储存的字符串在结尾没有‘\0’,会一直往后找‘\0’,直到找到为止,计算出的字符串长度是随机值。- 由上得出,函数参数指向的字符串必须要以‘\0’结尾。
- 为什么代码中的条件语句,得到的结果是 >号?
函数的返回值为size_t,是无符号类型的。无符号数-无符号数还是无符号数,大于0
1.2 模拟实现strlen函数
#include <stdio.h> #include <assert.h> size_t my_strlen(const char* str) //const保护指针所指向的内容不被改变 { int count = 0; assert(str != NULL);//当str等于空指针时,编译会报错 while (*str) { str++; count++; } return count; } int main() { char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
打印结果
- 采用计数器的形式,对一个一个字符进行计数,直到找到空字符‘\0’停止计数。模拟实现strlen函数还有递归的方法、指针-指针的方法。
- 库函数assert()
assert函数是一个断言函数,功能为:当括号中的条件不满足时,在程序进行编译的过程中会报错。
为了避免传过来的是个空指针,而编译人员又没有发现,使用assert函数进行断言,assert(str != NULL); 或者这样写assert(str);- const修饰指针变量
- const如果放在 * 的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本身的内容可以改变。
- const如果放在 * 的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容可以通过指针改变。
- 我们的目的只是对字符数组中储存的字符串进行计数,为了避免改变指针指向内容,在参数用const对指针进行修饰。
2、strcpy函数
2.1 strcpy函数功能介绍
strcpy函数是对字符串进行拷贝的函数。
char* strcpy(char* destination, const char* source);
strcpy函数展示
#include <stdio.h> #include <string.h> int main() { char arr1[20] = { 0 }; char arr2[] = "abcdef"; char* arr3 = "fvgdf";//不能作为目标空间,因为要求目标空间可变,但是这是一个常量字符串,常量是不可修改的 char arr4[] = "123456"; strcpy(arr1, arr2); //源字符串arr2的'\0'也会拷贝过去,所以要求源字符串必须有'\0'。 strcpy(arr4, arr2); printf("%s\n", arr1); printf("%s\n", arr4); return 0; }
打印结果
- 源字符串必须以‘\0’结束,会将源字符串中的‘\0’拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。
arr1与arr4作为目标空间都是可变的,arr3是一个指针,指向常量字符串“abcdef”,常量字符串不可变,所以arr3不能作为目标空间。
2.2 模拟实现strcpy函数
#include <stdio.h> #include <assert.h> char* my_strcpy(char* dest, const char* src) { assert(src && dest);//这两个指针都不能为空 char* ret = dest; while (*src) { *dest = *src; dest++; src++; } *dest = *src;//将最后一个'\0'也拷贝过去。 return ret; } int main() { char arr1[20] = { 0 }; char* arr2 = "hello bit"; my_strcpy(arr1, arr2); printf("%s\n", arr1); printf("%s\n", my_strcpy(arr1, arr2));//l链式访问,函数的返回值直接作为另一个函数(printf函数)的参数 return 0; }
打印结果
- strcpy函数返回的是目标空间的起始位置。
- 使用库函数对目标指针与源指针进行断言,避免出现传参过来的是空指针。
- 为什么要设置返回类型,而不是设置成void*?
有了返回值才能实现链式访问。- 什么是链式访问?
把一个函数的返回值作为另外一个函数的参数。如:
printf(“%s\n”, my_strcpy(arr1, arr2));my_strcpy函数的返回值直接作为 printf函数的参数。给 printf函数一个字符串的起始位置就是一次向后进行打印,直到‘\0’。- 当指针访问到源字符串的‘\0’时,循环中止。为了将‘\0’拷贝过去,再跳出循环后,还要再进行拷贝一次。
还能对上述my_strcpy函数进行优化
#include <assert.h> char* my_strcpy(char* dest, const char* src) { assert(src && dest);//这两个指针都不能为空 char* ret = dest; while (*dest++ = *src++)//更为简便的写法,最后的'\0'也能拷贝过去 { ; } return ret; //strcpy函数返回的是目标空间的起始位置。 }
- while (* dest++ = * src++)
每一次循环得到的结果是赋值后的结果,也就是 *dest,当指针访问到源字符串的 ‘\0’ 时,先将源字符串的 ‘\0’ 拷贝过去,此时循环条件 *dest== ‘\0’ ,循环中止。一次性将 ‘\0’ 拷贝到了目标字符串。
3、strcat函数
3.1 strcat函数功能介绍
strcat函数是字符串追加函数,在一个字符串的结尾 ‘\0’处开始进行追加其他字符串。
char* strcat(char* destination, const char* source);
strcat函数展示
#include <stdio.h> #include <string.h> int main() { char arr1[20] = "hello"; char arr2[] = " bit"; strcat(arr1, arr2);//从目标字符串的‘\0’处开始追加,源字符串的‘\0’也会追加过去 printf("%s\n", arr1); return 0; }
打印结果
- 源字符串必须以‘\0’结束,目标字符串也得有‘\0’,从目标字符串的‘\0’处开始追加,目标字符串 ‘\0’被覆盖。
- 目标空间必须右足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改,即不能是常量字符串。
- 字符串不能自己给自己追加,即 strcat(arr2, arr2); ,会陷入死循环,为什么?
如上图所示,当源字符串开始往目标字符串的 ‘\0’ 开始一一拷贝完毕以后,源字符串要将自己的 ‘\0’ 也拷贝过去,用来结束拷贝。因为源字符串与目标字符串是同一块字符串,之前的 ‘\0’处已经被字符 ‘b’覆盖了。所以拷贝不会停止,进入死循环。
3.2 模拟实现strcat函数
#include <assert.h> char* my_strcat(char* dest, char* src) { char* ret = dest; assert(dest && src); while (*dest) { dest++; //找目标空间的‘\0’ } while (*dest++ = *src++)//从目标空间的‘\0’处开始拷贝 { ; } return ret; //strcat函数返回的是目标空间的起始位置。 }
- strcat函数本质上可以认为是一个拷贝函数,只不过要先找到目标函数的结尾 ‘\0’ 处,从目标函数的结尾 ‘\0’ 处开始拷贝。
- strcat 函数的返回值是目标函数的起始位置,所以返回类型是 char*。
4、strcmp函数
4.1 strcmp函数功能介绍
strcmp函数是字符串比较函数,比较的是两个字符串中对应字符的ASCII值。
int strcmp(const char* str1,const char* str2);
strcmp函数展示
#include <stdio.h> #include <string.h> int main() { char arr1[] = "abcdef"; char arr2[] = "abq"; int ret = strcmp(arr1, arr2);//返回值是大于0,小于0,等于0的整数 if (ret > 0) { printf(">0\n"); } else if (ret == 0) { printf("==\n"); } else { printf("<0\n"); } return 0; }
打印结果
- 如何判断两个字符串的大小?
两个字符串的大小比较的是两个字符串中对应字符的ASCII值,如果相同,就比较下一对,直到不同或者都遇到 ‘\0’, ‘\0’也要参与比较。如代码中的两个字符串前两个字符对应相等,第三个字符‘c’的ASCII值小于字符‘q’,所以arr1中的字符串小于arr2中的字符串。- 第一个字符串大于第二个字符串,则返回大于0的数字。
- 第一个字符串等于第二个字符串,则返回0。
- 第一个字符串小于第二个字符串,则返回小于0的数字
4.2 模拟实现strcmp函数
#include <assert.h> int my_strcmp(const char* s1, const char* s2) { assert(s1 && s2); while (*s1 == *s2) { if (*s1 == '\0') { return 0; } s1++; s2++; } if (*s1 > *s2) { return 1; } else { return -1; } }
- 目标字符串与源字符串都不能改变,所以加上const修饰。
- 避免目标字符串的指针与源字符串的指针为空指针,所以对其断言。
- 在这里,将返回值设定成了 -1、0、1。
strcpy函数、strcat函数、strcmp函数都是长度不受限制的字符串函数。下面分别介绍strncpy函数、strncat函数、strcnmp函数,这些是长度受限制的字符串函数,即可以规定长度进行拷贝、追加、比较。
5、strncpy函数
5.1strncpy函数功能介绍
类比strcpy函数,不过多了一个参数----->要拷贝几个字符的个数。
char* strncpy(char* destination,const char* source,size_t num);
strncpy函数展示
int main() { char arr1[] = "abcdef"; char arr2[] = "qwertyuiop"; strncpy(arr1, arr2, 6);//可以指定拷贝的字符个数 printf("%s\n", arr1); return 0; }
打印结果
- 拷贝了6个字符从源字符串到目标空间
如果源字符串的长度小于num个,则拷贝完源字符串之后,在目标的后边追加0,直到num个。如下所示
int main() { char arr1[] = "abcdef"; char arr2[] = "qwe"; strncpy(arr1, arr2, 6);//要拷贝的字符个数大于字符串中字符真正的个数,后面自动填充‘\0’,直到6个。 printf("%s\n", arr1); return 0; }
- 前面三个 ‘\0’,是为了满足六个字符,最后一个 '\0’是自动添加的,用来结尾。
6、strncat函数
6.1strncat函数功能介绍
类比strncat函数,不过多了一个参数----->要追加几个字符的个数。
char* strncat(char* destination,const char* source,size_t num);
strncat函数展示
#include <stdio.h> #include <string.h> int main() { char arr1[20] = "abcdef"; char arr2[] = "qwertyuiop"; strncat(arr1, arr2, 5);//追加5个字符过去,同时‘\0’也会追加过去。当追加的个数大于源字符串的字符个数时,只会补加一个‘\0’。 printf("%s\n", arr1); return 0; }
打印结果
- 追加5个字符过去,同时‘\0’也会追加过去。
- 当追加的个数num,大于源字符串的字符个数时,不会追加num个 ‘\0’ ,只会补加一个 ‘\0’ 用以结尾。
7、strncmp函数
7.1 strncmp函数功能介绍
类比strcmp函数,不过多了一个参数----->要比较几个字符的个数。
int strncmp(const char* str1,const char* str2,size_t num);
strncmp函数展示
#include <stdio.h> #include <string.h> int main() { char arr1[] = "abcdef"; char arr2[] = "abcdq"; char arr3[] = "qwertyuiop"; int ret = strncmp(arr1, arr2, 4); int rets = strncmp(arr1, arr3, 4); printf("%d\n", ret); printf("%d\n", rets); return 0; }
打印结果
- arr1与arr2比较前4个字符,相等。
- arr1与arr3比较前4个字符,arr1储存的字符小于arr3储存的字符。
下面介绍字符串查找函数
8、strstr函数
8.1 strstr函数功能介绍
strstr函数是子串查找函数,看str2是不是str1的子串,是的话,返回的是str2在str1中第一次出现的地址,如果不是,返回空指针。
char* strstr(const char* str1 ,const char* str2);
strstr函数展示
#include <stdio.h> #include <string.h> int main() { char arr1[] = "abcdefabcdef"; char arr2[] = "cdef"; char* ret = strstr(arr1, arr2);//找不到,返回空指针。找到了,返回的是子串第一次出现的地址。 if (NULL == ret) { printf("找不到子串"); } else { printf("%s\n",ret); } return 0; }
打印结果
- cdef是子串,返回第一次出现在arr1中的地址,即第一个c的地址,printf函数根据返回的地址,依次向后打印字符串。
- 如果不是子串,返回空指针。
8.2 模拟实现strstr函数
#include <assert.h> char* my_strstr(const char* str1, const char* str2) { assert(str1 && str2); const char* s1 = str1; const char* s2 = str2; const char* cur = str1; while (*cur) { s1 = cur; s2 = str2; while (*s1 && s2 && (*s1 == *s2)) { s1++; s2++; } if (*s2 == '\0') { return (char*)cur; //找到了 } cur++; } return NULL; //找不到 }
- 将str1与str2分别赋给s1与s2,利用s1与s2往字符串后面遍历查找相同的部分。
- cur 是用来记录str1与str2字符串第一次出现相等字符时的位置,等找到子字符串后,用来作为返回值。
- 如图所示,第一次进入,在两个字符串的起始位置,字符不相等,所以cur不会在这记录,cur++;
- 第二个while循环是用来往后找字符串相同的部位的。第二个while循环的循环条件首先要保证 *s1与 *s2不为空字符,为空字符的话,说明s1或者s2已经找到字符串结尾了,循环中止。*s1与 *s2指向的字符不相等,循环也要中止。
- 如果第二个while循环跳出来后,判断一下 *s2是否为空字符,为空字符的话,说明s2已经遍历一遍,s2中所有的字符都有在s1中被找到。此时s2中储存的字符串是s1的子字符串。
- 第二个while循环跳出来后,并且又判断没有找到,将cur记录的位置进行修正,往后走一步,cur++;,此时第一个while进行第二次循环,将cur的位置赋值给s1,s1被修正(s1=cur++,说明s1此时的位置是上次开始记录位置的后一个,从这里开始重新与s2进行比对),再将s2的起始位置修正回来,重新进行比对。
- 如果 cur 都往后遍历到空字符了,还是没有找到,说明s2不是s1的子字符串,返回空指针NULL。
- 函数参数的设定都用const修饰了,所以 *s1与 *s2与 *cur都要用const修饰,使得 = 左右两边变量的权限是保持一致的,不然 = 右边的变量收到const修饰,= 左边的变量不受到const修饰。
- 返回类型是char* ,返回值是记录位置,cur之前被const所修饰了,此时要改回来,强制类型转换----->char*。
9、strtok函数
9.1 strtok函数功能介绍
char* strtok(char* str,const char* sep);
- sep参数是个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str中的下一个标记,并将其用 ‘\0’ 结尾,返回一个指向这个标记的指针。
- strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容,并且可修改。
- strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
- 如果字符串中不存在更多的标记,则返回NULL指针。
strtok函数展示
#include <stdio.h> #include <string.h> int main() { char arr[] = "zhengguobin@163.com"; char buf[30] = { 0 }; strcpy(buf, arr); const char* sep = "@."; printf("%s\n",strtok(buf, sep));//只找第一个标记 printf("%s\n", strtok(NULL, sep));//是从保存好的开始继续往后找 printf("%s\n", strtok(NULL, sep)); return 0; }
打印结果
- 由打印结果可见,字符串根据分割标记被分割了。
- 如果再添加一个printf(“%s\n”, strtok(NULL, sep));,则会打印(NULL)。因为返回值是 NULL。
上面的打印过程可以优化,不用写三个打印函数。如下
#include <stdio.h> #include <string.h> int main() { char arr[] = "zhengguobin@163.com"; char buf[30] = { 0 }; strcpy(buf, arr); const char* sep = "@."; char* str = NULL; for (str = strtok(buf, sep); str != NULL; str = strtok(NULL, sep)) { printf("%s\n", str); } //printf("%s\n", strtok(buf, sep));//只找第一个标记 //printf("%s\n", strtok(NULL, sep));//是从保存好的开始继续往后找 //printf("%s\n", strtok(NULL, sep)); return 0; }
打印结果
10、strerror函数
10.1 strerror函数功能介绍
返回错误码所对应的错误信息。函数的参数是错误码。
char* strerror(int errnum);
strerror函数展示
#include <stdio.h> #include <string.h> int main() { printf("%s\n", strerror(0)); printf("%s\n", strerror(1)); printf("%s\n", strerror(2)); printf("%s\n", strerror(3)); return 0; }
打印结果
- 打印结果为这些错误码对应的错误信息。
strerror函数的一般使用方式为:strerror(errno));,如下
#include <stdio.h> #include <string.h> #include <limits.h> #include <errno.h> #include <stdlib.h> int main() { int* p = (int*)malloc(INT_MAX);//malloc函数向堆区申请内存,参数为申请的空间大小。当申请开辟空间失败时,返回空指针。 if (p == NULL) { printf("%s\n", strerror(errno)); perror("malloc"); //相比strerror,perror会直接将错误信息打印出来,参数随便给一个字符或者字符串就行,用来提醒是哪个地方发生了错误 return 1; } else { printf("成功了\n"); } return 0; }
打印结果
- 当程序运行产生错位时,会将错误信息对应的错误码存放在全局变量errno中,所以全局变量 errno作为strerror函数的参数。errno的头文件为 errno.h
- malloc函数是向堆区申请内存空间的函数,参数为申请的空间大小。当申请开辟空间失败时,返回空指针。头文件为 stdlib.h。
- INT_MAX为整形空间的最大值,在32为环境下,申请这么大的空间会失败。INT_MAX的头文件为limits.h。
- 代码中的 perror函数是什么?
相比strerror函数,perror函数会直接将错误信息打印出来。参数随便给一个字符或者字符串就行,参数也会打印出来,用来提醒是哪个地方发生了错误。在此代码中,如果写的是perror(“a”);,那么对应的打印结果是 a: Not enough space。perror函数的头文件是 stdio.h。
下面介绍内存操作函数,可以从字节的尺度上操作任意类型的数据,而不是仅仅限于字符串。
11、memcpy函数
11.1 memcpy函数功能介绍
void* memcpy(void* destination,const void* source,size_t num);
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到‘\0’的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。即不能自己拷贝自己。
memcpy函数展示
#include <stdio.h> #include <string.h> int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9 }; int arr2[5] = { 0 }; memcpy(arr2, arr1, 20);//20代表20个字节 int i = 0; for (i = 0; i < 5; i++) { printf("%d ", arr2[i]); } }
打印结果
- 一个整型等于四个字节,拷贝20个字节就是拷贝5个整型元素。
- 不能自己拷贝自己----->memcpy(arr1, arr1, 20);得到的结果是未定义。
11.2 模拟实现mencpy函数
#include <assert.h> void* my_memcpy(void* dest, const void* src, size_t count) { assert(dest && src); void* ret = dest; while (count--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; //dest = (char*)dest; //src = (char*)src; //*dest = *src; //dest = dest + 1; //src = src + 1; //错误写法 } return ret; }
- 根据mencpy函数本来的定义,我们将模拟的函数返回类型与参数都定义为 void*,为什么?
答:对于mencpy函数来说,可以操作任意类型的数据,所以对参数的设置要具有包容性,通过 void* 接收参数后再用强制类型转换符转换为 char*。- 又为什么再接收参数后,再函数内部要转化为char*,而不是其他类型的指针?
答:同样是为了具有包容性,char*类型一次只访问一个字节,字节是内存的最小单位(最小单位不是比特,比特是内存的最基本单位),所以什么类型的数据都可以通过一个一个字节访问完毕。- dest = (char*)dest;
src = (char*)src;
* dest = * src;
dest = dest + 1;
src = src + 1;
在编译时,会提醒第三个式子被const所修饰,第四、五个式子void* 是未知大小,编译不通过,可是我们在第一第二个式子已经转化为char* 了,为什么不通过?
答:1. 强制类型抓换不是永久的。2.我们已经对函数的参数有了明确的类型定义,这种参数的类型一经定义就不能变,强制类型转换后得到一个临时变量,再赋值给变量,导致强制类型转化的生命周期在这里非常短。所以在这里的每一次赋值前都要进行强制类型转化,而不是只在开头强制转化一次。
12、memmove函数
12.1 memmove函数功能介绍
与mencpy函数类比
void* memmove(void* destination,const void* source,size_t num);
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。即可以自己拷贝自己。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
memmove函数展示
#include <stdio.h> #include <string.h> int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 }; memmove(arr1+2, arr1, 20);//20代表20个字节 int i = 0; int sz = sizeof(arr1) / sizeof(arr1[0]); for (i = 0; i < sz; i++) { printf("%d ", arr1[i]); } }
打印结果
- 将arr1的前五个字符从arr1[2]的位置开始拷贝。
12.2 模拟实现memmove函数
自己拷贝自己时,遇到的三种情况,从src拷贝到des
- des<src,从前向后拷贝,即从src所指向的地方,往des所指向的地方一一往后对应拷贝。
- des>src&&des<src+count,从后向前拷贝,即从src+count所指向的位置,往des+count所指向的位置一一向前拷贝。
- des>src+count,从前向后拷贝,或者从后向前拷贝都可以。
- 为什么有上面三条的规定?
答:当des<src 时。如上图所示,假设将 arr[2] 开始的20个字节(即五个整数)拷贝到 arr[1]的位置上去,如果从后向前拷贝,即先将 7放到 6的位置上,然后准备将 6放到 5的位置上,此时发现源空间的 6已经被 7给覆盖了。
同理,当des>src&&des<src+count 时,如果从前向后拷贝,也会出现源空间的数据被覆盖的情况。
当des>src+count 时,源空间与目标空间没有存在重叠的情况,从前向后或者从后向前拷贝都可以。这也是我们对两个不同的位置的数据进行拷贝的一般情况,即 11节中所展示的案例。
memmove函数展示
#include <stdio.h> #include <assert.h> void* my_memmove(void* dest, const void* src, size_t count) { assert(dest && src); void* ret = dest; //1 if (dest < src) //从前向后拷贝 { while (count--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } } else //从后向前拷贝 { while (count--) { *((char*)dest + count) = *((char*)src + count); //第一次进来,这里的count已经变成了19 } } return ret; } int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[10] = { 1,2,3,4,5,6,7,8,9,10 }; my_memmove(arr1 + 2, arr1, 20);//20代表20个字节 my_memmove(arr2, arr2 + 2, 20); int i = 0; int sz = sizeof(arr1) / sizeof(arr1[0]); for (i = 0; i < sz; i++) { printf("%d ", arr1[i]); } printf("\n"); for (i = 0; i < sz; i++) { printf("%d ", arr2[i]); } }
打印结果
- 我们在这里设计的是,当 dest<src从前向后拷贝,除了这种情况,另外两种情况都选择从后向前拷贝。
- 函数返回值是存储数据是起始位置,在这里即是数组的地址。
除了上述设计外,还可以设计当 des>src&&des<src+count时,从后向前拷贝,除了这种情况,另外两种情况都选择从前向后拷贝。代码展示如下
#include <assert.h> void* my_memmove(void* dest, const void* src, size_t count) { assert(dest && src); void* ret = dest; //2 if (dest > src && dest < ((char*)src + count))//从后向前拷贝 { while (count--) { *((char*)dest + count) = *((char*)src + count); //第一次进来,这里的count已经变成了19 } } else //从前向后拷贝 { while (count--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } } return ret; }
13、memcmp函数
13.1 memcmp函数功能介绍
比较从ptr1和ptr2指针开始的num个字节,返回值同strcmp函数的返回值一致
int memcmp ( const void* ptr1, const void* ptr2, size_t num );
mencmp函数展示
#include <stdio.h> #include <string.h> int main() { int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 1,2,3,4,0x11223305 }; int ret = memcmp(arr1, arr2, 16); //前17个字节都是一样的。 printf("%d", ret); }
打印结果
- 由于数据是进行的小端存储,在内存中可以看到arr1与arr2 的前17个字节都是一样的。
- ptr1指向的字节大于ptr2指向的字节,则返回大于0的数字。
- ptr1指向的字节等于ptr2指向的字节,则返回0。
- ptr1指向的字节小于ptr2指向的字节,则返回小于0的数字。
14、memset函数
14.1 memset函数功能介绍
memset函数是将指针 ptr指向的位置处往后所有的字节都改变成自己想要的。
void* memset ( void* ptr, int value, size_t num );
memset函数展示
#include <stdio.h> #include <string.h> int main() { int arr[] = { 1,2,3,4,5 }; memset(arr+1, 2, 20); int i = 0; int sz = sizeof(arr) / sizeof(arr[0]);//指定的每个字节都是设置成2了,而不是将4个整数设置成2。 for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } }
打印结果
- 从内存中可以看到,从arr+1处往后的十六个字节全部都设置成2。
- 十六进制的 0x02020202对应的十进制就是33686018。
15、总结
- 创建实现某种功能的函数时,先分析需求,再一步步去实现需求
- 为了提高代码的可执行性和可靠性,函数参数的设置要具有包容性,同时根据需求,确定函数的返回值类型;有时为了保证指针参数所指向的内容不变,要用const修饰参数;函数内部用断言函数assert避免传过来的是空指针,断言函数不仅仅可以用来避免空指针,其可以避免自己想避免的其他东西。