C 语言经典笔试面试题全解析:覆盖高频考点,助力求职通关

在 C 语言求职笔试与面试中,核心考点集中在内存管理、指针操作、关键字用法、结构体设计等领域。本文基于《经典笔试面试题目.pdf》,将 62 道题目按 “面试问答类”“笔试编程类” 分类拆解,每道题均附详细解析与避坑指南,帮你系统掌握考点,轻松应对考核。

一、面试高频问答类(理解与记忆考点)

1. 野指针的危害(海康威视面试)

野指针定义:指向非法内存地址的指针,常见场景包括指针未初始化、指向已释放的内存、指针越界访问。
核心危害

  • 访问非法内存会直接触发程序崩溃(如 Linux 下的 “Segmentation Fault”);
  • 若野指针指向合法但非预期的内存(如其他变量地址),会篡改该内存数据,导致逻辑混乱;
  • 嵌入式场景中,野指针可能误操作硬件寄存器,引发设备异常。
    避坑建议:指针初始化时设为NULL,内存释放后及时置NULL,使用前检查指针有效性。

2. 简述 i++ 和 ++i(模拟面试 + 延伸)

两者均为自增运算符,核心差异在于 **“自增时机” 与 “返回值”**,具体对比如下:

运算符执行逻辑示例(初始 i=3)结果
i++先返回 i 的原始值,再执行 i=i+1j = i++;j=3,i=4
++i先执行 i=i+1,再返回自增后的值j = ++i;j=4,i=4

延伸考点

  • 循环中(如for(i=0;i<5;i++)),若不使用自增返回值,两者效率无差异;
  • 表达式中(如a = i++ + ++i),结果依赖编译器(未定义行为),面试中需避免此类写法。

3. sizeof 和 strlen 区别(模拟面试)

sizeofstrlen是 C 语言中常用的 “长度计算工具”,但本质与用法完全不同,对比如下:

