《C语言面试通关秘籍:高频考点深度解析与实战》 C语言面试通关秘籍高频考点深度解析与实战C语言作为编程界的常青树其面试考点往往集中在内存管理、指针操作、底层原理等方面。根据这张思维导图我们将面试内容划分为六大板块。让我们逐一击破。一、数组和指针这部分是C语言面试的“重灾区”特别是复杂的类型声明。1、指针数组指针数组即存放指针的数组。例如:int*p1,*p2,*p3;//声明指针变量int*arr{p1,p2,p3};//arr是指针数组存放3个int*2、数组指针数组指针即指向数组的指针变量。例如intarr{12345};int(*p)[5]arr;//p是一个指针变量指向的数组存放5个intint(*p)[5]arr;//是错的很多人误以为数组名就是首元素地址这个说法并不严谨。C 语言里数组名在大多数表达式会隐式转换成首元素地址但sizeof(arr)和arr是两个例外。arr和arr打印出来地址数值一样但是类型不同arr隐转后是int*代表首元素地址arr是int (*)[5]数组指针代表整个数组的地址。数组指针赋值时不能直接写parr必须写parr。指针加减的偏移量由指针的类型决定。3、函数指针函数指针即指向函数的指针变量。例如intAdd(inta,intb){returnab;}int(*P)(int,int)Add;//*p是一个指向函数的指针变量函数的返回值是int参数是(int,int)4、函数指针数组函数指针数组即数组里存的是函数指针。C虚函数表即函数指针数组。例如intadd(inta,intb){returnab;}intsub(inta,intb){returna-b;}intmain(){// 函数指针数组int(*ops[2])(int,int){add,sub};printf(%d\n,ops[0](10,5));// 输出 15return0;}代码解析int(*ops[2])(int,int){add,sub};*ops是一个函数指针数组数组的容量是2存放的函数的返回值是int需要接收的参数是:(int,int)。printf(%d\n,ops[0](10,5));// 输出 15ops[0](10, 5):拿到ops第0号元素,即int add(int a,int b),传递参数(10,5)。5、const和指针const如果放在*的左边修饰的是指针指向的内容保证指针指向的内容不能通过指针来改变但是指针变量本身的内容可变。例如const int * p;const如果放在*的右边修饰的是指针变量本身保证了指针变量的内容不能修改但是指针指向的内容可以通过指针改变。例如int* const p;例如从图中可以看出结果和我们得出的结论一致。所以在项目中某些不会改变的参数就可以使用const修饰来防止该变量被误改提升程序的健壮性。6、sizeof 和指针和数组sizeof操作符计算变量/类型所占内存大小包括\0编译时确定。数组名在sizeof(数组名)中代表整个数组算出数组总字节大小。指针变量sizeof得到的是指针本身的大小32 位机器 4 字节64 位机器 8 字节数组名传参到函数里会退化成为指针在函数内部sizeof就变成指针大小。例如图中arr数组一共有5个int类型数据一共20字节一个指针大小为8字节。7、strlen 和指针和数组strlen接收的是地址不管你传的是字符数组名还是char*指针都会当成地址去遍历。sizeof编译期求内存大小strlen运行期扫描内存。扫描时遇到\0就停止。例如8、回调函数通过函数指针调用的函数。如果把函数的指针地址作为参数传递给另一个函数当这个指针被用来调用其所指向的函数时我们就说这是回调函数。二、库函数的模拟实现1. memcpymemcpy不处理内存重叠按字节从前往后拷贝。#includestddef.hvoid*my_memcpy(void*dest,constvoid*src,size_tn){if(destNULL||srcNULL||n0)returndest;char*d(char*)dest;constchar*s(constchar*)src;while(n--){*d*s;}returndest;}要点参数用void *内部转成char *按字节拷贝。返回dest支持链式调用。不处理重叠内存重叠时行为未定义。2. memmovememmove需要处理内存重叠。判断 dest 和 src 的相对位置若dest src或dest src n不重叠或 dest 在前从前往后拷。否则dest 在 src 后面且重叠从后往前拷避免覆盖。#includestddef.hvoid*my_memmove(void*dest,constvoid*src,size_tn){if(destNULL||srcNULL||n0)returndest;char*d(char*)dest;constchar*s(constchar*)src;if(ds||dsn){// 从前往后拷贝while(n--){*d*s;}}else{// 从后往前拷贝dn-1;sn-1;while(n--){*d--*s--;}}returndest;}要点核心是判断重叠方向决定拷贝顺序。比较d s时严格来说跨对象指针比较是 UB但工程实践中常见更严谨可用uintptr_t比较。更严谨的写法#includestdint.hvoid*my_memmove(void*dest,constvoid*src,size_tn){if(destNULL||srcNULL||n0)returndest;char*d(char*)dest;constchar*s(constchar*)src;if((uintptr_t)d(uintptr_t)s||(uintptr_t)d(uintptr_t)sn){while(n--)*d*s;}else{dn-1;sn-1;while(n--)*d--*s--;}returndest;}3. strstr在haystack中查找首次出现needle的位置返回该位置指针找不到返回NULL。暴力实现#includestddef.hchar*my_strstr(constchar*haystack,constchar*needle){if(haystackNULL||needleNULL)returnNULL;if(*needle\0)return(char*)haystack;constchar*hhaystack;while(*h){constchar*p1h;constchar*p2needle;while(*p1*p2*p1*p2){p1;p2;}if(*p2\0)return(char*)h;h;}returnNULL;}要点needle为空串时返回haystackC 标准规定。外层遍历 haystack 每个起点内层逐字符比较。时间复杂度 O(m*n)m 为 haystack 长度n 为 needle 长度。KMP 实现进阶如果面试要求优化可以手写 KMP#includestdlib.h#includestring.hstaticvoidbuild_next(constchar*pat,int*next,intlen){next[0]-1;inti0,j-1;while(ilen-1){if(j-1||pat[i]pat[j]){i;j;next[i]j;}else{jnext[j];}}}char*my_strstr_kmp(constchar*haystack,constchar*needle){if(haystackNULL||needleNULL)returnNULL;intn(int)strlen(haystack);intm(int)strlen(needle);if(m0)return(char*)haystack;if(nm)returnNULL;int*next(int*)malloc(sizeof(int)*m);if(!next)returnNULL;build_next(needle,next,m);inti0,j0;while(injm){if(j-1||haystack[i]needle[j]){i;j;}else{jnext[j];}}free(next);if(jm)return(char*)(haystacki-m);returnNULL;}要点next数组记录最长公共前后缀。匹配失败时j next[j]主串指针i不回退。时间复杂度 O(nm)。三、自定义类型1、内存对齐规则第一个成员在偏移量为0处。其他成员要对齐到对齐数编译器默认对齐数与成员自身大小的较小值的整数倍地址。结构体总大小为最大对齐数的整数倍。嵌套结构体对齐到自己的最大对齐数整数倍。示例structS{charc;// 偏移0大小1inti;// 对齐数4偏移4大小4 (浪费3字节)doubled;// 对齐数8偏移8大小8 (浪费0字节)};// 总大小8的倍数故为16字节2. 联合体、共用体概念所有成员共用同一块内存空间大小等于最大成员的大小。应用判断大小端。unionU{inti;charc;};// 如果u.i1u.c为1则是小端为0则是大端3. 位段网络报文的报头不常考概念结构体成员后面加冒号和数字指定占用的比特位数。structS{inta:2;// a占2个比特位intb:5;// b占5个比特位};注意位段的内存分配依赖编译器可移植性差。四、数据存储1. 整形的存储规则原反补原码直接二进制表示。反码符号位不变其余取反。补码反码1。内存存储计算机内存中一律存储补码。2. 为什么要存补码统一了0的表示0和-0在补码中都是全0。正数的原反、补、码一样而负数的补码是自己的反码1而计算机又只有加法器那么就可以利用加法来实现减法。例如a-b可以写成a(-b)也就是a的反码加上b的反码。3. 大小端如何判断大端低位字节存高地址。小端低位字节存低地址。代码判断intcheck_sys(){inti1;return*(char*)i;// 返回1是小端0是大端}4. 浮点数的存储遵循IEEE 754标准。公式(-1)^S * M * 2^E。32位float1位符号8位指数23位尾数。64位double1位符号11位指数52位尾数。5. 类型提升和截断提升参与运算时短整型char, short会自动提升为int。截断将长类型赋值给短类型时直接截取低位。charc300;// 300的二进制是 1 0010 1100截断后为 0010 1100即44五、编译、链接1. 宏考察很多轻松实现宏函数概念预处理阶段的文本替换。示例写一个宏求两个数的最大值。#defineMAX(a,b)((a)(b)?(a):(b))// 必须加括号否则会有优先级问题2. 编译链接的过程预处理展开头文件、宏替换、去注释、条件编译。生成.i文件。编译语法分析、词法分析、语义分析、生成汇编代码。生成.s文件。汇编将汇编代码转为机器码。生成.o文件目标文件。链接合并段表、符号表合并与重定位。生成可执行文件。3. 条件编译常用指令#ifdef,#ifndef,#endif,#if,#elif,#else。应用防止头文件重复包含。#ifndef__TEST_H__#define__TEST_H__// 头文件内容#endif2. 编译链接的过程预处理展开头文件、宏替换、去注释、条件编译。生成.i文件。编译语法分析、词法分析、语义分析、生成汇编代码。生成.s文件。汇编将汇编代码转为机器码。生成.o文件目标文件。链接合并段表、符号表合并与重定位。生成可执行文件。[外链图片转存中…(img-fSQLKLTT-1790504346561)]3. 条件编译常用指令#ifdef,#ifndef,#endif,#if,#elif,#else。应用防止头文件重复包含。#ifndef__TEST_H__#define__TEST_H__// 头文件内容#endif如有错误恳请指出。