嵌入式软件常用面试题汇总之 C/C++ 语言相关(1)

C/C++之关键字相关的面试题汇总

对校招来说这部分考的非常频繁,应届生务必要熟悉!!

1.static关键字的作用

C语言中static的作用:

  • 在函数内部声明的变量,加上 static 关键字后,该变量的生命周期会延长至整个程序的运行期间,但是其作用域仍然是限制在声明它的函数内部,即使函数执行完毕,该变量仍然存在并保持其值,直到程序结束。这样的变量通常被称为“静态局部变量”
  • 在全局变量前加上 static 关键字,会将该全局变量的作用域限制在当前文件中,使得该变量对其他文件是不可见的,即便是extern外部声明也不可以。这样做的目的通常是为了实现信息隐藏,避免命名冲突以及减少全局变量对程序的影响范围。
  • static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。Static修饰的局部变量存放在全局数据区的静态变量区。初始化的时候自动初始化为0;

在C++中static关键字除了具有C语言中的作用还有在类中的使用:在类中,static可以用来修饰静态数据成员和静态成员方法

  • 静态数据成员
  • 静态数据成员可以实现多个对象之间的数据共享,它是类的所有对象的共享成员,它在内存中只占一份空间,如果改变它的值,则各对象中这个数据成员的值都被改变。
  • 静态数据成员是在程序开始运行时被分配空间,到程序结束之后才释放,只要类中指定了静态数据成员,即使不定义对象,也会为静态数据成员分配空间。
  • 静态数据成员可以被初始化,但是只能在类体外进行初始化,若为对静态数据成员赋初值,则编译器会自动为其初始化为0
  • 静态数据成员既可以通过对象名引用,也可以通过类名引用。
  • 静态成员函数
  • 静态成员函数和静态数据成员一样,他们都属于类的静态成员,而不是对象成员。
  • 非静态成员函数有this指针,而静态成员函数没有this指针。
  • 静态成员函数主要用来方位静态数据成员而不能访问非静态成员。

2.extern关键字的作用

用于声明一个全局变量或函数,表示该变量或函数是在其他文件中定义的。

  • 声明外部变量:在一个文件中使用 extern 关键字来声明一个在另一个文件中定义的全局变量。这样,编译器会知道该变量存在于其他文件中,从而避免重复定义。
  • 声明外部函数:类似地,使用 extern 关键字可以声明一个在其他文件中定义的函数,这样编译器就能知道该函数的存在。
  • 解决多文件之间的重复定义问题:当多个文件中存在同名的全局变量或函数时,使用 extern 可以避免重复定义的错误。

3.const关键字的作用

const关键字修饰变量名的时候,把这个变量变为是一个只读变量,这里的只读不是全局数据区。它所修饰的变量原来是在哪个区域,const修饰之后它还在那个区域。比如修饰一个局部变量const int num = 5;那么这个局部变量还是存在栈空间上。它本身对应的内存空间还是可以变的。但是不可以通过num去修改它。如果定义一个指针指向num的地址。则可以改变num的值。其次const可以修饰形参(保护形参不被修改),在这一点上,站在实现者的角度,const可以防止函数对实参修改带来的问题。站在调用者的角度来说,它对于实参只能访问,不能修改。所以、合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

  • C语言中const修饰的是只读变量,不能通过变量名来修改,只能通过其他方式修改(通过内存指针修改);
  • C++修饰的是常量,存放在符号表中,不能修改;
  • C++修饰成员函数是常成员函数,函数内部只能访问数据,不能修改数据;
  • C++修饰成员变量是一个常量,需要通过构造函数后面的初始化列表来初始化。

虽然理解起const来相对比较容易理解,但是const不仅仅可以用来修辞基本类型,它还可以经常用来修辞一些构造类型和指针及其参合体,如数组、指针、指针数组、结构体数组、结构体指针数组一旦和这些复杂的类型结合起来还是有一定的迷惑性的,比如:

const int a = 10;
int const a = 10;
const int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const int *p;
int * const p;
const struct devices dev[5];
struct devices const * dev[5];

辨识技巧是将类型去掉,看const修饰谁,谁的值就不能被修改,即:

  • 去掉类型 int 变成“const a = 10”,a 拥有了铁布衫,a的值是不变的
  • 去掉类型 int 变成“const a = 10”,a 拥有了铁布衫,a的值是不变的,这两个的效果一样
  • 去掉类型 int 变成“const a[10]”, a[10]拥有了铁布衫,a数组里面的值是不变的
  • 去掉类型 int 变成“const *p”,*p拥有铁布衫,p所指的空间里面的值是不变的
  • 去掉类型 int * 变成 “const p”,指针变量p拥有了铁布衫,指针变量p的值不变,也就是说p不能再指向其他地址了,但是p所指向的空间的值可变
  • 去掉类型struct devices变成“const dev[5]”,dev[5]拥有了铁布衫,dev[5]数组的值不变
  • 这是一个devices结构体类型的指针数组,它拥有5个devices结构体类型的指针,每个指针指向一个devices结构体,const修饰*dev[5],去掉类型struct devices变成“const *dev[5]”,指针数组*dev[5]拥有了铁布衫,指针数组dev中每个元素指向的空间里的值不变。

