嵌入式软件工程师面试题——2025校招社招通用(C/C++)(四十九)

说明:

  • 面试群,群号: 228447240
  • 面试题来源于网络书籍,公司题目以及博主原创或修改(题目大部分来源于各种公司);
  • 文中很多题目,或许大家直接编译器写完,1分钟就出结果了。但在这里博主希望每一个题目,大家都要经过认真思考,答案不重要,重要的是通过题目理解所考知识点,好应对题目更多的变化;
  • 博主与大家一起学习,一起刷题,共同进步;
  • 写文不易,麻烦给个三连!!!

前面1-15已经是C/C++,但是由于前面写的比较混乱,把八股文和题目混在了一起,所以从这一篇开始重新整理重新写,前面1-15也就可以选看了,希望多多支持!

1.C++ 11有哪些新特性

答案:

  • nullptr替代 NULL
  • 引入了 auto decltype 这两个关键字实现了类型推导
  • 基于范围的 for 循环for(auto& i : res){}
  • 类和结构体的中初始化列表
  • Lambda 表达式(匿名函数)
  • std::forward_list(单向链表)
  • 右值引用和move语义

2.几个this指针的易混问题

答案:

1)this 指针是什么时候创建的?
this 在成员函数的开始执行前构造,在成员的执行结束后清除。
2)this 指针存放在何处?堆、栈、全局变量,还是其他?
this 指针会因编译器不同而有不同的放置位置。可能是栈,也可能是寄存器,甚至全局变量。在汇编级别里面,一个值只会以3 种形式出现:立即数、寄存器值和内存变量值。不是存放在寄存器就是存放在内存中,它们并不是和高级语言变量对应的。
3)this 指针是如何传递类中的函数的?绑定?还是在函数参数的首参数就是 this 指针? 那么, this 指针又是如何找到 类实例后函数的
大多数编译器通过 ecx (计数寄存器)寄存器传递 this 指针。事实上,这也是一个潜规则。一般来说,不同编译器都会遵从一致的传参规则,否则不同编译器产生的obj 就无法匹配了。
call 之前,编译器会把对应的对象地址放到 eax 中。 this 是通过函数参数的首参来传递的。 this 指针在调用之前生成,至于“ 类实例后函数 ,没有这个说法。类在实例化时,只分配类中的变量空间,并没有为函数分配空间。自从类的函数定义完成后,它就在那儿,不会跑的。
4)this 指针是如何访问类中的变量的?
如果不是类,而是结构体的话,那么,如何通过结构指针来访问结构中的变量呢?如果你明白这一点的话,就很容易理解这个问题了。
C++ 中,类和结构是只有一个区别的:类的成员默认是 private ,而结构是 public。this 是类的指针,如果换成结构体,那 this 就是结构的指针了。
5)我们只有获得一个对象后,才能通过对象使用 this 指针。如果我们知道一个对象 this 指针的位置,可以直接使用吗?
this 指针只有在成员函数中才有定义。 因此,你获得一个对象后,也不能通过对象使用 this 指针。所以,我们无法知道一个对象的this 指针的位置(只有在成员函数里才有 this 指针的位置)。当然,在成员函数里,你是可以知道this 指针的位置的(可以通过 &this 获得),也可以直接使用它。
6)每个类编译后,是否创建一个类中函数表保存函数指针,以便用来调用函数?
普通的类函数(不论是成员函数,还是静态函数)都不会创建一个函数表来保存函数指针。只有虚函数才会被放到函数表中。但是,即使是虚函数,如果编译期就能明确知道调用的是哪个函数,编译器就不会通过函数表中的指针来间接调用,而是会直接调用该函数。正是由于this 指针的存在,用来指向不同的对象,从而确保不同对象之间调用相同的函数可以互不干扰。

3.在成员函数中调用delete this会出现什么问题?对象还可以使用吗?

答案:
在类对象的内存空间中,只有数据成员和虚函数表指针,并不包含代码内容,类的成员函数单独放
在代码段中。在调用成员函数时,隐含传递一个 this 指针,让成员函数知道当前是哪个对象在调用它。当调用delete this 时,类对象的内存空间被释放。在 delete this 之后进行的其他任何函数调用,只要不涉及到this 指针的内容,都能够正常运行。一旦涉及到 this 指针,如操作数据成员,调用虚函数等,就会出现不可预期的问题。

4.你知道空类的大小是多少吗

