# C++ 常见面试题
# 智能指针
一般来说,智能指针指的是 C++11 引入的三种智能指针,旨在解决指针管理的难题。
💔常见的指针管理困难
- 资源释放但是并未置空
- 野指针 (独立控制权)
野指针 auto p = new int;
delete p; // 释放后并未置空
int a = *p; // 内存泄漏!
- 指针悬挂 (多控制权)
指针悬挂 mark 1-6 auto p1 = new int;
auto p2 = p1;
auto p3 = p1;
delete p1;
p1 = nullptr;
int a = *p2; // Error!
- 忘记释放
如析构时没有手动释放资源 - 重复释放
auto p1 = new int;
delete p1;
delete p1; //Error!
针对几个问题,C++11 提出了 unique_ptr,shared_ptr,weak_ptr 来用于不同的场景。其中,unique_ptr 和 shared_ptr 只要规范使用,都可以杜绝忘记释放和重复释放的问题。
# unique_ptr
unique_ptr 主要针对野指针问题,他是独享控制权的。C++14 提供了 make_unique 的方法。
主要实现方式呢就是 unique_ptr 删除了默认的拷贝构造函数和拷贝赋值,只有默认构造,移动构造和移动赋值。
函数是可以返回 unique_ptr 的,这是由于 return 临时对象的原则是优先使用移动构造,再使用拷贝构造,如果俩都没有再报错。
unique_ptr<T> get_unique(){ | |
unique_ptr<T> u; | |
return u; | |
} | |
auto x = get_unique() // 触发移动构造 |
# shared_ptr
shared_ptr 主要针对指针悬挂问题,shared_ptr 的内部会维护一个强引用计数。当引用计数为 0 时自动调用析构函数。
常见用法如下:
// 1. 多个使用 | |
auto p1 = make_shared<int>(2); | |
auto p2 = p1; | |
// 2. 作为类内要返回自己的函数 | |
// !!!错误示范!!! | |
class T{ | |
shared_ptr<T> get_self(){ | |
return this; // 这里就会再次使用裸指针来构造共享指针,原共享指针失去意义 | |
} | |
} | |
//--- 正确示范 --- | |
class T:public enable_shared_from_this<T> { | |
shared_ptr<T> get_self(){ | |
return shared_from_this(); | |
} | |
} |
# weak_ptr
weak_ptr 用以辅助解决 shared_ptr 的循环引用问题。
#include <memory> | |
#include <iostream> | |
using namespace std; | |
class B; | |
class A | |
{ | |
public: | |
shared_ptr<B> spb; | |
~A() { std::cout << "~A()" << std::endl; } | |
}; | |
class B | |
{ | |
public: | |
shared_ptr<A> spa; | |
~B() { std::cout << "~B()" << std::endl; } | |
}; | |
int main() | |
{ | |
auto a = make_shared<A>(); | |
auto b = make_shared<B>(); | |
a->spb = b; | |
b->spa = a; | |
std::cout << "a.use_count() = " << a.use_count() << std::endl; // a.use_count() = 2 | |
std::cout << "b.use_count() = " << b.use_count() << std::endl; // b.use_count() = 2 | |
return 0; | |
} |
执行结果如下:
PS D:\Files\Code\test> g++ 1.cpp -o 1
PS D:\Files\Code\test> ./1
a.use_count() = 2
b.use_count() = 2
可见,由于 A,B 内部存在了对方的 shared_ptr, 强引用计数都为 2, 谁都不肯放资源,只能由操作系统擦屁股。但是这个时候两个部分的析构都未正确释放。因此我们需要引入 weak_ptr 来协助。
#include <memory> | |
#include <iostream> | |
using namespace std; | |
class B; | |
class A | |
{ | |
public: | |
weak_ptr<B> spb; | |
~A() { std::cout << "~A()" << std::endl; } | |
}; | |
class B | |
{ | |
public: | |
weak_ptr<A> spa; | |
~B() { std::cout << "~B()" << std::endl; } | |
}; | |
int main() | |
{ | |
auto a = make_shared<A>(); | |
auto b = make_shared<B>(); | |
a->spb = b; | |
b->spa = a; | |
std::cout << "a.use_count() = " << a.use_count() << std::endl; // a.use_count() = 1 | |
std::cout << "b.use_count() = " << b.use_count() << std::endl; // b.use_count() = 1 | |
return 0; | |
} |
PS D:\Files\Code\test> g++ 1.cpp -o 1 | |
PS D:\Files\Code\test> ./1 | |
a.use_count() = 1 | |
b.use_count() = 1 | |
~B() | |
~A() |
这是由于 weak_ptr 的引用计数是弱引用计数。shared_ptr 维护的计数是强引用计数。只有强引用计数为 0 才会析构。但是控制块会保证所有弱引用计数为 0 后再析构。
那么 weak_ptr 和 shared_ptr 有啥不同呢? 不同就是 weak_ptr 绝对不允许直接操作指针相关的任何内容。如果有需求,必须通过 weak_ptr<T>::lock 方法来获取一个 shared_ptr 来操作。
# 智能指针实现原理
如上。
# 智能指针,里面的计数器何时会改变
- unique_ptr 无引用计数
- shared_ptr 维护引用计数
由裸指针构造,由 shared_ptr 构造 会增加 ---- 强引用计数
shared_ptr 栈上释放会减少,主动调用 reset () 会减少 ---- 强引用计数
weak_ptr 通过 shared_ptr 构造 会增加 --- 弱引用计数
weak_ptr 栈上释放或者调用 reset () 会减少 --- 弱引用计数
# 智能指针和管理的对象分别在哪个区
智能指针本身在栈区,托管的资源在堆区,利用了栈对象超出生命周期后自动析构的特征,所以无需手动 delete 释放资源。
# 面向对象
# 面向对象的特性:多态原理
# 介绍一下虚函数,虚函数怎么实现的
# 多重继承和多继承的情况下子类的虚函数表是怎样的?
# 多态和继承在什么情况下使用
# 除了多态和继承还有什么面向对象方法
# 内存分布
# C++ 内存分布。什么样的数据在栈区,什么样的在堆区
- 全局变量区
- .data 已初始化不为 0 的全局变量和静态局部变量
- .bss 未初始化或初始化为 0 的全局变量和静态局部变量
- 常量区
- .rodata 存放只读数据 const
- 代码区
- .text 存放代码指令
- 栈区
- 非静态局部变量的分配
- 堆区
- 动态内存的分配,使用 new,delete 管理
# C++ 内存管理(RAII 啥的)
- new & delete / new[] & delete[]
- 需要手动释放
- 栈空间自动释放
- RAII - 三个智能指针
# C++ 从源程序到可执行程序的过程
24.5.27 刚考完系统基础 杠杠的
- 预处理
.cpp --> .i
接受源代码,并处理预处理指令。包括 #include 导入对应的头文件内容,#define 替换宏。 - 编译
.i --> .s 汇编文件
检查语法,进行词法分析等工作。主要是把代码转化为规定的汇编语言代码。 - 汇编
.s --> .o 可重定位文件
将汇编代码转为机器代码,生成可重定位目标文件。这个文件中重定位的符号部分都是空着的。 - 链接
.o --> 可执行文件!
符号解析 + 重定位。将各重定位节的重定位了,填上符号所对应的最终地址,那么我们最终的文件就可执行了。
# size_of 是在编译期还是在运行期确定
编译期
# 函数重载的机制。重载是在编译期还是在运行期确定
编译期
# 如果 new 了之后出了问题直接 return。会导致内存泄漏。怎么办
(智能指针,raii)