4.volatile关键字的作用

volatile字面意思是易挥发、易变化的意思,它修饰的变量表示该变量的值很容易由于外部的因素而发生改变,强烈要求编译器要老老实实的在每次对变量进行访问时去内存里读取。这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

  • 并行设备的硬件寄存器(如:状态寄存器)
  • 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
  • 多线程应用中被几个任务共享的变量

一个参数既可以是const还可以是volatile吗?解释为什么。

答:是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

一个指针可以是volatile 吗?解释为什么。

答:是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

5.register关键字的作用

register 是一个关键字,用于向编译器提示某个变量可能会频繁地使用,因此希望将其存储在 CPU 寄存器中,以提高程序的性能。然而,在现代编译器中,register 关键字的使用已经不再像过去那样有明显的性能提升,因为现代编译器通常会根据上下文和优化策略自行决定变量的存储位置。因此,register 关键字已经变得不太常用,而且在 C99 标准中已经被弱化,C++17 中也已不推荐使用。

一般我们会把经常使用或者频繁访问的局部变量用register修饰,这里需要注意的是register修饰的局部变量名,不可以对其取地址,比如register int num = 5,不能&num 。因为&是从内存中取。而num在寄存器中。

还有很重要的两点就是对于全局变量和函数则不可以使用register修饰,而且register修饰的数据类型必须是CPU所接收的数据类型。比如有的单片机不支持浮点型,那么register就不能修饰浮点型变量。

6.sizeof与strlen的区别

strlen是一个函数,它测量的是字符串中除去 '\0' 以外的字符数;

而sizeof是一个关键字,含义可用作运算符,它测量的是对象或者类型所占的内存的字节数,包括 '\0' 在内,在编译时求值,不需要运行时计算;

对于strlen的使用没有什么限制。而当sizeof测量指针的长度,得到的是地址的长度;当sizeof测量数组的时候,得到的是整个数组的长度;当函数调用数组做为参数的时候,用sizeof测量得到的仍然是一个指针的长度。

7.#include <head.h> 与#include“head.h”的区别

#include <head.h>尖括号<>表明这个文件是一个工程或标准的头文件,在预处理查找过程中会首先检查系统预定义的目录,如果没有找到就报错

#include "head.h"双引号括起来的文件表明这是一个用户自定义的头文件,查找文件的时候会先在当前文件目录中查找,如果没有找到再去系统预定义的目录中查找,如果没有找到再报错

8.extern "C" 的作用

在C++中,extern "C" 是一个语言扩展,用于指示编译器使用C语言的链接约定来处理指定的代码块或函数。这在与C语言代码混合编译或链接时非常有用,因为C++和C的函数链接约定可能不同。

9.struct与union的区别

都是用于定义用户自定义的数据类型的关键字,有以下区别:

  • 内存布局:
  • struct 中的所有成员在内存中是按顺序排列的,每个成员都有自己的内存空间,结构体的大小等于所有成员大小之和。
  • union 中的所有成员共享同一块内存空间,所有成员的偏移量都是 0,而 union 的大小等于最大成员的大小。
  • 成员访问:
  • struct 中的成员可以同时被访问,因为它们各自占用独立的内存空间。
  • union 中的成员共享同一块内存空间,只能同时访问其中一个成员。
  • 存储方式:
  • struct 适合用于存储多个相关的数据,每个数据都有自己的内存空间。
  • union 适合用于存储多个不同类型但是共享同一块内存的数据,节省内存空间。
  • 内存使用:
  • struct 在内存中存储了所有成员,因此它的大小通常比较大。
  • union 中只有一个成员会被存储,因此它的大小通常等于最大成员的大小。
  • 使用场景:
  • struct 通常用于表示一组相关联的数据,例如表示一个学生的姓名、年龄、分数等。
  • union 通常用于节省内存空间,或者表示不同类型的数据共享同一块内存空间的情况,例如表示一个数据的不同表示形式。
  • 初始化:
  • struct 中的成员可以分别进行初始化。
  • union 中的成员共享同一块内存空间,初始化一个成员会影响到其他成员的值。