答案:
1) C++ 空类的大小不为 0 ,不同编译器设置不一样, vs 设置为 1
2) C++ 标准指出,不允许一个对象(当然包括类对象)的大小为 0 ,不同的对象不能具有相同的地址;
3) 带有虚函数的 C++ 类大小不为 1 ,因为每一个对象会有一个 vptr 指向虚函数表,具体大小根据指针大小确定;
4) C++ 中要求对于类的每个实例都必须有独一无二的地址 , 那么编译器自动为空类分配一个字节大小,这样便保证了每个实例均有独一无二的内存地址。

5.autodecltypedecltype(auto)的用法

答案:
1 auto
C++11 新标准引入了 auto 类型说明符,用它就能让编译器替我们去分析表达式所属的类型。和原来那些只对应某种特定的类型说明符( 例如 int) 不同
auto 让编译器通过初始值来进行类型推演。从而获得定义变量的类型,所以说 auto 定义的变量必须有 初始值。
//普通;类型
int a = 1, b = 3;
auto c = a + b;// c为int型
//const类型
const int i = 5;
auto j = i; // 变量i是顶层const, 会被忽略, 所以j的类型是int
auto k = &i; // 变量i是一个常量, 对常量取地址是一种底层const, 所以b的类型是const int*
const auto l = i; //如果希望推断出的类型是顶层const的, 那么就需要在auto前面加上cosnt
//引用和指针类型
int x = 2;
int& y = x;
auto z = y; //z是int型不是int& 型
auto& p1 = y; //p1是int&型
auto p2 = &x; //p2是指针类型int*
2 decltype
有的时候我们还会遇到这种情况, 我们希望从表达式中推断出要定义变量的类型,但却不想用表达式的 值去初始化变量。 还有可能是函数的返回类型为某表达式的值类型。在这些时候 auto 显得就无力了,所以C++11 又引入了第二种类型说明符 decltype 它的作用是选择并返回操作数的数据类型。在此过程中, 编译器只是分析表达式并得到它的类型,却不进行实际的计算表达式的值。
int func() {return 0};
//普通类型
decltype(func()) sum = 5; // sum的类型是函数func()的返回值的类型int, 但是这时不会实际调用
函数func()

int a = 0;
decltype(a) b = 4; // a的类型是int, 所以b的类型也是int
//不论是顶层const还是底层const, decltype都会保留 
const int c = 3;
decltype(c) d = c; // d的类型和c是一样的, 都是顶层const
int e = 4;
const int* f = &e; // f是底层const
decltype(f) g = f; // g也是底层const
//引用与指针类型
//1. 如果表达式是引用类型, 那么decltype的类型也是引用
const int i = 3, &j = i;
decltype(j) k = 5; // k的类型是 const int&
//2. 如果表达式是引用类型, 但是想要得到这个引用所指向的类型, 需要修改表达式:
int i = 3, &r = i;
decltype(r + 0) t = 5; // 此时是int类型
//3. 对指针的解引用操作返回的是引用类型
int i = 3, j = 6, *p = &i;
decltype(*p) c = j; // c是int&类型, c和j绑定在一起
//4. 如果一个表达式的类型不是引用, 但是我们需要推断出引用, 那么可以加上一对括号, 就变成了引用
类型了
int i = 3;
decltype((i)) j = i; // 此时j的类型是int&类型, j和i绑定在了一起
3 decltype(auto)
decltype(auto) C++14 新增的类型指示符,可以用来声明变量以及指示函数返回类型。在使用时,会将“=”号左边的表达式替换掉 auto ,再根据 decltype 的语法规则来确定类型。
int e = 4;
const int* f = &e; // f是底层const
decltype(auto) j = f;//j的类型是const int* 并且指向的是e

6.C++NULLnullptr区别

答案:

NULL 来自 C 语言,一般由宏定义实现,而 nullptr 则是 C++11 的新增关键字。 C 语言中, NULL 被定义 (void*)0, 而在 C++ 语言中, NULL 则被定义为整数 0
C++ 中指针必须有明确的类型定义。但是将 NULL 定义为 0 带来的另一个问题是无法与整数的 0 区分。 因为C++ 中允许有函数重载。
void fun(char* p) {
 cout << "char*" << endl;
}
void fun(int p) {
 cout << "int" << endl;
}
int main()
{
 fun(NULL);
 return 0;
}
//输出结果:int

