C语言常见面试题【备战春招秋招】

一、用变量a给出下面的定义

此题除了笔试题出现,在面试或双选会中hr也偶尔会让人去手写

一个有十个指针的数组,该指针指向的是一个整形数

一个指向有十个整型数组的指针

一个指向函数的指针,该函数有一个整形参数并返回一个整形数

一个有10个指针的数组,该指针指向一个函数,该函数有一个整形参数并返回一个整型数

int *a[10];
int (*a)[10];
int (*a)(int);
int (*a[10])(int);

二、函数指针一般用在什么地方?

1)需要调用函数但不知道函数名,只能知道函数地址的情况

  • 回调函数:当一个对象需要响应另一个对象的某些事件时,可以使用函数指针作为回调机制的一部分。例如,在图型用户界面中,可能需要将鼠标点击或键盘输入的事件处理程序与某个函数绑定,以便执行特定的操作。

  • 动态库:在编程语言如C/C++中,函数指针也用于访问外部库中的函数。通过将函数地址存储在函数指针中,可以间接地调用这些函数。

    2)建立函数指针表(函数指针数组)管理多个函数

    • 多态性:在面向对象编程中,函数指针可以用于实现方法的重载或多态。例如,一个基类可能定义多个同名虚函数,而子类可以通过重写这些函数来实现不同的行为。(虚函数表)

      3)函数指针作为参数传递给其他函数

      • signal():该函数用于设置信号处理函数,在触发对应信号的时候,调用指定的函数。
      • pthread_create():该函数用于创建线程,传递一个函数指针用于指定线程处理函数

        三、指针常量与常量指针的定义以及区别,分别用在什么地方?

        int a = 10;
        const int *p = &a;      //常量指针,不能通过指针p去修改变量a的值,但可以改变p的指向
        int const *p = &a;      //常量指针,同上
        int * const p = &a;     //指针常量,可以通过指针p去修改变量a的值,但不能改变p的指向

        常量指针一般用于传递常量数据给函数,以确保数据不被修改。另外调用函数的人员也能明确传入的参数不会被修改。

        指针常量可以确保指针不会意外地指向其它非法地址,减少段错误出现概率。比如在C++中的引用的实现原理就是指针常量,另外C++中this指针也是指针常量。

        四、strlen与sizeof的区别?

        strlen主要用于计算字符串的长度,不包含'\0'。而sizeof主要用于计算类型的大小。如下示例

        char buf[1024] = "hello";
        char *p = buf;
        char buf2[] = "hello";
        sizeof(buf);   //1024,buf类型为char[1024],所占空间大小为1024
        strlen(buf);   //5, 字符串长度为5
        sizeof(p);     //32位系统上为4,64位系统上为8  p的类型为char *是指针类型
        strlen(p);     //5,计算的依然是字符串长度
        sizeof(buf2);  //6,数组长度会被初始化为6,最后一位用于保存'\0'
        strlen(buf2);  //5,字符串长度依然为5

        五、define与XX的区别

        define是预处理指令,用于定义常量、宏等,是在编译之前进行文本替换。

        1)define与const的区别

                C/C++ 语言可以用 const 来定义常量,也可以用 #define 来定义常量。但是前者比后\者有更多的优点:

                const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会 产生意料不到的错误(边际效应)。

                有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。 

        2)define与typedef的区别       

                typedef 可以为数据类型定义新的名称,使代码更易读,减少重复代码,提高代码的可维护性。而如果使用宏替换数据类型,在声明一系列变量时会有问题。例如:

        #define FLOAT_POINTER float *
        FLOAT_POINTER pa, pb;

        预处理器会将该声明转换为

        float * pa, pb;  //pa为float的指针类型,但pb是float类型

        typedef是在C++11前替换类型别名的唯一选择(C++11后可使用using替换)。不过typedef只能用于替换类型别名。

        3)宏函数与函数 / 内联函数 的优缺点

        宏函数优点是效率高,没有函数调用开销,但缺点是可读性差,容易出错。

        函数的优点是可读性好,易于调试与维护,缺点是有函数调用开销。

        扩展:函数调用开销在C++中可以用内联函数规避,内联函数是在编译阶段进行函数体替换,但缺点是函数体内必须简洁,不能使用循环以及开关语句。

        六、请描述C语言五种内存模型

        • BSS段:用于存放未初始化的全局变量和静态变量,在程序执行前会被清零。

        • 数据段(data):用于存放已初始化的全局变量和静态变量。

        • 只读数据段(rodata):存放一些常量,例如常量字符串

        • 代码段(text):存放程序的机器指令,即可执行的代码。

        • 栈:用于存放函数的局部变量、函数参数、返回地址等,以及函数调用的上下文信息。

        • 堆:用于动态分配内存,由程序员手动管理,通常用于存放动态分配的数据结构和对象

          数据段与BSS段可以归为 静态区

          只读数据段与代码段可以归为 常量区

          一般描述可以直接说:堆区、栈区、静态区、常量区、代码段

          七、C语言程序文件的编译过程分为几个步骤?每个步骤都做什么事情?

          1. 预处理(Preprocessing):处理以#开头的预处理指令,如#include、#define等,生成经过宏替换的源文件。

          2. 编译(Compilation):将预处理后的源文件翻译成汇编代码。

          3. 汇编(Assembly):将汇编代码翻译成目标文件(Object file)。

          4. 链接(Linking):将目标文件与库文件链接,生成可执行文件。

          八、请描述下堆和栈的区别

          注意此题有陷阱,在数据结构中与内存中都存在堆与栈的概念。所以需要分开回答。

          栈在数据结构中分为顺序栈与链式栈,为顺序存储结构,存储数据要求先进后出,堆在数据结构中主要使用二叉树实现,分为大顶堆与小顶堆。

          栈在内存中一般有系统管理,分配方式按照“后进先出”原则进行,另外栈的空间容量较小,访问速度相对较快。堆在内存中的管理由程序员自行管理,使用完毕后必须手动释放,否则会导致内存泄漏,堆的空间容量相对较大,可以动态扩展空间,访问堆需要通过指针间接访问,响应速度相对较慢。

          九、const关键字的理解

          为最常见的面试问题之一,发挥空间很大,切忌只谈一点点内容,最好回答问题的时候增加使用位置

          const关键字用于声明常量,表示该变量的数值在程序执行期间不能被修改。主要用法如下

          1. 常量声明:用于声明常量,如const int MAX_SIZE = 100;。参考第五题与define的区别

          2. 函数参数:用于声明函数参数为常量,以防止函数修改参数值。

          3. 指针:用于声明指针为常量指针或指向常量的指针,以确保指针指向的数据不被修改。参考第三题

          4.成员函数(C++):用于声明成员函数为常量成员函数,表示该函数不会修改对象的状态。

          使用const能提高函数健壮性与可读性。我们一般在能加const的地方都建议加上const。

          十、static关键字的理解

          1. 在全局变量前使用static:

                  静态全局变量:限制全局变量的作用域,使其只在当前文件内可见,不同文件中的同名静态全局变量不会冲突。

          2. 在局部变量前使用static:

                  静态局部变量:使局部变量在函数调用结束后仍保持其值,不会被销毁,下次调用函数时仍保持上次的值。

          3. 在函数前使用static:

                  静态函数:限制函数的作用域,使其只在当前文件内可见,不同文件中的同名静态函数不会冲突。


                  总的来说,static关键字可以用于限制变量或函数的作用域,使其在特定范围内可见,同时可以保持静态变量的值不变

          十一、内存分配的方式

          内存分配方式有三种:

          (1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。

          (2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

          (3) 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

          十二、volatile关键字的理解

          此题目多出现于嵌入式相关岗位或者涉及操作系统开发的公司,应用层开发的岗位考的概率不大

          volatile关键字用于告诉编译器该变量是易变的,可能会在未知的时间被改变,编译器不应该对其进行优化。volatile关键字一般会用在以下情况:

          1. 外部硬件状态:用于声明与外部硬件相关的变量,如IO端口状态、定时器计数器等,这些变量的值可能会在程序之外被改变。

          2. 多线程共享变量:用于声明多线程共享的变量,防止编译器对变量的读写进行优化,确保变量的实时性。

          3. 信号处理:用于声明在信号处理程序中可能被改变的变量,以确保在信号处理程序中对变量的访问是正确的。

          结语

          编写该文章目的主要为想从事相关工作的同学找到一份好的工作,以上题目在面试中经常出现,如果有在外面试的朋友发现有更常见更经典的题目也可以私信告知,后续也会更新到博客当中。

          如果有朋友想系统的学习相关知识,从事相关的行业,可以私信我,有一些经典的电子档书籍资料和开源网课学习链接。