本文整理了200+ 道常见面试题。大部分的面试题对于 C++ 后端、桌面开发、嵌入式、音视频开发都是通用的。目录一、C/C++ 常见问题二、数据结构算法常见问题三、gdb / gcc / g++四、设计模式五、操作系统原理六、系统编程七、定时器八、网络原理九、网络编程十、数据库 MySQL十一、Redis十二、服务器开发十三、RPC十四、开放性问题 / 项目相关十五、音视频相关一、C/C++ 常见问题1. 智能指针实现原理智能指针是一个类模板,封装了原始指针,基于RAII(资源获取即初始化)思想管理动态内存:构造函数获取资源(持有裸指针),析构函数自动释放资源;重载operator*和operator-模拟指针行为;通过引用计数(shared_ptr)或独占所有权(unique_ptr)管理生命周期;栈上对象出作用域自动析构,从而保证异常安全(即使中途return或抛异常也会释放)。template typename T class MySharedPtr { T* ptr; int* count; // 引用计数 public: explicit MySharedPtr(T* p = nullptr) : ptr(p), count(new int(1)) {} ~MySharedPtr() { if (--(*count) == 0) { delete ptr; delete count; } } // 拷贝构造:共享计数 +1 MySharedPtr(const MySharedPtr o) : ptr(o.ptr), count(o.count) { ++(*count); } };2. 智能指针里面的计数器何时会改变以shared_ptr为例,引用计数变化时机:+1:拷贝构造、拷贝赋值(赋值前先处理旧引用);-1:析构、调用reset()、被新的shared_ptr覆盖赋值;不变:移动构造 / 移动赋值(所有权转移,不增减计数);weak_ptr不增加引用计数(只增加弱引用计数),unique_ptr无引用计数。3. 智能指针和管理的对象分别在哪个区智能指针对象本身在栈区(作为局部对象/成员存在);它托管的资源(new出来的对象)在堆区;核心原理:利用栈对象超出生命周期后自动析构的特征,析构时释放堆资源,无需手动delete。4. 面向对象的特性:多态原理面向对象三大特性:封装、继承、多态。静态多态:函数重载、运算符重载、模板(编译期确定);动态多态:虚函数(运行期确定)。动态多态原理:类含虚函数时编译器生成虚函数表(vtable),对象头部存虚指针(vptr)。通过基类指针/引用调用虚函数时,运行时按对象实际类型查虚表取得函数地址,实现"一个接口,多种实现"。5. 介绍一下虚函数,虚函数怎么实现的虚函数用virtual声明,实现动态绑定(晚绑定);实现机制:编译器为含虚函数的类生成虚函数表(数组,存放各虚函数地址),对象内存开头放vptr指向该表;调用p-func()时编译成间接调用:(*(p-vptr)[index])(p);构造对象时vptr被初始化为当前类的虚表(构造函数中设置),因此基类构造函数中调用虚函数不会发生多态。6. 多态和继承在什么情况下使用继承:存在 "is-a" 关系(派生类是基类的一种)、需要复用基类接口与公共代码时使用;组合优先于继承,仅在真正语义相关时用继承;多态:需要以统一接口操作不同子类对象、后续可能扩展新类型时使用(如策略模式、工厂模式、插件化架构);设计原则:面向接口编程、开闭原则——增加新类型不修改已有代码。7. 除了多态和继承还有什么面向对象方法封装:隐藏内部实现,通过接口访问;抽象:抽象基类/纯虚函数定义契约;组合/聚合:对象包含对象,优于继承(低耦合);泛型编程(模板):类型参数化,编译期多态;接口(纯虚类):定义行为规范;RAII:用对象生命周期管理资源。8. C++ 内存分布。什么样的数据在栈区,什么样的在堆区栈区:局部变量、函数参数、返回地址,编译器自动分配释放,容量小(默认几 MB);堆区:new/malloc动态分配的内存,需手动释放,容量大;全局/静态区:全局变量、static变量;常量区:字符串常量等只读数据;代码区:存放函数指令。9. C++ 内存管理(RAII 啥的)RAII(Resource Acquisition Is Initialization):在构造函数中获取资源(内存、文件、锁、连接),析构函数中释放,资源生命周期与对象绑定,天然异常安全;结合智能指针管理堆内存;栈上对象自动释放;注意new[]配delete[]、防内存泄漏、防悬挂指针。10. C++ 从源程序到可执行程序的过程四个阶段:预处理:展开宏、处理#include、删除注释、条件编译,生成.i文件;编译:词法/语法/语义分析、优化,生成汇编代码.s;汇编:汇编代码转机器指令,生成目标文件.o(含符号表、重定位信息);链接:合并目标文件与库(静态/动态),符号解析与重定位,生成可执行文件g++ -E main.cpp -o main.i # 预处理 g++ -S main.i -o main.s # 编译 g++ -c main.s -o main.o # 汇编 g++ main.o -o main # 链接11. 一个对象 = 另一个对象会发生什么(赋值构造函数)触发拷贝赋值运算符operator=;默认(编译器生成)执行浅拷贝(逐位拷贝):指针成员会复制地址,两个对象指向同一块内存,析构时双重释放,还可能内存泄漏(原资源未释放);解决:自定义深拷贝(重新分配内存并复制内容)或使用智能指针/copy-and-swap。12. 如果 new 了之后出了问题直接 return,会导致内存泄漏。怎么办使用智能指针(unique_ptr/shared_ptr)自动管理,出作用域自动释放;使用RAII封装资源;使用异常安全写法:分配与使用都在对象生命周期内完成;避免裸new/delete配对管理,遵循"谁分配谁释放"。void f() { std::unique_ptrint p(new int(42)); // return/异常也不泄漏 if (cond) return; *p = 100; }13. C++11 的智能指针有哪些?weak_ptr 的使用场景?什么情况下会产生循环引用unique_ptr:独占所有权,不可拷贝,可移动,无额外开销;shared_ptr:共享所有权,引用计数,可拷贝,线程安全的计数操作;weak_ptr:弱引用,不增加引用计数,用于打破循环引用、观察shared_ptr管理的对象是否存活(expired()/lock())。循环引用:两个对象各自持有对方的shared_ptr,形成环,计数永远不为 0,内存无法释放。struct A { std::shared_ptrB b; }; struct B { std::shared_ptrA a; }; // 循环引用 // 解决:将其中一方改为 weak_ptr struct B { std::weak_ptrA a; };14. 多进程 fork 后不同进程会共享哪些资源共享:代码段(只读)、打开的文件描述符表(及文件偏移)、信号处理器、环境变量、共享内存(mmap MAP_SHARED)、全局的只读数据(写时拷贝,物理页共享);不共享:独立的虚拟地址空间、栈、堆、进程 PID、互斥锁状态(锁不是共享的);fork 后采用写时拷贝(COW):只有写入时才复制物理页,节省内存。15. 多线程里线程的同步方式有哪些互斥锁(mutex):保护临界区;读写锁(rwlock):读共享、写独占;条件变量(condition_variable):线程间等待/通知;信号量(semaphore):控制并发数量;原子操作(atomic):无锁同步;自旋锁(spinlock):忙等,适合短临界区;屏障(barrier):多线程到达同步点。16. sizeof 是在编译期还是在运行期确定编译期确定(常量表达式),不执行表达式;例外:C 语言中的变长数组(VLA)在运行期确定(C++ 不支持 VLA);sizeof不计算表达式值,例如sizeof(*p)即使p为空指针也安全(编译期计算类型大小)。17. 函数重载的机制。重载是在编译期还是在运行期确定重载依据:函数名相同 + 参数列表不同(个数/类型/顺序),与返回类型无关;机制:编译器进行名字修饰(name mangling),将参数类型编码进符号名,使链接器可区分;在编译期确定调用哪个版本(静态多态)。18. 指针常量和常量指针常量指针(const 修饰指针本身):int* const p;—— 指针不能再指向别处,但指向的内容可变;指针常量(const 修饰指向的内容):const int* p;或int const* p;—— 指向的内容不可通过该指针修改,但指针本身可指向别处;记忆技巧:const在*左边修饰指向对象,在*右边修饰指针本身。19. vector 的原理,怎么扩容底层是动态数组(连续内存),维护三个指针:start、finish(size)、end_of_storage(capacity);插入时若size == capacity触发扩容:申请新内存(通常1.5 或 2 倍),将旧元素移动/拷贝过去,释放旧内存;扩容是高代价操作(拷贝/移动所有元素),因此预先reserve()可避免频繁扩容;扩容后迭代器、指针、引用失效。20. 介绍一下 const修饰变量:只读,不可修改;修饰指针:见第 18 题(顶层/底层 const);修饰成员函数:void f() const;承诺不修改成员变量(mutable成员除外),const对象只能调用const成员函数;修饰引用参数:const T避免拷贝且不可修改;编译期约束,可与宏区分(有类型检查、作用域)。21. 引用和指针的区别区别引用指针本质变量的别名存地址的变量初始化必须初始化可以不初始化重新绑定不可改绑可重新赋值空值无空引用可为 nullptr运算无指针运算支持 ++/-- 等sizeof原对象大小地址大小(8 字节 64 位)引用更安全、语义清晰;指针更灵活但易出错。22. C++ 新特性知道哪些C++11:auto、nullptr、范围 for、智能指针、右值引用/移动语义、lambda、std::function/std::bind、可变参数模板、constexpr、thread、chrono、tuple;C++14:泛型 lambda、make_unique、返回类型推导;C++17:if constexpr、结构化绑定、std::optional/variant/any、filesystem、折叠表达式;C++20:协程(Coroutine)、concept、模块(Module)、ranges、span;C++23:std::expected、print等。23. 类型转换static_cast:编译期相关类型的转换(int→double、void*→T*、下行转换不检查);dynamic_cast:多态类型安全下行转换(运行时检查,失败返回 nullptr/抛 bad_cast),需有虚函数;const_cast:去除/添加 const;reinterpret_cast:底层二进制重新解释(如指针↔整数),最危险。double d = static_castdouble(1) / 3; Base* b = dynamic_castBase*(derived_ptr); // 安全下行 const_castchar*(str). ...24. RAII 基于什么实现的基于对象生命周期与作用域:对象在进入作用域时构造(获取资源),离开作用域时析构(释放资源),配合构造/析构函数的确定性调用,保证资源被自动、及时释放,异常安全。25. 手撕:unique_ptr 控制权转移(移动语义);类继承、堆栈上分别代码实现多态#include memory std::unique_ptrint p1(new int(10)); std::unique_ptrint p2 = std::move(p1); // 控制权转移,p1 变为空多态实现:struct Base { virtual ~Base() {} virtual void show() { std::cout "Base\n"; } }; struct Derived : Base { void show() override { std::cout "Derived\n"; } }; // 堆上:基类指针 + 虚函数 std::unique_ptrBase p = std::make_uniqueDerived(); p-show(); // Derived // 栈上:基类引用指向派生类对象 Derived d; Base ref = d; ref.show(); // Derived26. unique_ptr 和 shared_ptr 区别特性unique_ptrshared_ptr所有权独占共享(引用计数)拷贝禁止(可移动)允许(计数+1)开销与裸指针相当计数维护(原子操作)循环引用不存在可能产生,需 weak_ptr适用独占资源多处共享资源27. 右值引用语法:T,只能绑定右值(临时对象、字面量、std::move的结果);作用:实现移动语义(移动构造/移动赋值),将即将消亡对象的资源"偷走",避免深拷贝;延长临时对象生命周期(绑定到右值引用);配合std::move/std::forward使用。std::vectorint v1{1,2,3}; std::vectorint v2 = std::move(v1); // 移动构造,v1 为空28. 函数参数可不可以传右值可以,有以下方式:参数声明为右值引用void f(T x):只能传右值;按值传递void f(T x):实参为右值时调用移动构造(传左值则拷贝);const T也能绑定右值,但不可修改且不会移动;模板中T为万能引用,配合完美转发可保留左/右值属性。29. 参考 c/c++ 堆栈实现自己的堆栈(不能用 STL 容器)用动态数组 + 栈顶指针实现:class MyStack { int* data; int cap; int top; public: MyStack(int n = 16) : data(new int[n]), cap(n), top(0) {} ~MyStack() { delete[] data; } void push(int x) { if (top == cap) resize(); data[top++] = x; } int pop() { return data[--top]; } int peek() const { return data[top-1]; } bool empty() const { return top == 0; } private: void resize() { /* 扩容拷贝 */ } };30. STL 容器了解吗?底层如何实现容器底层结构特点vector动态数组随机访问 O(1),尾部操作高效list双向链表任意位置插入删除 O(1)deque分段连续空间(map+缓冲区)两端操作 O(1)map/set/multimap/multiset红黑树有序,O(logN)unordered_map/unordered_set哈希表(拉链法)无序,平均 O(1)priority_queuevector 上的堆(默认大顶堆)取最大/最小 O(1)红黑树实现要点:节点红/黑着色、根黑、红节点子必黑、任意路径黑节点数相同、最长路径不超过最短的 2 倍。31. 完美转发介绍一下,去掉 std::forward 会怎样?完美转发:在模板中将实参的左/右值属性原样传递给被调函数,保持值类别不丢失;实现:万能引用T+std::forwardT(arg);去掉std::forward:参数在函数内部是具名左值,一律按左值传递,右值实参无法触发移动语义,导致不必要的深拷贝,性能下降。templatetypename T void wrapper(T arg) { foo(std::forwardT(arg)); // 保留实参左右值属性 }32. 介绍一下 unique_lock 和 lock_guard 区别lock_guard:构造时加锁、析构时解锁,不可手动控制,简单高效,适合常规作用域;unique_lock:更灵活——可手动 lock/unlock、延迟加锁(defer_lock)、尝试加锁(try_lock)、可移动,常与条件变量配合(wait 需要 unlock 能力);开销上unique_lock略大,默认优先用lock_guard。33. C 代码中引用 C++ 代码有时候会报错为什么?C++ 支持函数重载,编译时对函数名进行名字修饰(name mangling)(如func→_Z4funcv);C 编译器不做修饰;C++ 代码编译出的符号与 C 侧期望的符号不匹配,链接失败(undefined reference);解决:C++ 头文件用extern "C" { ... }包裹,使函数按 C 方式命名和链接。#ifdef __cplusplus extern "C" { #endif void c_api_func(int); #ifdef __cplusplus } #endif34. 静态多态有什么?虚函数原理、虚表是什么时候建立的、为什么析构函数要设置成虚函数静态多态:函数重载、模板、运算符重载(编译期绑定);虚函数原理:虚函数表(vtable)+ 虚指针(vptr),运行时根据对象实际类型查表间接调用;虚表建立时机:虚表在编译期生成(存放在只读数据段);对象构造时(构造函数体内)vptr被设置为指向当前类的虚表;析构函数必须虚:若基类析构非虚,通过基类指针delete派生类对象时只调用基类析构,派生类资源泄漏;虚析构保证正确调用派生类析构。35. map 为啥用红黑树不用 AVL 树?(map 和 unordered_map 区别)AVL 严格平衡(左右子树高度差 ≤1),查找稍快,但插入/删除需要频繁旋转,维护成本高;红黑树只保证最长路径 ≤ 2 倍最短路径(近似平衡),插入删除最多 3 次旋转,综合性能更优;两者查找复杂度都是 O(logN)。map vs unordered_map:对比mapunordered_map底层红黑树哈希表有序性有序无序查找O(logN)平均 O(1)内存节点连续紧凑桶+节点,开销大适用需要有序/范围查询追求查找速度36. inline 失效场景函数体过大(编译器权衡后放弃内联);函数包含递归;函数地址被取(需要实体存在);虚函数(动态绑定无法内联);通过函数指针/回调调用;编译优化选项关闭;构造函数/析构函数复杂(隐式调用多);inline只是建议,编译器可忽略。37. C++ 中 struct 和 class 区别默认访问权限:struct默认为public,class默认为private;默认继承方式:struct默认public继承,class默认private继承;其他(成员函数、模板、继承等)完全相同。38. 如何防止一个头文件 include 多次include guard(可移植):#ifndef MY_HEADER_H #define MY_HEADER_H // 内容 #endif#pragma once(主流编译器支持,简单高效):#pragma once39. lambda 表达式的理解,它可以捕获哪些类型lambda 本质是编译器生成的匿名仿函数对象,格式:[捕获](参数) - 返回类型 { 函数体 };捕获类型:[=]值捕获所有用到的局部变量;[]引用捕获所有用到的局部变量;[x, y]混合捕获;[this]捕获 this 指针(访问成员);C++17 支持[*this]值捕获对象;C++14 初始化捕获[x = expr];默