目录
十、内存拷贝函数memmove(同一个数组里面重叠数字的拷贝):
一、指针高难度面试题详解
#include<stdio.h> int main() { char* c[] = { "ENTER","NEW","POINT","FIRST" }; char** cp[] = { c + 3,c + 2,c + 1,c }; char*** cpp = cp; printf("%s\n", **++cpp); printf("%s\n", *-- * ++cpp + 3); printf("%s\n", *cpp[-2] + 3); printf("%s\n", cpp[-1][-1] + 1); return 0; }
首先我们可以根据代码画出下图:
1、初始数据解读:
c时一个char*类型的指针数组,里面分别存有不同的字符串,cp是一个char**类型的指针数组,里面放的是指针c的地址+n的结果(c+3==c[3],其他相同),cpp是一个char***类型的指针,指向cp的首地址。(特别注意,以下的cpp指针指向的改变会随着printf程序中的使用而改变,不是独立存在的!!)
2、第一个printf解读:
*操作符和++操作符相比,++操作符优先计算,所有可以得到如图所示:
解释:cpp先++指向了cp中的c+2,通过一个*解引用操作符,可以得到cp中的值,这个值就是c+2,再通过一个解引用,可以得到cp指针中c+2指向的对象,即使POINT,所以第一个输入就会输入POINT。
3、第二个printf程序解读:
原理同上,++,--操作符优先考虑,但是+号最好计算,即可得到如图:
解释: ++cpp优先执行,cpp指向了cp[2]的位置,即使c+1的位置,通过解引用操作符(*),可以得到cp[2]中的值(c+1),再通过--使c+1变成了c,再通过解引用操作符(*),获得了cp[3]即是c指向的值ENTER,最好因为后面的+3使指针cp[3]向后走三个元素,就到了E的位置,所以最后输出的是ER;(注意指针指向的是首元素地址,+操作使其向后偏移步长值)。
4、第三个printf程序解读:
本题中注意,cpp[-2]==*(cpp-2),意思是活得cpp[-2]中的地址然后解引用,特别注意,cpp的指向并不会改变,改变的是他指向cp后,步长-2,从而得到了cp[0]的地址,可得如图:
解释:cpp重新因为cpp[-2]指向了cp[0]的位置,再因为cpp[-2]取得了cp[0]中的c+3的地址,再通过解引用操作符(*)取得了c+3指向的内容的地址,c+3指针指向FIRST,+3使其向后偏移3个步长值,最后走到S的位置,所以最后输出ST;
5、第四个printf程序解读:
本题注意cpp[-1][-1]==*(*(cpp-1)-1),注意同上,cpp指向不改变,改变的是指向后的cp中取得的地址的位置,可得到如图:
解释:cpp指向cp[2],通过-1,这里减的是cp数组中的下标,cp[2-1]=cp[1],cp[1]中是c+2,再解引用,得到了c+2的地址,c+2,再次通过-1得到了c+1的地址,这里是对cp中的元素进行-1,c+2-1=c+1,再通过解引用使指针指向了NEW,+1使其向后偏移一个字符,最后输出EW。
最后输出结果:
大家可以多动手画图编码,调试、试试,会有不一样的体验。
二、计算字符串长度strlen函数拟写和易错点:
#include<stdio.h> #include<string.h> int main() { if(strlen("abcde")-strlen("abc")>0) { printf(">"); } else { printf("<"); } //因为strlen的返回值是size_t,是无符号数,所以他不会<0; } #include<stdio.h> #include<assert.h> size_t my_strlen(const char* str) { assert(str);//断言str不为空指针 char* end = str; while (*end != '\0') { end++; } return end-str; } int main() { char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d", len); return 0; }
解释:通过end指针后移去找\0,最后返回end指针-str首地址指针的值就可以得到两个指针之间的元素,注意要用size_t类型的,因为不都是int类型。
三、字符串复制函数strcpy函数拟写:
#include<stdio.h> #include<assert.h> char* my_strcpy(char* dest, const char* src) { assert(dest); assert(src); char* ret = dest; while(*dest++=*src++) { ; } return ret; } int main() { char a1[20] = { "abc" }; char a2[] = { "ni hao ya" }; my_strcpy(a1, a2); printf("%s", a1); return 0; }
解释:返回值为char*类型,返回他的首地址来进行打印,src在函数中是被复制的值,不会被改变,所以需要用const修饰,先是断言空指针,定义一个char*类型的ret来存放复制到的位置的首地址,通过解引用操作符(*)把src的值赋给dest,再分别指针++,dest++,src++寻找下一个,直到寻找到'/0'就停止。
四、字符串追加函数strcat的拟写:
#include<stdio.h> char* my_strcat(char* des, const char* src) { char* cur = des; //先找到需要追加的字符串中'\0'的位置 while (*cur != '\0') { cur++; } //从'\0'的位置开始一个个字符串粘贴过去,通过赋值的方法 while(*cur++=*src++) { ; } //返回被追加的字符串的首地址 return des; } int main() { char a1[20] = { "hello " }; char a2[] = {"world"}; my_strcat(a1, a2); printf("%s\n", a1); return 0; }
解释:先通过被追加字符串首地址的指针++,找到他的‘\0’,再从'\0'开始复制字符串,从而完成字符串的追加。
五、字符串比较函数strcmp:
#include<stdio.h> #include<assert.h> int my_strcmp(const char* s1, const char* s2) { assert(s1 && s2); while (*s1 == *s2) { s1++; s2++; } return *s1 - *s2; } int main() { char a1[] = "abc"; char a2[] = "abc"; int ret = my_strcmp(a1, a2); if(ret>0) { printf("a1>a2"); } else if(ret<0) { printf("a1<a2"); } else { printf("a1==a2"); } return 0; }
解释:首先断言空指针情况,通过while循环首先排除相等的字符,然后到了不同的字符跳出循环,通过*s1-*s2就可以得到哪个字符大,再通过int类型的返回值,得到整形数据,就可以完成字符串的比较了。
六、字符串匹配函数strstr:
在一个字符串中寻找另一个字符串是否存在
存在:返回字符串所在的地址
不存在:返回NULL
#include<stdio.h> char* my_strstr(const char* s1, const char* s2) { char* p = s1; //创建一个char*类型的p接受s1的位置 char* a = s1; //创建a指向s1的位置 char* b = s2; //创建b指向s2的位置 while (*p) //p是s1和s2相等的位置,如果p一直指导了\0还没找到一样的就直接退出循环 { a = p; //每次循环让a的位置指向p b = s2; //让b的位置在s2处 while (*a != '\0' && *b != '\0' && (*a == *b)) //a和b中指向内容相同就进入循环继续寻找下一个相等的,直到不相等就退出循环 { a++; b++; } if(*b=='\0') //如果b(b在a中寻找相同的字符串)中走到了\0,证明已经找到了相同的字符串,直接返回第一个相等的位置 { return p; } p++; //每次a和b不相等,直接p向后找 } //循环完都没找到就返回空指针 return NULL; } int main() { char a[] = "abcdeeedcba"; char b[] = "ee"; char* p = my_strstr(a, b); if (p == NULL) { printf("不存在"); } else printf("%s\n", p); return 0; }
解释:这个strstr函数就是在前一个字符串中找与后一个字符串相等的片段,如果有相等的直接输出相等位置后面所有的字符串,自我实现大概要注意,首先选择一个两个字符第一次相等的位置p,记录下来,相等时p不动,a和b++向后走,如果b走到了\0,则返回p位置,主函数就直接输出p后面的所有字符了,如果b没走到\0,则p++,重新进入while循环寻找,最后走到了*p==\0,则返回空指针,证明前一个字符串没有后面字符串的内容。
七、字符串分隔函数strtok:
这个函数主要通过分隔符号对对应字符串进行分割
#include<stdio.h> #include<string.h> int main() { char a[] = "3478611368@qq.com"; char b[200] = { 0 }; char c[200] = { 0 }; strcpy(b, a); strcpy(c, a); //因为strtok函数会改变原来字符串内容,将分隔符变为\0,所以我们需要用一个临时变量来测试 char* p = "@."; //char*类型指针存在分隔符号 char* str = strtok(b, p); //第一次分割b,使指针指向首元素地址,向后寻找\0停止 printf("%s\n", str); str = strtok(NULL, p); //第二次分割直接传入NULL,在strtok函数中会自动寻找上次记忆留下的\0,从\0后面开始寻找分隔符号变为\0, printf("%s\n", str); str = strtok(NULL, p); //同上 printf("%s\n", str); //高级优雅做法 char* set = NULL; //首先定义空指针 for (set = strtok(c, p); set != NULL; set = strtok(NULL, p)) //第一步赋值第一个分隔符前的首元素地址给set,再通过判断非空循环,第一次以后都是传入NULL,所以可以用循环直接写 //不需要自己多次手写第一次以后的内容,更加方便简洁; { printf("%s\n", set); } return 0; }
运行结果:
八、错误码信息函数: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)); printf("%s\n", strerror(4)); //相应数字分别对应相应的错误信息 //将错误码信息记录到错误码的变量中 //errno c语言提供的全局的错误变量 FILE* pf = fopen("text.txt", "r");//打开文件text.txt,r表示用read的方式 if (pf == NULL) { perror("fopen");//使用perror函数打印errno中所对应的错误信息 printf("%s\n", strerror(errno));//打印错误码中的错误信息 return 1; } //读文件 fclose(pf); pf = NULL; return 0; }
九、内存拷贝函数memcpy(不重叠的拷贝):
#include<stdio.h> #include<assert.h> void* my_memcpy(void* dest, void* src, size_t num) //函数定义,返回值为void*指针,参数类型也为void*可以满足任意类型的参数。 { void* ret = dest;//定义一个void*类型变量存放首地址,因为后面dest指针要向后走 assert(dest && src);//断言空指针 while (num--)//循环结束条件是传入的字节--后为0 { *(char*)dest = *(char*)src;//将两个指针强制转换为char*类型可以一个字节一个字节的赋值,从字节传值可以满足任意类型 dest = (char*)dest + 1;//void*类型的指针不能自己++,只能将其强转为其他指针,又因为这里需要逐个字节寻找, //所以需要使用char*类型一个字节一个字节的寻找 src = (char*)src + 1;//这个src同上 } return ret;//最后返回首元素地址 } int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[20] = { 0 }; my_memcpy(arr2, arr1, 40); for (int i = 0; i < 10; i++) { printf("%d ", arr2[i]); } printf("\n"); char* a = "abcde"; char b[20] = { 0 }; my_memcpy(b, a, 5); printf("%s\n", b); float c[] = { 1.0f,2.0f,3.0f,4.0f,5.0f }; float d[20] = { 0 }; my_memcpy(d, c, 20); for (int i = 0; i < 5; i++) { printf("%f ", d[i]); } return 0; }
十、内存拷贝函数memmove(同一个数组里面重叠数字的拷贝):
#include<stdio.h> void* my_memmove(void* dest, void* src, size_t num) { void* ret = dest; if (dest < src) //如果是重叠的拷贝,被拷贝元素src>dest,就应该从前面开始拷贝,就不会出现后面有元素重叠的问题。 //例如1 2 3 4 5 6 7 8 9 10中,要把4 5 6拷贝到2 3 4的位置,此时src=4,dest=2,src>dest,从前面拷贝 // 如果从后面拷贝,先让4的位置变成了6,3的位置变成了5,到了2的位置时,就会出现拷贝到了6的情况, //因为4的位置已经被拷贝了6了,所以应该从前面开始拷贝,2变成4,3变成5,4变成6,这样就不会出现重复了 { while (num--) { *((char*)dest) = *((char*)src); dest = (char*)dest + 1; src = (char*)src + 1; } } else //如果是被拷贝元素src<dest,就应该从两者的后面开始拷贝,就不会出现前面的元素重叠 //例如:1 2 3 4 5 6 7 8 9 10中,要把1 2 3拷贝到 3 4 5的位置,此时src=1,dest=3,dest>src,就应该从后面开始拷贝 //如果从前面开始拷贝,3的位置变成了1,4的位置变成了2,到了5的位置时就会拷贝到现在3的位置变成的1,就出现了重复 //所以应该从后面开始拷贝。5变成3,4变成2,3变成1; { while (num--)//因为他们自己本身占了一个字节,所以此处--刚好使他们少偏移一个字节,正确找到区间末尾 { *((char*)dest + num) = *((char*)src + num);//让他们字节加上偏移的字节量num-1,可以找到拷贝区间的最后一个。 } } return ret; } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; my_memmove(arr + 2, arr, 20); for (int i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }