您的位置:首页 > 房产 > 家装 > 面试题(自整理)待完善

面试题(自整理)待完善

2024/12/23 15:38:14 来源:https://blog.csdn.net/m0_73769412/article/details/139370941  浏览:    关键词:面试题(自整理)待完善

目录

🧄🐳由于需要面试,所根据自身情况整理的面试题目,分享给大家,涵盖C语言、C++、单片机等。

一、C语言

1、gcc的编译过程?

1、static的用法

🍊修饰局部变量

 🍌修饰全局变量

🍐修饰函数

2、变量/函数的声明和定义之间有什么区别 

🍅函数的声明和定义

 1.1)函数的声明是函数的原型

 1.2)函数的定义是函数功能的确立

🥒变量的声明和定义(难) 

2.1)变量的声明

2.2)变量的定义

3、C语言的内存模型

1. 代码区

2.BSS段

3.数据段

4.堆区(Heap)

5.栈区(Stack)

4、解释堆和栈的区别

1.1 栈简介

1.2 堆简介 

 1.3 堆与栈区别

(1)管理方式不同

(2)空间大小不同

(3)生长方向不同

堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。 

(4)数据结构不同

(5)存放内容不同

(6)分配效率不同

5、指针问题

🍉指针常量与常量指针

🍌1.1常量指针

🍌 1.2指针常量

🥬1.3const即修饰指针,又修饰变量

🎴指针函数和函数指针

1.1 指针函数

1.2 函数指针

📫 指针数组和数组指针

1.1 指针数组 *p[n]

 1.2 数组指针 (*p)[n]

🀄函数指针数组 

 🔫指向函数指针数组的指针

6、C语言参数传递方式

7、#include<> 与#include ""的区别?

8、#define 宏定义

 9、ifndef/define/endif 的作用?

 10、c语⾔中有符号和⽆符号的区别?

 11、violate关键字

12、sizeof 和 strlen 的区别

13、设置地址为 0x67a9 的整型变量的值为 0xaa66

 14、常见的排序和思路

🚃冒泡排序

 🚃快速排序

 🚃插入排序

二、C++区域

😀C和C++的区别

😁new、delete、malloc、free之间的关系 

 😃构造函数和析构函数

1.1构造函数

1.2析构函数 

😅虚函数、纯虚函数 

1.1虚函数

😳typdef和define区别 

😖 什么是野指针

😞 线程与进程的区别

🍏 main 函数执行以前,还会执行什么代码? 

三、单片机区域

🍊STM32 启动过程

🍐介绍一下GPIO模式

🍋UART的介绍

 🍉I2C协议

🍇🍇SPI 

🍈ADC


🧄🐳由于需要面试,所根据自身情况整理的面试题目,分享给大家,涵盖C语言、C++、单片机等。

一、C语言

1、gcc的编译过程?

🍏gcc编译过程分为4个阶段:预处理、编译、汇编、链接。

  • 预处理:头⽂件包含、宏替换、条件编译、删除注释
  • 编译:主要进⾏词法、语法、语义分析等,检查⽆误后将预处理好的⽂件编译成汇编⽂件。
  • 汇编:将汇编⽂件转换成 ⼆进制⽬标⽂件
  • 链接:将项⽬中的各个⼆进制⽂件+所需的库+启动代码链接成可执⾏⽂件

1、static的用法

🍊修饰局部变量

  ——称为静态局部变量

static修饰局部变量时,会改变局部变量的存储位置,从而使得局部变量的生命周期变长

 其本质是:

一个局部变量是存储在栈区的,但是被static修饰后就存储在静态区了,因为存储类型的变化,生命周期就跟着变化了。

注意:static修饰局部变量只改变生命周期,不改变作用域!

 🍌修饰全局变量

  ——称为静态全局变量

首先我们要知道全局变量是具有 外部链接属性 的

全局变量被static修饰后,它的外部链接属性就变成内部链接属性,即这个全局变量只能在自己的.c文件内被使用,其他文件看不到也无法使用,相当于作用域缩小了。

🍐修饰函数

  ——称为静态函数

本质和全局变量很像:

