嵌入式八股 - 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++ 中分为publicprotected 和 private 这三种继承方式,本质上决定了基类的成员在派生类中会变成什么访问权限

基类成员权限

public 继承

protected 继承

private 继承

public

保持 public

变为 protected

变为 private

protected

保持 protected

保持 protected

变为 private

private

不可访问

不可访问

不可访问

28、函数对象以及跟普通函数的区别?

在C++中,函数对象是通过重载operator()的类实例,使其可以像普通函数一样被调用,拥有函数行为和状态,而普通函数是一段代码。

区别:

  • 状态保持:函数对象可以通过成员变量保存状态,每次调用都可以基于之前的状态,而普通函数无法在调用之间保存数据
  • 调用方式:函数对象需要先创建实例,然后用实例调用。函数对象需要先创建实例,然后用实例调用,而普通函数可以直接被调用
  • 灵活性:函数对象灵活性高,可以通过构造函数初始化不同的行为策略,而普通函数逻辑固定,难以参数化,灵活性低

29、自动类型推导auto

在现代 C++中,auto 关键字允许编译器在编译期根据初始化表达式自动推导变量类型,从而减少冗长的类型声明,提高代码可读性与可维护性。

auto推到原则:

写法

规则描述

示例

结果类型

auto x = expr;

按值推导:忽略引用和顶层 const

const int ci = 1; auto x = ci;

int

auto& x = expr;

按引用推导:保留 const 和引用属性

const int ci = 1; auto& x = ci;

const int&

auto* x = expr;

按指针推导:推导为指针类型

int* p = &ci; auto* x = p;

int*

注意问题:

  • 必须初始化,依赖初始值来推导类型,因此声明时必须赋初值,否则编译器会报错
  • 可能丢失 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表达式是一种简洁的编程语言特性,其允许以匿名函数的形式快速定义和实现功能。其语法结构如下:

[捕获列表] (参数列表) -> 返回值类型 { 函数体 }

捕获列表说明:

捕获方式

语法

行为描述

注意事项

值捕获

[x]

拷贝一份x的副本进 Lambda

默认是const,修改副本需加mutable

引用捕获

[&x]

直接引用外部的x

修改 Lambda 内的x会改变外部变量

隐式值捕获

[=]

自动按值捕获所有用到的外部变量

方便,但要注意大对象拷贝的性能开销

隐式引用捕获

[&]

自动按引用捕获所有用到的外部变量

最常用,但需小心变量生命周期问题

混合捕获

[=, &x]

大部分按值,x按引用

灵活控制

捕获 this

[this]

捕获当前对象的指针

用于访问类的成员变量和方法

#嵌入式八股##嵌入式笔面经分享##嵌入式软件八股##嵌入式软件##嵌入式#
嵌入式面试八股汇总 文章被收录于专栏

涉及嵌入式全方面知识。根据个人学习以及面试所得,并且加上自己见解、理解记忆方法。 大致内容:C、C++、ARM、QT、Linux驱动、FreeRTOS、Linux应用编程、数据结构、操作系统、计算机网络、算法以及其他嵌入式相关内容。 优势:专门适用于嵌入式软件岗位的面试高频内容总结,短时间内快速掌握重要面试内容

全部评论
点赞 回复 分享
发布于 昨天 17:14 北京

相关推荐

评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务