# 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允许函数返回
unique_ptr<T> get_unique(){
    unique_ptr<T> u;
    return u;
}
auto x = get_unique() // 触发移动构造

# shared_ptr

shared_ptr 主要针对指针悬挂问题,shared_ptr 的内部会维护一个强引用计数。当引用计数为 0 时自动调用析构函数。
常见用法如下:

shared_ptr常见用法
// 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 的循环引用问题。

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 来协助。

使用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 来操作。

# 智能指针实现原理

如上。

# 智能指针,里面的计数器何时会改变

  1. unique_ptr 无引用计数
  2. 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)

# 多进程和多线程

# 多进程 fork 后不同进程会共享哪些资源

# 多线程里线程的同步方式有哪些

# STL

# 移动语义与完美转发

# 指针常量和常量指针

# vector 的原理,怎么扩容

# 介绍一下 const

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Yian Zhao 微信支付

微信支付

Yian Zhao 支付宝

支付宝

Yian Zhao 贝宝

贝宝