嵌入式超高频八股:C C++语法部分
面试了这么多场,个人感觉面试中对语法部分的考察其实占比非常少,而且基本问来问去就这么几个问题,所以本着用20%的力气,产生80%的效益的目的,只把问到的问题记录下来,这些一定一定要都搞懂!!被问到的几率非常大!
C
被const修饰的变量可以改变么
如果是局部的const(栈),可以通过指针的方式改变,如果是全局的会报错(rodata)
//可以运行 但不要这样
int main{
int a = 1;
int *p = &a;
*p = 2;
}
-----------------------
// 错
int a = 1;
int main{
int *p = &a;
*p = 2;
}
static 和 const
static
- 局部变量:放到data中,初始化一次不被释放
- 全局变量:当前文件可见
- 函数:当前文件可见
- 成员函数:全类共用,只能访问静态变量
- 成员变量:全类共用
const
- 常量:定义时初始化以后不能更改
- 形参:
- 修饰类成员函数:该函数对成员变量只能进行只读操作
volatile
- 作用:
- 防止编译器优化:
- 通常,编译器会进行优化,将变量值存储在寄存器中,以减少内存访问次数,提高程序运行效率。但是,对于
volatile
变量,编译器会放弃这种优化,每次访问都直接从内存读取或写入。
- 通常,编译器会进行优化,将变量值存储在寄存器中,以减少内存访问次数,提高程序运行效率。但是,对于
- 确保可见性:
- 在多线程编程或硬件编程中,某些变量可能由多个线程或外部硬件设备修改。使用
volatile
关键字可以确保这些变量的最新值在每次访问时都能被正确读取。
- 在多线程编程或硬件编程中,某些变量可能由多个线程或外部硬件设备修改。使用
- 使用场景
- 硬件寄存器
- 多线程共享变量:每次都获得最新的值
volatile const
使用场景:
- 只读寄存器,放置被优化并且从寄存器里读值,寄存器不准改写
野指针和悬空指针
野指针是定义出来没有初始化的指针,其内容是随机无意义的。 悬空指针是指向已经被释放内存的地址。
危害:访问到无效的内存区域:可能编译失败、执行不正确、崩溃、也有小概率执行正确。
避免方法:用智能指针、Santilazer、静态分析工具
sizeof 和strlen
复杂度
sizeof是编译时计算,所以是O(1) strlen是运行时函数,所以是O(n)
区别
strlen 运行时计算、只能看字符串、不算'\0' sizeof 编译时计算、都可以、算'\0'
C++
lambda表达式
\[捕获列表\](参数列表) {执行体}
[=] 全部值捕获 [&] 全部引用捕获 [=, &b] b引用捕获其余都值捕获 [&, b] b值捕获其余都引用捕获
拷贝构造和拷贝赋值
假设A中有new的 _data
以及长度 _length
class A;
//拷贝构造
A(const A& other):_data(new int[other._length]) {
//拷贝一些成员
copy(other._data, other._data + other._length , _data);
...
}
//拷贝赋值
A& operator=(const A& other) {
//注意要先把自己里的释放掉
delete[] _data;
_length = other._length;
_data = new int[other._length];
copy(other._data, other._data + other._length , _data);
...
}
移动构造和移动赋值
class A;
// 移动构造
A(A&& other):_data(nullptr), _length(0) {
_data = other._data;
_length = other._length;
//注意不是释放掉而是赋一个空
other._data = nullptr;
...
}
//移动赋值
A& operator=(A&& other) {
delete[] _data;
_data = other._data;
_length = other._length;
other._data = nullptr;
other._length = 0;
...
}
从上面两个可以看出来:
- 赋值重载,都需要先把new的delete掉后,将other的直接赋值过来(拷贝需要重新new,移动可以直接赋值)
- 移动最后都需要将其设置成nullptr,防止重复释放
- 拷贝需要用到拷贝函数,进行值拷贝
为什么析构函数可以为virtual型,而构造函数则不能?
虚函数的主要意义在于被派生类继承从而产生多态。派生类的构造函数中, 编译器会加入构造基类的代码,如果基类的构造函数用到参数,则派生类在其构造函 数的初始化列表中必须为基类给出参数,就是这个原因。虚函数的意思就是开启动态 绑定,程序会根据对象的动态类型来选择要调用的方法。然而在构造函数运行的时 候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不 能动态绑定。(动态绑定是根据对象的动态类型而不是函数名,在调用构造函数之 前,这个对象根本就不存在,它怎么动态绑定?)
智能指针
C++提供了三种主要的智能指针:std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
。它们的主要作用是管理动态内存的生命周期,避免手动调用 delete 引发的内存泄漏或未定义行为。以下是对它们的详细介绍:
std::unique_ptr
特点:
- 独占所有权:一个 std::unique_ptr 对象独占它所管理资源的所有权,不能被复制(拷贝语义被禁用)。
- 轻量级:由于没有引用计数,性能开销最小。
- 当
std::unique_ptr
超出作用域时,会自动销毁其管理的资源。
常见用法:
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass created\n"; }
~MyClass() { std::cout << "MyClass destroyed\n"; }
};
int main() {
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
// std::unique_ptr<MyClass> ptr2 = ptr1; // 错误!不能复制
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 转移所有权
return 0;
}
适用场景:
- 独占资源的场景。
- 不需要多个智能指针共享同一资源。
std::shared_ptr
特点:
- 共享所有权:多个 std::shared_ptr 对象可以共享同一资源的所有权。
- 使用引用计数管理资源:当最后一个 std::shared_ptr 销毁时,资源会被释放。
常见用法:
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass created\n"; }
~MyClass() { std::cout << "MyClass destroyed\n"; }
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加
std::cout << "Use count: " << ptr1.use_count() << "\n"; // 输出 2
return 0;
}
适用场景:
- 需要多个对象共享同一资源,且管理其生命周期的场景。
底层实现原理:主要就是其中有一个计数器,用来记录有多少个,每次构造都会+1,每次析构就会-1,直到计数器为0才会真正析构。
std::weak_ptr
如果两个
shared_ptr
相互引用会导致两个都无法释放造成泄露,所以有了weak_ptr
特点:
std::weak_ptr
不会影响资源的引用计数。通常与 std::shared_ptr 配合使用,用于解决循环引用问题。- 需要通过
lock()
方法将其转化为std::shared_ptr
来访问资源。
常见用法:
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass created\n"; }
~MyClass() { std::cout << "MyClass destroyed\n"; }
};
int main() {
std::shared_ptr<MyClass> shared = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weak = shared; // 弱引用
if (auto ptr = weak.lock()) { // 转换为 shared_ptr
std::cout << "Resource is still alive\n";
} else {
std::cout << "Resource has been released\n";
}
return 0;
}
适用场景:
- 解决
std::shared_ptr
的循环引用问题。
总结与对比:
所有权 | 独占 | 共享 | 不拥有资源(弱引用) |
是否使用引用计数 | 否 | 是 | 否 |
典型用途 | 独占资源管理 | 多个共享资源管理 | 配合 std::shared_ptr 使用 |
是否可复制 | 否 | 是 | 是 |
shared_ptr<>
- 创建方式
shared_ptr<int> (int*)
接管指针make_shared<int>(32)
shared_ptr<int> sp(new int(32))
weak_ptr<>
.reset()
置空.use_count()
返回共享对象shared_ptr的数量.lock()
返回一个shared_ptr
weak_ptr与shared_ptr的转换
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);
shared_ptr<int> sp = wp.lock();
define引起的问题
struct s* p1,p2;
*
是紧跟变量的,所以p1是指针struct s*
,p2是s的结构体struct s
没注意到这个类型的不同,使用很容易出问题
队列底层实现
std::deque
(双端队列):这是std::queue
默认的底层实现。std::deque
提供双端插入和删除的能力,允许在队列的头部和尾部高效地添加和移除元素。std::list
:链表实现也可以作为底层容器,但在实际使用中不如std::deque
常见,因为std::deque
提供了类似std::list
的效率,但具有更多的特性。std::vector
:虽然可以使用std::vector
作为底层容器,但它并不适合作为std::queue
的底层,因为在std::vector
的头部插入或删除元素效率较低,涉及到元素的移动。
如何计算class大小
#pragma pack(4)
class SStar{
long number;
int *pData;
union BUF{
short chn;
char buffer[11];
int number;
}buf;
enum{Mon,Tue,Wed, Thr, Fri}day;
typedef char(f)(void*);
unsigned char a:4;
unsigned char b:4;
}sstar;
36
- long 8
- int * 8
- BUF 11+1 (4字节对齐)
- enum 4 (默认int)
- typedef 0
- a+b 4
bash三剑客
sed
sed -e<script> -f<脚本文件> file
//第三行插入一行 newline
sed -e 3a\newline test
sed '3a newline' test
//删除2-4行
sed '2,4d' test
//替换2-4行
sed '2,4c xxx' test
awk
# 以:为分割,当第三个部分小于10,打印该行的第一个部分\t第三个部分
awk 'BEGIN {FS=":"} $3<10 printf{$1 "\t" $3}'