嵌入式八股 - C++ 三
21、final和override
C++中final 和 override 关键字用于增强继承和虚函数重写的安全性和可读。也就是说,override 用于派生类,意在确认重写;而 final 用于基类(或类本身),意在禁止重写(或继承)。
1)override
override 关键字用在派生类的成员函数声明末尾,作用是显式地告诉编译器函数是要重写基类的虚函数。加上 override 后,编译器会强制进行检查。如果派生类的函数没有精确匹配基类的任何一个虚函数,编译器会立即报错,从而将错误从运行时提前到编译时,极大地降低了调试成本。
class Base {
public:
virtual void funcA(int x);
virtual void funcB() const;
};
class Derived : public Base {
public:
// 错误!参数类型不匹配,编译器会报错
void funcA(double x) override { }
// 正确!签名完全匹配
void funcB() const override { }
};
2)final
final 关键字有两种用法
- 修饰虚函数
当 final 用于修饰一个虚函数时,表示这个虚函数是它所在类的最终版本,任何后续的派生类都不允许再重写这个函数。
- 修饰类
当 final 用于修饰一个类时,表示这个类不能被继承。任何尝试继承这个类的行为都会导致编译错误。
class Stu final {
// ...
};
// 错误!编译器会报错,因为 Stu 是 final 类
class LitStu: public Stu { };
22、nullptr和NULL的区别
在现代 C++开发中,nullptr 和 NULL 虽然都用来表示空指针,但它们在类型安全性和使用场景上有本质的区别。nullptr 是专门引入的新关键字,用于初始化空类型指针,而 NULL 往往本质上是整数 0。在C++函数重载等场景下使用NULL容易发生歧义(NULL还是0?)
23、C++类中默认的成员函数有哪些?
在 C++ 中,如果你没有显式地定义某些成员函数,编译器会在特定条件下自动为你生成这些函数。这些被称为默认成员函数。
在新创建的一个C++类中,默认成员函数有:
- 默认构造函数
- 拷贝构造函数
- 析构函数
- 重载赋值运算符函数
- 重载取址运算符函数
- 重载取址运算符const函数
- (C++11引入移动构造函数和重载移动赋值操作符函数)
class ClassName{
public:
ClassName(); // 默认构造函数,对象实例化且无参数时
~ClassName(); // 析构函数,对象生命周期结束时
ClassName(const ClassName&); // 拷贝构造函数,用同类对象初始化新对象时
operator=(const ClassName&); // 拷贝赋值运算符,将一个已存在对象赋值给另一个已存在对象时
operator&(); // 重载取址运算符函数,对非 const 对象使用 & 时
operator&() const; // 重载取址运算符const函数,对 const 对象使用 & 时
ClassName(ClassName&&); // 移动构造函数,用右值初始化新对象时
operator=(ClassName&&); // 重载移动赋值操作符函数,将右值赋值给已存在对象时
};
24、默认函数控制
在 C++11 及以后的版本中,=default 和 =delete 是两个用于精确控制类默认行为的关键字。
=default指定显示默认函数(只能是默认构造函数、拷贝构造函数、析构函数、重载赋值运算符函数、移动构造函数和重载移动赋值操作符函数六种函数)=delete指定禁止使用函数,可以是默认函数和自定义函数
25、拷贝构造函数和赋值运算符调用时机
特性 | 拷贝构造函数 | 赋值运算符 |
触发时机 | 对象创建时 | 对象已存在,进行赋值时 |
对象状态 | 目标对象是全新的,内存未初始化 | 目标对象已初始化,内存中可能有旧数据 |
内存操作 | 直接分配新内存并写入数据 | 先释放旧内存(如有),再分配新内存 |
1)拷贝构造函数的调用时机
只要涉及用旧对象去初始化一个新对象,就会调用拷贝构造函数。
1、显式初始化当你用一个已存在的对象去初始化另一个新对象时
MyClass a; MyClass b= a;// 调用拷贝构造(虽然写了=,但本质是初始化) MyClassc(a);// 调用拷贝构造
2、函数参数按值传递当函数的参数是对象(而非引用或指针)时,实参会拷贝一份给形参
voidfunc(MyClass param);// 形参 param 是新对象 MyClass a; func(a);// 调用拷贝构造,将 a 拷贝给 param
3、函数按值返回对象当函数返回类型是对象时,通常会生成一个临时对象作为返回值
MyClassfunc(){
MyClass temp;
return temp;
// 理论上调用拷贝构造(可能被优化)
}
4、容器插入元素当你把对象放入 std::vector 等容器时,容器内部会创建对象的副本
std::vector<MyClass> vec; MyClass a; vec.push_back(a);// 调用拷贝构造,将 a 的副本存入容器
2)赋值运算符的调用时机
只有当两个对象都已经存在,且进行赋值操作时,才会调用赋值运算符。
1、已存在对象的赋值
MyClass a, b; b= a;// b 已经存在,调用赋值运算符,将 a 的值赋给 b
2、链式赋值
MyClass a, b, c; a= b= c;// 先 b=c,再 a=b,均调用赋值运算符
26、C++三大特性和介绍
C++ 的三大核心特性是 封装、继承 和 多态,它们构成了面向对象编程的基础。封装通过隐藏实现细节提高了安全性和模块化。继承实现了代码复用和逻辑层次结构。多态则通过统一接口实现了行为多样化,增强了代码的灵活性。
封装:封装是将数据和操作数据的函数绑定在一起,并隐藏实现细节,仅暴露必要的接口。通过封装,可以限制外部对类内部数据的直接访问,确保数据的完整性和安全性。
继承 :继承是实现代码复用的重要手段,它允许我们基于一个已有的类(称为基类或父类)来创建一个新的类(称为派生类或子类)。
多态 :通过多态可以用统一的接口来操作不同类型的对象,而程序会在运行时根据对象的实际类型来决定调用哪个具体的方法。
27、三种继承方式介绍
在 C++ 中分为public、protected 和 private 这三种继承方式,本质上决定了基类的成员在派生类中会变成什么访问权限。
基类成员权限 | public 继承 | protected 继承 | private 继承 |
public | 保持 public | 变为 protected | 变为 private |
protected | 保持 protected | 保持 protected | 变为 private |
private | 不可访问 | 不可访问 | 不可访问 |
28、函数对象以及跟普通函数的区别?
在C++中,函数对象是通过重载operator()的类实例,使其可以像普通函数一样被调用,拥有函数行为和状态,而普通函数是一段代码。
区别:
- 状态保持:函数对象可以通过成员变量保存状态,每次调用都可以基于之前的状态,而普通函数无法在调用之间保存数据
- 调用方式:函数对象需要先创建实例,然后用实例调用。函数对象需要先创建实例,然后用实例调用,而普通函数可以直接被调用
- 灵活性:函数对象灵活性高,可以通过构造函数初始化不同的行为策略,而普通函数逻辑固定,难以参数化,灵活性低
29、自动类型推导auto
在现代 C++中,auto 关键字允许编译器在编译期根据初始化表达式自动推导变量类型,从而减少冗长的类型声明,提高代码可读性与可维护性。
auto推到原则:
写法 | 规则描述 | 示例 | 结果类型 |
| 按值推导:忽略引用和顶层 |
|
|
| 按引用推导:保留 |
|
|
| 按指针推导:推导为指针类型 |
|
|
注意问题:
- 必须初始化,依赖初始值来推导类型,因此声明时必须赋初值,否则编译器会报错
- 可能丢失
const或引用属性,如果你希望变量是引用,必须显式加上。否则,会进行值拷贝。
vector<int> v = {1, 2, 3};
auto val = v; // val 是 int 的拷贝,修改 val 不影响 v
auto& ref = v; // ref 是 v 的引用,修改 ref 会改变 v
- 降低代码可读性
auto使用限制:
- 不能作为函数参数使用,但是允许用于 Lambda 的参数,实现泛型编程。
- 不能用于类的非静态成员变量的初始化
- 不能使用auto关键字定义数组
- 无法使用auto推导出模板参数
30、lambda表达式
Lambda表达式是一种简洁的编程语言特性,其允许以匿名函数的形式快速定义和实现功能。其语法结构如下:
[捕获列表] (参数列表) -> 返回值类型 { 函数体 }
捕获列表说明:
捕获方式 | 语法 | 行为描述 | 注意事项 |
值捕获 |
| 拷贝一份 | 默认是 |
引用捕获 |
| 直接引用外部的 | 修改 Lambda 内的 |
隐式值捕获 |
| 自动按值捕获所有用到的外部变量 | 方便,但要注意大对象拷贝的性能开销 |
隐式引用捕获 |
| 自动按引用捕获所有用到的外部变量 | 最常用,但需小心变量生命周期问题 |
混合捕获 |
| 大部分按值, | 灵活控制 |
捕获 |
| 捕获当前对象的指针 | 用于访问类的成员变量和方法 |
涉及嵌入式全方面知识。根据个人学习以及面试所得,并且加上自己见解、理解记忆方法。 大致内容:C、C++、ARM、QT、Linux驱动、FreeRTOS、Linux应用编程、数据结构、操作系统、计算机网络、算法以及其他嵌入式相关内容。 优势:专门适用于嵌入式软件岗位的面试高频内容总结,短时间内快速掌握重要面试内容
查看10道真题和解析