10.const与#define的特点与区别

  • define宏是在预处理阶段展开;const常量是编译运行阶段使用。
  • define宏没有类型,不做任何类型检查,仅仅是展开;const常量有具体的类型,在编译阶段会执行类型检查。
  • 存储方式不同,define宏仅仅是展开,有多少地方使用,不会分配内存;const常量会在内存中分配(可以是堆中也可以是栈中)。
  • const定义的常量在程序运行过程中只有一份拷贝,而define定义的常量在内存中有若干个拷贝。

11.#define和typedef的比较

  • 作用时间不同:#define是宏定义,发生在预处理阶段,只进行简单的字符串替换,不进行正确性检查;而typedef在编译阶段有效,有类型检查的功能;
  • 功能不同:#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等;typedef用来定义类型的别名,包括内部类型、自定义类型、机器无关的类型等;
  • 作用域不同:#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而typedef有自己的作用域。
  • 修饰指针类型时作用不同:typedef int * pint; const pint p; //p是一个指向常整型数的指针;#define int * Pint; const Pint P; //P是一个指向整型数的常指针;

12.#define 定义的宏函数和自定义函数的比较(优缺点)

#define 定义的宏函数:

  • 优点:
  • 性能: 宏函数在编译时会被简单地文本替换,而不需要像函数调用那样进行压栈、跳转等操作,因此在一些小型、简单的功能上,宏函数的性能可能会更好。
  • 灵活性: 宏函数可以接受任意数量的参数,并且可以包含任意的代码,因此它们更加灵活,可以实现一些复杂的功能,比如一行代码实现一个简单的循环。
  • 缺点:
  • 可读性差: 由于宏函数是简单的文本替换,使用宏函数的代码可能会变得难以阅读和理解,特别是当宏函数中包含较长或复杂的代码时。
  • 调试困难: 由于宏函数在编译时被替换为文本,因此在调试时无法直接跟踪宏函数的执行过程,使得调试困难。

自定义函数:

  • 优点:
  • 可读性好: 自定义函数使用传统的函数调用方式,函数名和参数清晰可见,易于理解和维护。
  • 调试方便: 自定义函数可以像普通的函数一样进行调试,可以直接跟踪函数的执行过程,方便调试。
  • 缺点:
  • 性能损失: 自定义函数调用需要进行压栈、跳转等操作,相比宏函数有一定的性能损失,特别是在一些简单的、频繁调用的地方。
  • 参数类型检查: 自定义函数可以对参数进行类型检查,当传递的参数类型不匹配时,编译器会给出警告或错误信息,而宏函数不具备这种能力,容易引发类型错误。

13.#define和枚举enum的区别

  • #define宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。
  • 一般在编译器里,可以调试枚举常量,但是不能调试宏常量。
  • 枚举可以一次定义大量相关的常量,而#define宏一次只能定义一个。

14.malloc与new的区别?Realloc等函数的作用?

  • malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符,它们都可用于申请动态内存和释放内存。
  • 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc /free. 因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete.注意new/delete不是库函数。
  • C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
  • new可以认为是malloc加构造函数的执行。New出来的指针是直接带类型信息的。而malloc返回的都是void指针;

realloc() 函数用于调整之前分配的内存块的大小。它的原型是 void* realloc(void* ptr, size_t size);,它将 ptr 指向的内存块的大小调整为 size 字节,并返回一个指向新内存块的指针。如果调整失败,realloc() 会返回 NULL,并保持原来的内存块不变。

总的来说,malloc() 和 new 都是用于动态分配内存的,但 new 更加类型安全并且具有自动初始化的特性,适用于 C++ 中动态对象的分配。realloc() 用于调整之前分配的内存块的大小。

15.return返回注意事项

不能返回局部变量指针或者数据名或者引用。

16.定义一个宏函数实现比较两个数的大小

#define定义宏函数的时候记着加括号,#defined Max(x,y) ((x>y) ? (x):(y))

#面试题##嵌入式##嵌入式软件##面试##笔试#

该专栏是我整理的一些嵌入式软件笔面试常见的题目,在有一定计算机基础上,再过一遍该专栏的内容,对应届生校招来说基本上笔面试就没什么问题了! 有任何疑问可随时与我联系,一起交流一起进步。

全部评论
觉得有帮助的话,帮楼主点个赞吧!!😁
点赞 回复
分享
发布于 04-05 18:11 广东
上次电话面问了我三个c语言的问题,就是你这1 3 4
点赞 回复
分享
发布于 04-20 22:12 安徽
联想
校招火热招聘中
官网直投

相关推荐

9 24 评论
分享
牛客网
牛客企业服务