1. vector 和 list 的区别?
-
vector 是内存连续的线性结构,支持随机访问,插入/删除(非末尾)效率低(需移动元素)。
-
list 是内存不连续的链表结构,不支持随机访问,但任意位置插入/删除效率高(O(1))。
-
遍历时 vector 因内存连续(缓存友好)性能更高,list 易导致 Cache Miss。
2. 进程和线程的区别?
-
进程是资源分配 的基本单位,线程是CPU调度的基本单位。
-
进程间内存隔离,线程共享堆、全局变量、代码段,但各有独立栈和寄存器上下文。
-
线程切换开销小(仅保存寄存器),进程切换需更新页表等,开销大。
-
进程间通信需 IPC(管道、消息队列等),线程可直接通过共享内存通信。
-
一个线程崩溃可能导致整个进程崩溃,进程间则相互独立。
3. 智能指针和循环引用?
-
智能指针是封装原生指针的栈对象 ,指向堆内存,内部含对象指针 和控制块指针(含引用计数等)。
-
循环引用:如
shared_ptr<A>和shared_ptr<B>相互指向,导致引用计数永不为0,内存泄漏。 -
解决:用
weak_ptr替代一方,避免增加引用计数。
4. 智能指针的两个区域
-
对象区域:指向实际堆内存资源。
-
控制块区域:指向堆中的控制块,包含强引用计数、弱引用计数、删除器等。
5. 信号量
- 本质是原子计数器,用于控制访问共享资源的线程数量,达到值时阻塞后续线程。
6. 互斥锁、读写锁、自旋锁
-
互斥锁:线程未获锁时被挂起(休眠)。
-
读写锁:读锁可共享,写锁独占。
-
自旋锁:线程未获锁时循环尝试(忙等),不挂起,适合短时间等待。
7. TCP 三次握手
-
过程:SYN → SYN-ACK → ACK。
-
服务端:收到 SYN 放入半连接队列 ,收到 ACK 后移入全连接队列。
8. map 和 unordered_map 的区别
-
map:基于红黑树,有序,查找 O(logN)。
-
unordered_map:基于哈希表,平均 O(1),无序,需处理哈希冲突。
9. 继承和多态
-
继承:实现代码复用。
-
多态:动态多态通过虚函数重写实现,统一接口;动态绑定在运行时确定调用函数。
10. 内存空间布局
-
自低地址到高地址:
-
代码区、常量区、静态区(BSS、Data)
-
堆区(向上增长)
-
共享库映射区
-
栈区(向下增长)
-
内核空间
-
-
堆和栈大小动态变化,中间有共享库区。
11. C++ 内存分布:栈区 vs 堆区
-
栈区:局部变量、函数参数、返回地址(自动管理)。
-
堆区:
malloc/new动态分配,手动管理生命周期。
12. C++ 从源程序到可执行文件的过程
-
预处理 :处理宏、头文件、条件编译等(
.cpp→.i)。 -
编译 :将源代码转为汇编(
.i→.s)。 -
汇编 :将汇编转为机器码(
.s→.o)。 -
链接:合并多个目标文件及库,生成可执行文件。
13. 多线程同步方式
- 互斥锁、读写锁、自旋锁、信号量、条件变量、屏障等。
14. sizeof在编译期还是运行期确定?
-
对固定大小类型(如基本类型、结构体)在编译期确定。
-
对**可变长度数组(C99)** 在运行时计算,但 C++ 不支持变长数组。
15. 函数重载机制及确定时机
-
重载依赖函数名、参数类型/数量/顺序,在编译期确定(名字改编)。
-
虚函数重写(多态)在运行期通过虚函数表确定。
16. 常量指针和指针常量
-
常量指针 :
const int* p或int const* p,指针指向的内容不可变。 -
指针常量 :
int* const p,指针本身(指向)不可变。
17. vector 原理及扩容
-
内部维护三个指针:
start、finish、end_of_storage。 -
当容量不足时,按一定比例(如 1.5 或 2 倍)重新分配内存,复制原数据到新空间。
18. 引用和指针的区别
-
引用必须初始化且不能为空,指针可以为空。
-
引用不能改变指向,指针可以。
-
引用是别名,指针是独立变量存储地址。
19. auto遍历容器
-
传统遍历:
for (auto it = vec.begin(); it != vec.end(); ++it)需手动*it解引用。 -
范围 for 循环:
for (auto& val : vec)自动解引用。
20. static_cast和 dynamic_cast
-
static_cast:编译期转换,用于相关类型(如数值类型、基类转派生类),不进行运行时检查。 -
dynamic_cast:运行期检查,用于多态类型(需虚函数),安全向下转换,失败返回nullptr。
21. inline和 #define
-
inline:编译期展开,有类型检查,可调试。 -
#define:预处理文本替换,无类型检查,难调试。
22. new和 malloc的区别
-
new是运算符,自动计算大小,调用构造函数,返回类型指针。 -
malloc是函数,需手动指定字节数,不调用构造函数,返回void*。
23. 如何防止头文件被多次包含?
-
#pragma once(非标准但广泛支持)。 -
条件编译:
#ifndef HEADER_NAME #define HEADER_NAME // 头文件内容 #endif
24. 父子进程匿名管道读写安全
-
管道是内核 FIFO 队列,保证读写顺序(先写先读)。
-
读写原子性:单次写操作 ≤ 4KB 是原子的。
-
无数据时读阻塞,满时写阻塞。
25. 类中能调用同类对象的私有方法吗?
- 可以,同类的不同对象可访问彼此的私有成员(封装是针对类而非对象)。
26. C++ 内存管理及常见问题
-
分区:栈、堆、静态/全局、常量、代码区。
-
管理方式:
new/delete、malloc/free、智能指针。 -
常见问题:
-
内存泄漏
-
野指针/悬垂指针
-
重复释放
-
内存碎片
-
27. 堆栈内存常见问题
-
栈:溢出(递归过深、局部变量过大)。
-
堆:
-
内存泄漏
-
越界访问
-
使用已释放内存
-
碎片化
-
28. 派生类中调用父类同名方法
class Base {
public:
void func() {}
};
class Derived : public Base {
public:
void func() {
Base::func(); // 显式调用父类版本
}
};
29. 派生类中定义父类指针
class Base { /* ... */ };
class Derived : public Base { /* ... */ };
Base* ptr = new Derived(); // 父类指针指向派生类对象
30. volatile的作用
- 防止编译器优化对变量的读写(如多线程、硬件寄存器访问),确保每次访问都从内存读取。
31. 僵尸进程和孤儿进程
-
僵尸进程 :子进程结束但父进程未回收(
wait/waitpid),占用进程表项。 -
孤儿进程:父进程先结束,子进程被 init/systemd 进程(PID=1)接管并回收。
-
危害:僵尸进程过多导致进程表满,孤儿进程无害(会被系统回收)。