函数本身也是有外部链接属性的;

被static修饰后,函数的外部链接属性被修改成内部链接属性,使得这个函数只能在自己的源文件内被使用,因此函数的作用域就变小了。

2、变量/函数的声明和定义之间有什么区别 

🍅函数的声明和定义

 1.1)函数的声明是函数的原型

声明部分的作用:

对有关标识符的属性进行说明

标识符例如变量、函数体、结构体、共用体等。一般在头文件中声明

 1.2)函数的定义是函数功能的确立

 函数的声明在声明部分,也可以在定义部分。

 函数的定义肯定不在函数的声明部分,它是一个文件中的独立模块一般在.C文件中定义

🥒变量的声明和定义(难) 

2.1)变量的声明

        声明部分的变量有两种:

1)需要建立存储空间的变量(如 int a;)——定义性声明(定义),即可说是声明,也可说是定义。

 2)不需要建立存储空间的变量(如extern int a;)——引用性声明,是声明,不是定义

        不需要建立存储空间的声明称为声明。

        所谓声明,其作用就是向编译系统发出一个信息,声明该变量是一个在后面定义的外部变量,仅仅是为了提前引用该变量而作的声明。        

2.2)变量的定义

        需要建立存储空间的声明称为定义。

  • 系统根据外部变量的定义分配存储单元,而不是根据声明分配存储单元。
  • 外部变量的初始化只能在定义时进行,不能在声明中进行。
  • extern 只用作声明,不用于定义。

3、C语言的内存模型

🧄C语言的内存模型分为5个区:代码区,BSS段,数据段,堆区和栈区。

1. 代码区

可执行代码、字符串常量

2.BSS段

  • 未初始化全局变量,未初始化全局静态变量

3.数据段

  • 已初始化全局变量、已初始化全局静态变量、局部静态变量、常量数据

4.堆区(Heap)

存放程序员创建的变量的区域,是一块不连续的区域,一般使用new,alloc,calloc,malloc关键字创建的变量,这些变量需要程序员调用delete,release,free关键字才能释放。若没有调用,则会造成内存泄露。

5.栈区(Stack)

存放函数的参数,局部变量的区域,由编译器自动分配和释放。通常在作用范围域外就会释放变量。形式上类似于数据结构中栈。因为在CPU的指令集中分配内存,所以操作快且效率高。但是空间就很有限。

4、解释堆和栈的区别

1.1 栈简介

栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈。

1.2 堆简介 

堆由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收,分配方式类似于链表。

 1.3 堆与栈区别
(1)管理方式不同

栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;

(2)空间大小不同

每个进程拥有的栈大小要远远小于堆大小。理论上,进程可申请的堆大小为虚拟内存大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;

(3)生长方向不同
堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。 
(4)数据结构不同

堆:一般使用数据结构中的树来管理。

栈:一般使用先进后出的队列来管理。

(5)存放内容不同

堆通常用于存储动态分配的对象,如数组、类对象和结构体等。而栈通常用于存储局部变量、函数参数、返回值和函数调用堆栈信息等。

(6)分配效率不同
  • 栈由操作系统自动分配,在CPU的指令集中完成分配并且有硬件层的支持(专门寄存器负责存储,专门指令负责压栈出栈)。
  • 堆是由系统内核API接口来完成申请与管理,实现机制较为复杂,并且频繁的内存申请容易产生内存碎片。这样栈的分配效率比堆高多了。

5、指针问题

🍉指针常量与常量指针
🍌1.1常量指针

语法:const  类型* 变量名;

  • const修饰指针
  • 指针的指向可改变(指向的地址可改变),指向的变量的值不可改变,
🍌 1.2指针常量

语法: 类型*  const  变量名;

  • const修饰变量
  • 指针指向的值可以改变,指针指向不可以改变
🥬1.3const即修饰指针,又修饰变量

语法: const  类型*  const  变量名;

  • 指针的指向和指针指向的值都不可以改

🎴指针函数和函数指针
1.1 指针函数

指针函数: 顾名思义,它的本质是一个函数,不过它的返回值是一个指针。

1.2 函数指针