对比维度sizeofstrlen
本质属性运算符(编译期计算结果)库函数(运行期计算结果)
计算范围变量 / 类型占用的总字节数(含字符串结束符\0字符串中有效字符数(不含\0,需以\0结尾)
支持参数变量、数组、数据类型(如sizeof(int)仅字符串指针或字符数组(非字符串会导致越界)
示例(char str[]="abc"sizeof(str)=4('a'+'b'+'c'+'\0')strlen(str)=3(仅统计 'a'/'b'/'c')

4. new/malloc、delete/free 的区别(模拟面试延伸)

malloc/free是 C 语言内存管理工具,new/delete是 C++ 运算符,核心区别如下:

对比维度malloc/freenew/delete
所属语言C 语言标准库函数C++ 语言运算符
类型检查无(返回void*,需强制转换类型)有(自动匹配变量类型,无需转换)
构造 / 析构仅分配 / 释放内存,不调用对象的构造 / 析构函数分配内存时调用构造函数,释放时调用析构函数(对象场景必需)
内存失败处理返回NULL,需手动判断抛出bad_alloc异常,需用try-catch捕获
数组支持需手动计算总字节数(如malloc(10*sizeof(int))支持数组语法(new int[10],需用delete[]释放,避免内存泄漏)

5. 内存泄漏和堆内碎块(海康威视面试)

  • 内存泄漏
    动态分配的堆内存(如malloc/new申请的内存)使用后未释放,导致内存被永久占用。程序运行时间越长,可用内存越少,最终可能因内存耗尽崩溃。
    示例:char* p = (char*)malloc(100); 未调用free(p);p指向的 100 字节内存永久泄漏。

  • 堆内碎块
    频繁分配 / 释放不同大小的堆内存后,堆中产生大量 “空闲小内存块”(碎片)。这些碎片总容量足够,但单个碎片无法满足大内存分配需求,导致内存利用率下降。
    解决方案:使用内存池(预先分配大块内存,按需分割),或尽量使用固定大小的内存分配。

6. volatile 关键字(移远通信面试)

核心作用:告诉编译器 “变量的值可能被意外修改(如硬件中断、多线程操作)”,禁止编译器对该变量进行优化(如将变量缓存到寄存器),确保每次访问都从内存读取最新值。

典型应用场景

  1. 硬件寄存器地址变量:volatile unsigned int *uart_reg = (unsigned int*)0x1234;(寄存器值可能被硬件自动修改);
  2. 多线程共享变量:volatile int flag = 0;(线程 A 修改flag后,线程 B 需实时读取最新值,避免读取寄存器缓存);
  3. 中断服务函数中修改的变量:volatile int count = 0;(主函数读取count时,需获取中断修改后的最新值)。

7. static 关键字(纳思达面试、移远通信面试)

static关键字的核心作用是 **“限制作用域”** 和 **“延长生命周期”**,修饰不同对象时效果不同:

修饰对象具体作用示例
局部变量生命周期延长至整个程序运行期(仅初始化 1 次),作用域仍局限于函数内部void func(){ static int a=0; a++; }(多次调用funca会累加)
全局变量作用域限制在当前.c文件(其他文件无法通过extern引用),避免全局变量重名冲突static int g_val = 10;(仅当前文件可访问g_val
函数作用域限制在当前.c文件(其他文件无法调用),隐藏内部实现细节static void inner_func(){};(仅当前文件可调用inner_func

8. const 关键字(纳思达面试)

const的核心是定义 “只读变量”,防止意外修改,提升代码安全性,常见用法有 4 种:

  1. 修饰普通变量const int a = 10;a的值不可修改,必须初始化,否则编译报错);
  2. 修饰指针(重点考点)
    • const int *p;:指针p指向的内容不可改(如*p = 20;报错),但p本身可改(如p = &b;合法);
    • int *const p = &a;:指针p本身不可改(如p = &b;报错),但指向的内容可改(如*p = 20;合法);
    • const int *const p = &a;:指针p和指向的内容均不可改;
  3. 修饰函数参数void func(const int x);(防止函数内部修改x,保护传入参数);
  4. 修饰函数返回值const int func();(返回值不可作为左值修改,如func() = 20;报错)。

9. 一个参数既可以被 const 修饰也能被 volatile 修饰吗?

可以constvolatile的作用不冲突,前者限制 “程序代码不能修改参数”,后者限制 “编译器不能优化参数”,适用于 “参数值由外部(非程序代码)修改,程序仅读取” 的场景。

典型示例:硬件状态寄存器(值由硬件修改,程序仅读取,不可修改):
void read_sensor(const volatile unsigned int *status_reg);

  • const:程序不能修改status_reg指向的寄存器值;
  • volatile:编译器不能优化status_reg,确保每次读取都是最新的硬件状态。

10. 内联函数 inline(模拟面试)

核心作用:将函数代码 “嵌入” 到调用处,避免函数调用的开销(如栈帧创建、参数传递、返回地址保存),提升程序执行效率,适用于短小、频繁调用的函数(如工具函数、数学计算函数)。

与宏定义的区别(高频对比考点)

对比维度内联函数(inline)宏定义(#define)
类型检查有(遵循函数类型规则,参数 / 返回值均需匹配)无(仅文本替换,可能因类型不匹配导致错误)
调试支持支持(可设置断点,查看变量值)不支持(替换后代码合并,无法定位宏内部问题)
副作用风险低(参数仅计算 1 次)高(参数可能多次计算,如#define ADD(a,b) a+bADD(1,2*3)结果为 7)

注意inline是 “编译器建议”,若函数体过大(如含循环 / 递归),编译器会忽略inline,按普通函数处理。

11. C 语言中 EOF 的值是多少?(扬智科技面试)

EOF(End of File,文件结束符)是 C 语言标准库中的宏定义,在<stdio.h>中定义为-1

核心原因:字符类型char默认是signed char(范围 - 128~127),-1的二进制为0xFF,不会与普通字符(0~127)冲突,可明确标识 “文件读取结束”。

典型用法:判断文件读取是否结束:

int ch;
FILE *fp = fopen("test.txt", "r");
while ((ch = fgetc(fp)) != EOF) { // 读取到EOF时退出循环
    putchar(ch);
}

12. C 语言与 C++ 中 NULL 的值是多少?(通则康威简答延伸)

NULL是 “空指针常量”,但在 C 语言与 C++ 中的定义不同,核心差异如下:

语言NULL 定义注意事项
C 语言#define NULL (void*)0本质是void*类型,可隐式转换为其他指针类型(如int* p = NULL;合法)
C++#define NULL 0C++ 不允许void*隐式转换为其他指针类型,若定义为(void*)0会编译报错;C++11 后推荐用nullptr(专门的空指针常量,避免与0混淆)

13. 递归次数过多如何优化

递归次数过多会导致栈溢出(每次递归调用都会创建栈帧,栈空间有限,通常为几 MB),优化方案有 4 种:

  1. 迭代改写:用for/while循环替代递归,彻底避免栈帧叠加。例如,斐波那契数列的递归实现改迭代:
    // 迭代实现斐波那契(第n项)
    int fib(int n) {
        if (n <= 2) return 1;
        int a = 1, b = 1, c;
        for (int i = 3; i <= n; i++) {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
    
  2. 尾递归优化:将递归调用放在函数最后一步(尾递归),编译器可将其优化为循环(如 GCC 支持尾递归优化)。例如,阶乘的尾递归实现:
    int fact_tail(int n, int res) {
        if (n == 0) return res;
        return fact_tail(n - 1, n * res); // 尾递归调用
    }
    int fact(int n) {
        return fact_tail(n, 1);
    }
    
  3. 扩大栈空间:通过编译器参数临时扩大栈大小(如 GCC 的-Wl,--stack=1024000,将栈设为 1MB),仅适用于临时测试,不推荐生产环境。
  4. 记忆化搜索:对重复计算的递归(如斐波那契、动态规划问题),用数组 / 哈希表缓存中间结果,减少递归次数。

14. 堆和栈的区别 (纳思达面试)

堆和栈是程序内存的两个核心区域,管理方式与用途差异极大,对比如下:

对比维度栈(Stack)堆(Heap)
分配方式编译器自动分配 / 释放(函数调用时分配栈帧,函数返回时释放)程序员手动分配 / 释放(malloc/freenew/delete
生长方向从高地址向低地址生长从低地址向高地址生长
大小限制通常较小(由操作系统决定,如 Windows 默认 1MB,Linux 默认 8MB)较大(接近系统可用物理内存,受虚拟内存限制)
分配效率高(栈帧操作是 CPU 指令级操作,无碎片)低(需查找空闲内存块,可能产生碎片,分配耗时较长)
存储内容函数参数、局部变量、返回地址、寄存器现场动态分配的变量(如大型数组、结构体实例)
安全性高(自动释放,不易泄漏)低(需手动释放,易出现内存泄漏、野指针)

15. scanf 的原理 (模拟面试)

scanf是 C 语言标准输入函数,用于从输入流(默认是键盘)读取数据,核心原理分 3 步:

  1. 解析格式字符串:按%d(整数)、%s(字符串)、%c(字符)等格式符,确定要读取的数据类型;
  2. 跳过空白字符:默认跳过输入流中的空白字符(空格、回车、制表符\t),除非格式符是%c(需读取空白字符);
  3. 写入目标内存:将解析后的数据存入对应指针指向的内存,返回成功读取的数据项数(若读取失败或到达 EOF,返回EOF)。

常见坑点

  • 读取字符串时,scanf("%s", str)会自动在末尾加\0,但不检查str的数组大小,需确保str足够大,避免内存越界;
  • 读取%c时,会读取空白字符(如前一个%d后的回车),需在%c前加空格(如scanf(" %c", &ch))跳过空白。

16. printf 的原理 (模拟面试延伸)

printf是 C 语言标准输出函数,用于向输出流(默认是屏幕)打印数据,核心原理分 3 步:

  1. 格式化数据:按格式字符串(如%d%s%.2f)将数据(整数、字符串、浮点数等)转换为字符流;
  2. 写入输出缓冲区:默认使用 “行缓冲”,即字符流先存入缓冲区,当遇到\n、缓冲区满或调用fflush(stdout)时,才将缓冲区内容刷新到屏幕;
  3. 返回结果:返回成功打印的字符数(若失败,返回负数)。

常见坑点

  • \n时,数据可能停留在缓冲区,需调用fflush(stdout)强制刷新(如嵌入式开发中打印调试信息);
  • 格式符与数据类型不匹配(如printf("%d", 3.14)),会导致未定义行为(输出乱码)。

17. 内核链表 kernellist (模拟面试)

内核链表是 Linux 内核中常用的双向循环链表,与普通链表的核心区别是 **“链表节点不包含数据,数据包含链表节点”**,灵活性极高,支持任意数据结构挂接。

后续,将会继续补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值