那么 在传入 NULL 参数时,会把 NULL 当做整数 0 来看,如果我们想调用参数是指针的函数,该怎么办 ? nullptr C++11 被引入用于解决这一问题, nullptr 可以明确区分整型和指针类型,能够根据环境自 动转换成相应的指针类型,但不会被转换为任何整型,所以不会造成参数传递错误

7.智能指针的原理、常用的智能指针

答案:

原理:  智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄 漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。
常用的智能指针
(1) shared_ptr
实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1 ,每当减少一个智能指针指向对象时,引用计数会减1 ,当计数为 0 的时候会自动的释放动态分配的资源。
(2) unique_ptr
unique_ptr采用的是独 享所有权语义,一个非空的 unique_ptr 总是拥有它所指向的资源。转移一个
unique_ptr 将会把所有权全部从源指针转移给目标指针,源指针被置空;所以 unique_ptr 不支持普通的拷贝和赋值操作,不能用在STL 标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);如果你拷贝一个unique_ptr ,那么拷贝结束后,这两个 unique_ptr 都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。
(3) weak_ptr
weak_ptr :弱引用。 引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak_ptr 打破环形引用。 weak_ptr 是一个弱引用,它是为了配合 shared_ptr 而引入的一种智能指针,它指向一个由shared_ptr 管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。如果一块内存被shared_ptr weak_ptr 同时引用,当所有 shared_ptr 析构了之后,不管还有没有weak_ptr 引用该内存,内存也会被释放。所以 weak_ptr 不保证它指向的内存一定是有效的,在使用之前使用函数lock() 检查 weak_ptr 是否为空指针。
(4) auto_ptr
主要是为了解决 有异常抛出时发生内存泄漏 的问题 。因为发生异常而无法正常释放内存。
auto_ptr 有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而 unique_ptr 则无拷贝语义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move() 进行转移。
auto_ptr 不支持拷贝和赋值操作,不能用在 STL 标准容器中。 STL 容器中的元素经常要支持拷贝、赋值操作,在这过程中auto_ptr 会传递所有权,所以不能在 STL 中使用。

8.说一说你了解的关于lambda函数的全部知识

答案:

1) 利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象;

2) 每当你定义一个 lambda 表达式后,编译器会自动生成一个匿名类(这个类当然重载了 () 运算符),我们称为闭包类型(closure type )。那么在运行时,这个 lambda 表达式就会返回一个匿名的闭包实例,其实是一个右值。所以,我们上面的lambda 表达式的结果就是一个个闭包。闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内的变量,前面的方括号就是用来定义捕捉模式以及变量,我们又将其称为lambda 捕捉块。
3) lambda 表达式的语法定义如下:
4) lambda 必须使用尾置返回来指定返回类型,可以忽略参数列表和返回值,但必须永远包含捕获列表和函数体;

9.智能指针出现循环引用怎么解决

答案:

弱指针用于专门解决 shared_ptr 循环引用的问题, weak_ptr 不会修改引用计数,即其存在与否并不影响对象的引用计数器。
循环引用就是:两个对象互相使用一个shared_ptr 成员变量指向对方。弱引用并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。

10.什么是STL

答案:

C++ STL从广义来讲包括了三类:算法,容器和迭代器。

  • 算法包括排序,复制等常用算法,以及不同容器特定的算法。
  • 容器就是数据的存放形式,包括序列式容器和关联式容器,序列式容器就是listvector等,关联式容器就是setmap等。
  • 迭代器就是在不暴露容器内部结构的情况下对容器的遍历。

11.使用智能指针管理内存资源,RAII是怎么回事?

答案:

1) RAII 全称是 “Resource Acquisition is Initialization” ,直译过来是 资源获取即初始化 ,也就是说在构造函数中申请分配资源,在析构函数中释放资源。
因为 C++ 的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII 的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。
2) 智能指针( std::shared_ptr std::unique_ptr )即 RAII 最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete 造成的内存泄漏。
毫不夸张的来讲,有了智能指针,代码中几乎不需要再出现 delete 了。

12.迭代器:++itit++哪个好,为什么

答案:
1) 前置返回一个引用,后置返回一个对象
// ++i实现代码为:
int& operator++()
{
 *this += 1;
 return *this;
}
2) 前置不会产生临时对象,后置必须产生临时对象,临时对象会导致效率降低
//i++实现代码为: 
int operator++(int) 
{
int temp = *this; 
 ++*this; 
 return temp; 
}

你可能感兴趣的:(嵌入式面试题,c语言,c++,面试,面试题,嵌入式)