与指针函数不同,函数指针 的本质是一个指针,该指针的地址指向了一个函数,所以它是指向函数的指针。
我们知道,函数的定义是存在于代码段,因此,每个函数在代码段中,也有着自己的入口地址,函数指针就是指向代码段中函数入口地址的指针

📫 指针数组和数组指针
1.1 指针数组 *p[n]

指针数组:是数组——装着指针的数组。

 1.2 数组指针 (*p)[n]

数组指针:是指针——指向数组的指针。

🀄函数指针数组 
//函数指针int (*p)(int,int);​//函数指针数组int (*pfArr[])(int,int);​
//对比int* p;//整形指针int* arr[];//整形指针数组
 🔫指向函数指针数组的指针
//函数指针int (*pf)(int,int);​//函数指针数组int (*pfArr[4])(int,int);​//指向函数指针数组的指针int (*(*ptr)[4])(int,int) = &pfArr;

6、C语言参数传递方式

  1. 传值,就是把你的变量的值传递给函数的形式参数,实际就是用变量的值来新生成一个形式参数,因而在函数里对形参的改变不会影响到函数外的变量的值。
  2. 传址,就是传变量的地址赋给函数里形式参数的指针,使指针指向真实的变量的地址,因为对指针所指地址的内容的改变能反映到函数外,也就是能改变函数外的变量的值。
  3. 传引用,实际是通过指针来实现的,能达到使用的效果如传址,可是使用方式如传值。

说几点建议:

  • 如果传值的话,会生成新的对象,花费时间和空间,而在退出函数的时候,又会销毁该对象,花费时间和空间。
  • 因而如果int,char等固有类型,而是你自己定义的类或结构等,都建议传指针或引用,因为他们不会创建新的对象。

7、#include<> 与#include ""的区别?

  1. #include<>到系统指定⽬录寻找头⽂件,
  2. #include ""先到项⽬所在⽬录寻找头⽂件,如果没有找再到系统指定的⽬录下寻找

8、#define 宏定义

预处理指令,用于在编译之前将标识符替换为特定的值或代码片段,宏缺乏类型检查

typedef相比:

typedef 编译阶段会检查错误,#define 预处理阶段不检查错误。

 9、ifndef/define/endif 的作用?

防止头文件被重复包含和编译。 头文件重复包含会增大程序大小,重复编译增加编译时间。

 10、c语⾔中有符号和⽆符号的区别?

  • 有符号:数据的最⾼位为符号位,0表示正数,1表示负数
  • ⽆符号:数据的最⾼位不是符号位,⽽是数据的⼀部分,只有正数,所以范围更大

 11、violate关键字

volatile是一个类型修饰符(type specifier), 防止编译器对代码进行优化

12、sizeof 和 strlen 的区别

1 sizeof 是一个操作符,strlen 是库函数
2 sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为‘\0‘的字符串作参数。
3 编译器在编译时就计算出了 sizeof 的结果。而 strlen 函数必须在运行时才能计算出来。并且 sizeof 计算的是数据类型占内存的大小,而 strlen 计算的是字符串实际的长度。
4 数组做 sizeof 的参数不退化,传递给 strlen 就退化为指针了。
 

13、设置地址为 0x67a9 的整型变量的值为 0xaa66

int *ptr;  
ptr = (int *)0x67a9;  
*ptr = 0xaa66;  

 14、常见的排序和思路

🚃冒泡排序

基本思路

冒泡排序是一个非常好理解的排序,顾名思义——冒泡,此时将要排序的数据从上至下排列,从最上面的数(第一个数据)开始对相邻的两个数据进行比较,较小的数据往上浮,较大的数据往下沉,达到排序的效果。

(1)对每一对相邻的元素进行比较,若第一个比第二个大,则调换这两个元素的位置,依次两两比较直到数据的最后一对,此为一轮操作。

(2)重复n轮此操作(n为元素的个数),不过每轮结束后的最后一个元素不用参与下一轮的比较,因为经历一轮排序后,最后一个元素一定比前面所有的元素都要大。

