2.11 memcpy
void* memcpy(void* destination,const void* source,size_t num);
注:
1)函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
2)这个函数遇到’\0’的时候并不会停下来。
3)如果source和destination有任何的重叠,复制的结果都是未定义的。
struct { char name[40]; int age; } person, person_copy; int main () { char myname[] = "Pierre de Fermat"; memcpy ( person.name, myname, strlen(myname)+1 ); person.age = 46; memcpy ( &person_copy, &person, sizeof(person) ); printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );//Pierre de Fermat 46 return 0; }
memcpy 负责拷贝两块独立空间中的数据,那么重叠内存的拷贝,是怎么做的呢?–>利用memmove
2.12 memmove
void* memmove(void* destination, const void* source, size_t num);
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的,如果源空间和目标空间出现重叠,就得使用memmove函数处理。
int main() { char str[] = "memmove can be very useful......"; memmove(str + 20, str + 15, 11); puts(str); return 0; }
2.13 memcmp
int memcmp(const void* ptr1, const void* ptr2, size_t num);
比较从ptr1和ptr2指针开始的num个字节,返回值与strncpy相同,区别:strncpy比较的是指针指向的字符的大小,memcmp比较的是所有类型的大小。
3. 库函数的模拟实现
3.1 模拟实现strlen(三种方式)
方式1:计数器
int my_strlen(const char * str) { int count = 0; while(*str) { count++; str++; } return count; }
方式2:递归
int my_strlen(const char * str) { if(*str == '\0') return 0; else return 1+my_strlen(str+1); }
方式3:指针减指针(得到的为指针指向类型的个数)
int my_strlen(char *s) { char *p = s; while(*p != ‘\0’ ) p++; return p-s; }
3.2 模拟实现strcpy
char* my_strcpy(char* dest, const char* src) { char* ret = dest; assert(dest && src); while(*dest++ = *src++) { ; } return ret; }
3.3 模拟实现strcat
char* my_strcat(char* dest, const char* src) { char* ret = dest; assert(dest && src); while (*dest)//不能在里面直接++,这样会跳过一个'\0'的位置 { dest++; } while (*dest++ = *src++) { ; } return ret; } int main() { char arr1[20] = "abcdef"; //char arr1[20]; char arr2[5] = "ABCD"; char* p = my_strcat(arr1, arr2); printf("%s\n", p); printf("%s\n", arr1); return 0; }
strcat不能自己给自己追加,会无限循环下去:
3.4 模拟实现strstr(下一篇扩展KMP算法)
三指针迭代,一指针记录位置,两个指针向后++查找
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 ( *s2 != '\0' && *s1 == *s2) { s1++; s2++; } if (*s2 == '\0') { return (char*)p; } p++; } return NULL; } int main() { char arr1[] = "abcdef"; char arr2[] = "def"; char* ret = my_strstr(arr1, arr2); if (ret == NULL) { printf("子串不存在\n"); } else { printf("%s\n", ret); } return 0; }
3.5 模拟实现strcmp
int my_strcmp(const char* s1, const char* s2) { assert(s1 && s2); while (*s1 == *s2) { if (*s1 == '\0') { return 0;//相等 } s1++; s2++; } return (*s1 - *s2); } int main() { char arr1[20] = "zhangsan"; char arr2[] = "zhangsanfeng"; int ret = my_strcmp(arr1, arr2); if (ret < 0) printf("arr1<arr2\n"); else if (ret == 0) printf("arr1=arr2\n"); else printf("arr1=arr2\n"); return 0; }
3.6 模拟实现memcpy
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;//强转的目的是让其+1迈过的长度为char,即一个字节 } return ret; } int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[10] = { 0 }; my_memcpy(arr2, arr1, 20);//20个字节大小,即5个整形 return 0; }
为什么memcpy不能拷贝重叠的内存呢?通过模拟实现我们知道:当我们拷贝到重叠的部分时,那里已经被新的值所覆盖,故再次拷贝将会把新值拷贝过去。
当然,这是模拟实现的memcpy,当我们真正运用memcpy时,会发现出来的结果跟memmove一样:
这是因为由于VS本身的功能会将这个错误避免,在不同编译器下,函数处理的结果可能会不一样,而模拟实现的my_memcpy才是memcpy真正的逻辑原理,因此,memmove还是非常必要的
3.7 模拟实现memmove
还以上面arr1数组为例,由于dest>src,故我们可以从右往左拷贝,即从高地址拷贝到低地址:
即对模拟实现的my_memcpy稍作拷贝上的修改即可:
void* my_memmove(void* dest, void* src, size_t num) { assert(dest && src); void* ret = dest; if (dest < src) { //与my_memcpy拷贝方向一致,即从前到后 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; } void test3() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; my_memmove(arr1 + 2, arr1, 20); //1 2 1 2 3 4 5 8 9 10 for (int i = 0; i < 10; i++) { printf("%d ", arr1[i]); } } int main() { test3(); return 0; }
4. 实现一道典型题目
这个分支是我后续加上的(2022.7.25),因为我突然想到了一个新的方法来实现这道题目。在此之前,可以用两种方法实现,今天之后,就变成三种了:
- 指针数组
- 两步翻转
- strtok辅助实现
- 1.指针数组
那先来介绍第一种,这也是我刚接触到这道题第一反应想到的方法:
思路是通过记住每一个单词的首地址,通过数组(数组的每一个元素为指针变量,即指针数组)封装起来,再将其地址逆序按%s输出:
#include<stdio.h> #include<string.h> int main() { char* at[500]; char arr[100]; gets(arr); int len = strlen(arr); int i = 0; int j = 0; at[j++] = &arr[0]; for(i=0;i<len;i++) { if(arr[i]==' ') { at[j++] = &arr[i+1]; arr[i] = '\0'; } } //arr[i] = '\0'; for(i=j-1;i>=0;i--) { printf("%s ",at[i]); } return 0; }
当然结果是对的,但实际上,我们只是记录其位置将其打印而已,字符串本身并没有发生变化。因此,我们参考一下第二种方法:
- 2.两部翻转
两步翻转,即先翻整体,再翻局部
#include<stdio.h> #include<string.h> #include<assert.h> void reverse(char* left, char* right) { assert(left); assert(right); while (left < right) { char tmp = *left; *left = *right; *right = tmp; left++; right--; } } int main() { char arr[101] = { 0 }; //输入 gets(arr);//I like beijing. //逆置 int len = strlen(arr); //1. 逆序整个字符串 reverse(arr, arr + len - 1); //2. 逆序每个单词 char* start = arr; while (*start) { char* end = start; while (*end != ' ' && *end != '\0') { end++; } reverse(start, end - 1); if (*end != '\0') end++; start = end; } //输出 printf("%s\n", arr); return 0; }
即这种方法在原数组的基础之上逆序了单词。
接下来,也就是增加这个分支的目的,即第三个方法,因为上文已经提到过strtok的用处,这里就不具体描述了。
- 3.strtok 辅助实现
#include<stdio.h> #include<string.h> #include<stdlib.h> int main() { char str[] = "I like beijing."; char* pch; char flag = *(str + strlen(str) - 1); pch = strtok(str, " "); char** arr = (char**)malloc(sizeof(char*) * 5); int j = 0; while (pch != NULL) { arr[j++] = pch; printf("%s ", pch); pch = strtok(NULL, " "); } printf("\n"); for (int i = j-1; i>=0; i--) { printf("%s ", arr[i]); } free(arr); return 0; }
这种方式其实也是类似于第一种的思想,存储位置,逆序打印,但很明显,这种方法在strtok的辅助之下使其更为简洁和灵活,值得关注的是,这里的malloc大小为20个字节,如果单词数比较多的话,可以多开辟空间,是没有影响的,因为j记录了实际有效单词的数量。 当然即便面对单词之间有多个空格,strtok也会自动将其忽略,这也是strtok的强大之处。
5.总结:
通过对以上函数的了解,对于字符数组的操作以及内存类的函数会变得得心应手,要用其功能必先了解其原理。那么,这篇文章就到这里,码字不易,你们的支持将是我前进的不竭动力!