嵌入式超高频八股: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

  1. 作用:
  • 防止编译器优化
    • 通常,编译器会进行优化,将变量值存储在寄存器中,以减少内存访问次数,提高程序运行效率。但是,对于volatile变量,编译器会放弃这种优化,每次访问都直接从内存读取或写入
  • 确保可见性:
    • 在多线程编程或硬件编程中,某些变量可能由多个线程或外部硬件设备修改。使用volatile关键字可以确保这些变量的最新值在每次访问时都能被正确读取。
  1. 使用场景
  • 硬件寄存器
  • 多线程共享变量:每次都获得最新的值

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_ptrstd::shared_ptrstd::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::unique_ptrstd::shared_ptrstd::weak_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}'

grep

#牛客激励计划#
全部评论

相关推荐

点赞 评论 收藏
分享
评论
点赞
4
分享
牛客网
牛客企业服务