(3)因此每一轮需要比较的元素都在减少,一直到没有数可比较为止。(不过为了减少比较次数,可以记录每轮是否有数据的交换,如果没有交换,表明当前数据已经按从小到大的顺序拍好了,可直接跳出循环)

 🚃快速排序

基本思路:

快速排序的重点在于找一个基准值,将数列分为两部分——大于基准值的放在右边小于基准值的 放在左边。然后分别对这两部分重复次操作,一分为二,二分为四······直到每个元素自成一部分。

1.将数据的中间元素设为基准值,初始化令 ii 指向最左边个元素,令 jj 指向最右边个元素,通过i++i++从左往右找一个大于基准数的数,通过j−−j−−从右往左找一个小于基准数的数,交换两数的位置,直到i=ji=j。

2.如此不断的细分递归,达到排序的目的

 🚃插入排序

将数据分为两组——一组是有序的,一组是无序的,将无序数据中的元素依次插入到有序数据中,从而将整个数据变为有序的(这里的分组是潜意识的,实际上并不会用两个数组来分)

1.初始时,将第一个元素分为有序组(因为只有一个元素,所以认为它是排好序的),其余元素分为无序组

2.因此只需从第二个元素开始,依次在有序组中找到自己的位置,插入即可,直到最后一个元素。

3.但这并不意味着只需要一次循环,因为在“找自己的位置”的过程中,需要将自己与前面的元素相比较,若是自己较小,则将那个元素后移一位;若是自己较大,则将自己插入到上一次比较的位置

二、C++区域

😀C和C++的区别

  • C是面向过程的语言,是一个结构化的语言,考虑如何通过一个过程对输入进行处理得到输出
  • C++是面向对象的语言,主要特征是“封装、继承和多态
  • C和C++动态管理内存的方法不一样,C是使用malloc/free,而C++除此之外还有new/delete关键字。
  • C++中有引用,C中不存在引用的概念

😁new、delete、malloc、free之间的关系 

new/delete,malloc/free都是动态分配内存的方式

1)malloc对开辟的空间大小严格指定,而new只需要对象名

2)new为对象分配空间时,调用对象的构造函数,delete调用对象的析构函数

3)malloc/free是库函数,new/delete是C++运算符

 😃构造函数和析构函数

1.1构造函数

构造函数在创建对象时自动执行,往往用来做一些初始化工作,例如对成员变量赋值、预先打开文件等,当然你也可以让构造函数什么也不做,或者做一些提示工作。

1.2析构函数 

创建对象时系统会自动调用构造函数进行初始化工作,销毁对象时系统也会自动调用析构函数来进行清理工作,例如释放分配的内存、关闭打开的文件等。

特点:

  1. 无类型
  2. 无返回值
  3. 名字与类名相同
  4. 不带参数,不可重载,析构函数只有一个!
  5. 析构函数前“~” (取反符,表示逆构造函数)

😅虚函数、纯虚函数 

1.1虚函数

在基类中,通过virtual关键字修饰的函数叫做虚函数,核心理念就是通过基类访问派生类定义的函数,是C++中多态性的一个重要体现。利用基类指针访问派生类中的虚函数,这种情况下采用的是动态绑定技术。

 1.2纯虚函数

  • 纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”.纯虚函数不能实例化对象。
  • 纯虚函数是只有声明没有实现的虚函数,是对子类的约束,是接口继承
  • 包含纯虚函数的类是抽象类,它不能被实例化,只有实现了这个纯虚函数的子类才能生成对象

😳typdef和define区别 

  • #define是预处理命令,在预处理是执行简单的替换,不做正确性的检查
  • typedef是在编译时处理的,它是在自己的作用域内给已经存在的类型一个别名

😖 什么是野指针

野指针不是NULL指针,是未初始化或者未清零的指针,它指向的内存地址不是程序员所期望的,可能指向了受限的内存。

成因:

1)指针变量没有被初始化

2)指针指向的内存被释放了,但是指针没有置NULL

3)指针超过了变量了的作用范围,比如b[10],指针b+11

😞 线程与进程的区别

1. 根本区别:进程是操作系统进行资源分配的最小单元,线程是操作系统进行运算调度的最小单元。

