C++八股文面经总结(含答案),收藏起来慢慢看 - linux的文章 - 知乎
https://zhuanlan.zhihu.com/p/669939217
1.函数指针
C++ 中的 函数指针 是一种可以指向函数的指针变量,允许你将函数作为参数传递、存储、动态调用等。它在回调机制、策略模式、动态调度等场景中非常常见。
void sayHello() {
std::cout << "Hello callback!" << std::endl;
}
void doSomething(void (*callback)()) {
// do something
callback(); // 调用传入的函数
}
int main() {
doSomething(sayHello); // 传入函数作为参数
}
和仿函数的区别
项目 | 函数指针 (*p)() | 仿函数 operator() |
---|---|---|
本质 | 指向函数的指针 | 重载 () 运算符的类对象 |
类型 | 指针 | 类/结构体对象 |
可调用 | ✅ | ✅ |
可携带状态 | ❌(无状态) | ✅(有成员变量) |
适合场景 | 简单回调、C 风格接口 | 需要携带上下文信息的回调 |
2.lamda表达式
在 C++ 中,Lambda 表达式 是一种用于定义匿名函数(inline 函数对象)的机制,写法简洁、灵活,非常适合用于临时函数逻辑、回调、STL 算法等场景。
[capture](parameters) -> return_type {
// function body
};
#include <iostream>
using namespace std;
int main() {
int base = 10;
// 捕获外部变量 base,同时接受两个参数 a 和 b
auto add_base = [base](int a, int b) {
return a + b + base;
};
cout << add_base(1, 2) << endl; // 输出 13
return 0;
}
3.bind用法
#include <functional>
auto new_func = std::bind(原函数, 参数1, 参数2, ...);
#include <iostream>
#include <functional>
using namespace std;
int add(int a, int b) {
return a + b;
}
int main() {
// 绑定第一个参数为10,第二个参数用占位符
auto f = std::bind(add, 10, std::placeholders::_1);
cout << f(5) << endl; // 输出 15,相当于调用 add(10, 5)
}
实际上,很多 std::bind
的用法在现代 C++ 中建议用 Lambda 表达式替代:
auto f = [](int b){ return add(10, b); };
4.C++ STL六大组件
为了建立数据结构和算法的一套标准,并且降低他们之间的耦合关系,以提升各自的独立性、弹性、交互操作性(相互合作性,interoperability),诞生了STL。
STL提供了六大组件,彼此之间可以组合套用,这六大组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。
容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据,从实现角度来看,STL容器是一种class template。
算法:各种常用的算法,如sort、find、copy、for_each。从实现的角度来看,STL算法是一种function tempalte.
迭代器:扮演了容器与算法之间的胶合剂,共有五种类型,从实现角度来看,迭代器是一种将operator* , operator-> , operator++,operator–等指针相关操作予以重载的class template. 所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class 或者class template
适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
空间配置器:负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class tempalte.
STL六大组件的交互关系,容器通过空间配置器取得数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数。
5.C++11新特性
C++11 新特性总结 - fengMisaka - 博客园
6.C++设计模式
创建型模式,结构型模式,观察型模式。
7.C++动态库和静态库的区别
静态库需要编译时链接,动态库运行时链接。
静态库不支持热替换,需要重新编译来链接。动态库支持热替换,如果跟其他库没有更改的链接关系可以不重新编译。
静态链接:浪费空间,每个可执行程序都会有目标文件的一个副本,这样如果目标文件进行了更新操作,就需要重新进行编译链接生成可执行程序(更新困难);优点就是执行的时候运行速度快,因为可执行程序具备了程序运行的所有内容。
动态链接:节省内存、更新方便,但是动态链接是在程序运行时,每次执行都需要链接,相比静态链接会有一定的性能损失。
8.右值(右值引用)在C++ 11里面的核心作用
移动语义
std::string a = "Hello";
std::string b = std::move(a); // 将 a 的资源转移给 b,避免不必要的复制
完美转发 (仅仅对模板函数等非实际函数有意义,可以保持传进来的左值、右值的属性)
template <typename T>
void wrapper(T&& arg) {
func(std::forward<T>(arg)); // 保持原始值类别
}
避免不必要的拷贝
void setValue(const std::string& s); // 拷贝
void setValue(std::string&& s); // 移动
场景 | 说明 |
---|---|
构造函数优化 | 移动构造函数,避免深拷贝 |
容器插入 | emplace_back(std::move(obj)) |
返回值优化 | 返回临时对象,直接使用右值引用接收 |
函数重载优化 | 为左值和右值分别定义重载 |
9.头文件中能定义全局变量吗
如果在头文件中定义全局变量,当该头文件被多个文件 include 时,该头文件中的全局变量就会被定义多次,导致重复定义,因此不能再头文件中定义全局变量。
全局变量能够被多个文件共享使用,而全局静态变量只在当前文件使用。
10.什么是内存对齐
内存对齐(Alignment)是指数据在内存中的存储起始地址必须是某个数值(通常是2、4、8、16等)的整数倍。
为什么需要内存对齐
现代CPU访问内存时,通常会以"字"(word)为单位进行读取。例如:
-
32位系统通常以4字节为单位
-
64位系统通常以8字节为单位
如果数据没有正确对齐:
-
性能损失:CPU可能需要多次内存访问才能读取完整数据
-
硬件异常:某些架构(如ARM、SPARC)会直接抛出硬件异常
结构体对齐规则
结构体的对齐遵循以下原则:
-
结构体的对齐要求等于其成员中最大的对齐要求
-
结构体的大小必须是其对齐要求的整数倍
-
成员按照声明顺序排列,但编译器可能会插入填充字节(padding)来满足对齐要求
11.模板类+多态的核心设计思想
模板非常适合处理类型不确定的情况,而多态适合处理行为不确定的情况。两者结合:
-
模板 → 提高复用性和性能
-
多态 → 提供统一的接口,让不同行为对象可以替换
模板是编译期机制(静态多态),多态是运行期机制(动态多态) 。
12.深拷贝与浅拷贝的区别
- c++默认的拷贝构造函数是浅拷贝
浅拷贝就是对象的数据成员之间的简单赋值,如你设计了一个类而没有提供它的复制构造函数,当用该类的一个对象去给另一个对象赋值时所执行的过程就是浅拷贝。当数据成员中没有指针时,浅拷贝是可行的;但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象,所以,此时,必须采用深拷贝。
- 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,而不是一个简单的赋值过程,从而也就解决了指针悬挂的问题。
13.const_cast,static_cast,reinterpret_cast和dynamic_cast这几个的区别
类型 | 用途 | 安全性 | 运行时检查 | 常见用途 |
---|---|---|---|---|
static_cast | 编译期类型转换,类型兼容可预期 | ✅ 相对安全 | ❌ | 普通类型转换,基类<->子类转换 |
const_cast | 去除 const /volatile 限定符 | ⚠️ 危险 | ❌ | 去掉对象的常量性(谨慎使用) |
reinterpret_cast | 将数据的位模式重新解释为其他类型(低层转换) | ❌ 极不安全 | ❌ | 指针类型互转、二进制 hack |
dynamic_cast | 多态类型间安全向下转型(基类指针→派生类指针) | ✅ 安全 | ✅ RTTI 支持 | 多态类之间的安全转换(需虚函数) |
14.多线程并发,出了原子变量和锁,还有什么别的方法来避免数据竞争
读写锁、条件变量 & 事件驱动、任务并行框架(线程池)、基于消息的通讯
15.std::future的作用
std::future
提供了一种线程间安全、简洁、类型安全的机制,用于获取另一个线程中的异步任务结果,并同步等待其完成。
它本质上就是:
-
一个跨线程通信工具,
-
用来等待并获取另一个线程中异步任务的 返回值 或 异常,
-
避免了我们手动写 条件变量、状态标志、互斥量等复杂同步逻辑。
16.explicit关键字
避免类的单参数构造函数与及operator函数的隐式转换。类的无参数和多参数默认不可以隐式转换。
如果隐式转换可能导致:
1.类的构造函数重载会可能出现模糊的情况。
2.也可能导致隐式转换时会喂入未知的类型而编译无法识别,可能会导致未知的bug。
17.std::condition_variable的作用
std::condition_variable
(条件变量)是 C++ 多线程编程中用于线程间同步的重要工具,它的核心意义是 让线程能够高效地等待某个条件成立,并在条件满足时被唤醒,避免忙等待(busy-waiting)造成的 CPU 资源浪费。
说白了就是在多线程中避免使用waiting,while来等待,造成cpu资源的浪费。
18.volatile关键字的作用
加了volatile关键字的变量,可以避免编译器优化(把值放进寄存器里面),直接从内存中读取数值。
如果变量被编译器优化了,放进寄存器里面,就有可能线程1一直用线程1寄存器里面的值,线程2已经修改了该标志位,但是线程1还是读寄存器里面的旧值,没有办法通过该标志位跳出循环导致死循环。
19.静态函数和静态变量能声明为const吗
静态变量可以,静态函数不行。
因为const函数的意义就是该函数不能修改成员变量,而静态函数本来就没有this指针,本来就无法修改成员变量,定义为const没有意义,同时编译也不合法。
20.mutable
关键字的意义与用途
允许const成员函数修改该成员变量。
21.如何理解内存池
特征 | 内存池(Memory Pool) | 线程池(Thread Pool) |
---|---|---|
核心资源 | 预分配的内存块 | 预创建的线程 |
避免开销 | 避免频繁 new/delete | 避免频繁 create/join 线程 |
管理方式 | 内部维护一个“空闲内存块列表” | 内部维护一个“线程队列”或“任务队列” |
复用机制 | 内存块分配后归还可复用 | 线程执行任务后返回池中可复用 |
典型用途 | 高频小对象、临时数据等 | 高频任务、异步处理等 |
性能提升 | 避免碎片,提高分配速度 | 减少线程调度开销,提高并发性能 |
22.模板类和模板函数的区别
模板函数类型可以自动推导,模板类的类型必须显示指定。C++17以后可以自动推导。
模板函数不支持部分特化,模板类可以支持部分/全部特化。
23.指针和引用的区别
指针:存储另一个变量的地址,可以为空,可以修改所指向的对象。
引用:是另一个变量的别名,必须初始化,不能改变绑定对象。