2. 从属关系不同:进程中包含了线程,线程属于进程。

3. 开销不同:进程的创建、销毁和切换的开销都远大于线程。

4. 拥有资源不同:每个进程有自己的内存和资源,一个进程中的线程会共享这些内存和资源。

5. 控制和影响能力不同:子进程无法影响父进程,而子线程可以影响父线程,如果主线程发生异常会影响其所在进程和子线程。

6. CPU利用率不同:进程的CPU利用率较低,因为上下文切换开销较大,而线程的CPU的利用率较高,上下文的切换速度快。

7. 操纵者不同:进程的操纵者一般是操作系统,线程的操纵者一般是编程人员。

🍏 main 函数执行以前,还会执行什么代码? 

全局对象的构造函数会在main 函数之前执行

三、单片机区域

🍊STM32 启动过程

  1. 通过Boot引脚设定,寻找初始地址
  2. 初始化栈指针 __initial_sp
  3. 指向复位程序 Reset_Hander
  4. 设置异常中断 HardFault_Handler
  5. 设置系统时钟 SystemInit
  6. 调用C库函数 _main

🍐介绍一下GPIO模式

GPIO 8种工作模式(gpio_init.GPIO_Mode):

  1. GPIO_Mode_AIN 模拟输入
  2. GPIO_Mode_IN_FLOATING 浮空输入
  3. GPIO_Mode_IPD 下拉输入
  4. GPIO_Mode_IPU 上拉输入
  5. GPIO_Mode_Out_OD 开漏输出
  6. GPIO_Mode_Out_PP 推挽输出
  7. GPIO_Mode_AF_OD 复用开漏输出
  8. GPIO_Mode_AF_PP 复用推挽输出

🍋UART的介绍

双线双向全双工异步通信,有两根线,(接收线)RX 和 (发送线)TX ,数据帧是1位起始位8位数据位,无奇偶校验位一位停止位,使用UART串口协议传输数据时,需要规定双方的传输速率(波特率)一致

  • 波特率(Baud Rate)单位bps是用于衡量串口通信速度的单位,它表示每秒钟发送的比特数。如果一个串口的波特率为9600,就表示该串口在一秒钟内可以发送9600个比特的数据。
 🍉I2C协议

同步半双工协议,有两根条信号线,一条是双向的串行数据线,一条是时钟线, 每个连接到IIC总线上的器件都有一个唯一的器件地址,通过寻址的方式选择从机

IIC产生四种信号,

开始信号(SCL高电平期间,SDA由高电平到低电平),

结束信号(SCL高电平期间,SDA由低电平到高电平),

应答信号(SCL高电平期间,SDA拉低),

非应答信号(SCL高电平期间,SDA拉高),且总线空闲时都处于高电平状态,

写数据(开始信号,发送7位器件地址+一个读写位,0是写 1是读,等待回应即可开始传输数据)

读数据(开始信号,发送7位器件地址+一个读写位。从设备回应,即可开始传输数据)

时钟线拉低---交换数据

时钟线拉高--稳定状态-读取数据

🍇🍇SPI 

高速同步全双工,总线上包括四条逻辑线,

MISO(主机输入从机输出)--MOSI(主机输出从机输出),

SCLK(时钟线),CS(片选线),内部通过移位寄存器进行数据进出,且有数据缓冲区来控制读写同时进行,每次发送一个字节的数据,通过拉低片选线来控制从机,SPI根据时钟极性和时钟相位通常有4种工作模式。

🍈ADC

模数转换器,ADC的作用就是将连续变化的模拟信号转换为离散的数字信号,

12位ADC是一个逐次逼近型模数转换器。

它有多达19个多路通道,允许它测量来自16个外部源和3个内部源(温度传感、内部参考电压、外部电池)的信号。

各种通道的A/D转换可以在单次、连续、扫描或间断的模式下进行。

ADC的结果被存储在一个向左对齐或向右对齐的16位数据寄存器中。

ADC的精度计算:参考电压/精度

🍓DMA

直接存储器访问,

DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。。DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节。给CPU节省资源,使CPU的工作效率提高

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com