C++ 八股文(高级 C++ 特性)
1. C++ 中的右值引用和移动语义是什么?
1. 右值引用概念: 使用&&声明的引用类型,可以绑定到临时对象(右值),左值是有名字的对象右值是临时对象,右值引用延长临时对象的生命周期,是实现移动语义的基础。
2. 移动语义: 转移资源所有权而不是拷贝,避免深拷贝提高性能,移动后的对象处于有效但未定义状态,通过移动构造函数和移动赋值运算符实现,时间复杂度从O(n)降到O(1)。
3. 值类别: 左值lvalue(有名字可取地址),纯右值prvalue(临时对象字面量),将亡值xvalue(std::move的结果),左值引用绑定左值右值引用绑定右值,const左值引用可以绑定右值。
4. 使用场景: 返回大对象避免拷贝,容器插入临时对象,unique_ptr转移所有权,完美转发保持值类别,swap操作使用移动提高效率。
class Buffer {
char* data;
size_t size;
public:
// 移动构造
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // 转移所有权
}
// 移动赋值
Buffer& operator=(Buffer&& other) noexcept {
if(this != &other) {
delete[] data;
data = other.data;
other.data = nullptr;
}
return *this;
}
};
Buffer b1(1024);
Buffer b2 = std::move(b1); // 移动,不拷贝
2. C++ 中的 std::move 和 std::forward 有什么区别?
1. std::move作用: 无条件将左值转换为右值引用,不移动任何东西只是类型转换,告诉编译器可以移动这个对象,实际移动由移动构造/赋值完成,使用后源对象不应再使用。
2. std::forward作用: 完美转发保持参数的值类别,左值转发为左值右值转发为右值,用于模板函数转发参数,避免不必要的拷贝,实现泛型代码的高效转发。
3. 使用场景: move用于明确表示转移所有权,forward用于模板函数的参数转发,move是无条件转换forward是有条件转换,move用于实现移动语义forward用于实现完美转发。
4. 实现原理: move等价于static_cast<T&&>,forward根据模板参数决定转换类型,都是编译期操作无运行时开销,正确使用可以显著提高性能。
// std::move:无条件转换为右值
std::string str = "hello";
std::string str2 = std::move(str); // str被移动
// std::forward:完美转发
template<typename T>
void wrapper(T&& arg) {
func(std::forward<T>(arg)); // 保持arg的值类别
}
wrapper(10); // 右值,forward转发为右值
int x = 10;
wrapper(x); // 左值,forward转发为左值
3. C++11 中的 Lambda 表达式是什么?
1. 基本语法:捕获列表 mutable noexcept -> 返回类型 { 函数体 },捕获列表指定外部变量的访问方式,参数列表和普通函数相同,返回类型可以省略由编译器推导。
2. 捕获方式: []不捕获,[=]按值捕获所有变量,[&]按引用捕获所有变量,[this]捕获this指针,[x, &y]混合捕获,[=, &x]默认按值x按引用,C++14支持初始化捕获。
3. 特性: mutable允许修改按值捕获的变量,noexcept声明不抛异常,泛型lambda使用auto参数(C++14),constexpr lambda编译期计算(C++17),模板lambda(C++20)。
4. 使用场景: STL算法的谓词函数,回调函数和事件处理,线程函数,延迟计算和惰性求值,替代函数对象简化代码。
// 基本用法
auto add = [](int a, int b) { return a + b; };
int sum = add(3, 4);
// 捕获外部变量
int x = 10;
auto f1 = [x]() { return x * 2; }; // 按值捕获
auto f2 = [&x]() { x++; }; // 按引用捕获
auto f3 = [=, &x]() { return x + y; }; // 混合捕获
// 初始化捕获(C++14)
auto f4 = [ptr = std::make_unique<int>(42)]() { return *ptr; };
// 泛型lambda(C++14)
auto print = [](auto x) { std::cout << x; };
4. 如何使用 C++ 中的 constexpr 定义常量表达式?
1. 基本概念: constexpr声明的变量或函数可以在编译期求值,比const更强要求编译期可计算,constexpr变量必须用常量表达式初始化,constexpr函数可以在编译期或运行期执行。
2. constexpr函数: 函数体必须是单一return语句(C++11)或满足一定限制(C++14放宽),参数和返回值必须是字面类型,可以递归调用,用常量参数调用时编译期求值。
3. constexpr类: 构造函数可以是constexpr,成员函数可以是constexpr,C++14支持constexpr成员变量修改,可以创建编译期对象,用于模板元编程和编译期计算。
4. 使用场景: 数组大小和模板参数,编译期计算避免运行时开销,类型萃取和元编程,constexpr if实现编译期分支(C++17),性能优化和代码生成。
// constexpr变量
constexpr int size = 100;
int arr[size]; // 编译期常量
// constexpr函数
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n-1);
}
constexpr int val = factorial(5); // 编译期计算
// constexpr类
class Point {
int x, y;
public:
constexpr Point(int x_, int y_) : x(x_), y(y_) {}
constexpr int getX() const { return x; }
};
constexpr Point p(3, 4);
constexpr int x = p.getX(); // 编译期求值
// constexpr if(C++17)
template<typename T>
auto getValue(T t) {
if constexpr (std::is_pointer_v<T>)
return *t;
else
return t;
}
5. C++ 中如何实现自定义的内存管理器?
1. 基本方法: 重载operator new和operator delete全局或类级别,实现allocate和deallocate函数,使用内存池预分配内存,实现STL兼容的allocator接口。
2. 内存池实现: 预分配大块内存减少系统调用,维护空闲链表快速分配,固定大小块或多级大小,使用位图或链表管理空闲块,注意线程安全和内存对齐。
3. STL allocator: 实现allocate、deallocate、construct、destroy,定义value_type等类型别名,支持rebind转换为其他类型的allocator,C++17简化了allocator接口。
4. 使用场景: 频繁分配释放小对象,减少内存碎片,实时系统避免不确定延迟,嵌入式系统精确控制内存,性能敏感的应用优化分配速度。
// 简单内存池
class MemoryPool {
union Block { Block* next; char data[32]; };
Block* freeList = nullptr;
public:
void* allocate() {
if(!freeList) expandPool();
Block* block = freeList;
freeList = freeList->next;
return block;
}
void deallocate(void* ptr) {
Block* block = static_cast<Block*>(ptr);
block->next = freeList;
freeList = block;
}
};
// STL allocator
template<typename T>
class PoolAllocator {
public:
using value_type = T;
T* allocate(size_t n) {
return static_cast<T*>(pool.allocate());
}
void deallocate(T* p, size_t n) {
pool.deallocate(p);
}
};
std::vector<int, PoolAllocator<int>> vec;
6. C++ 中的模板元编程(Template Metaprogramming)是什么?
1. 基本概念: 使用模板在编译期进行计算和代码生成,模板实例化时执行计算,结果是编译期常量或类型,图灵完备可以实现任意计算,性能优于运行期计算但编译时间长。
2. 实现技术: 模板递归实现循环,模板特化实现条件分支,类型作为值传递,constexpr函数简化元编程(C++11),变参模板处理任意数量参数(C++11)。
3. 应用场景: 编译期计算常量(阶乘、斐波那契),类型萃取和类型操作,表达式模板优化数值计算,策略模式的编译期选择,静态多态和零开销抽象。
4. 现代替代: constexpr函数更易读易写,constexpr if简化条件分支(C++17),concepts约束模板参数(C++20),fold expression简化变参处理(C++17)。
// 编译期计算阶乘
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N-1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
constexpr int f5 = Factorial<5>::value; // 120
// 类型选择
template<bool Cond, typename T, typename F>
struct If { using type = T; };
template<typename T, typename F>
struct If<false, T, F> { using type = F; };
// 现代方式:constexpr函数
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n-1);
}
7. 如何实现 C++ 中的类型萃取(Type Traits)?
1. 基本概念: 在编译期查询和操作类型信息,标准库提供std::is_integral、std::is_pointer等,使用模板特化实现,返回编译期常量或类型,用于泛型编程和SFINAE。
2. 实现方法: 主模板定义默认行为,特化模板处理特定类型,使用std::true_type和std::false_type表示布尔值,使用type成员表示类型,C++17的std::void_t简化实现。
3. 常用traits: is_same判断类型相同,is_base_of判断继承关系,remove_const移除const,add_pointer添加指针,enable_if条件启用,decay类型退化。
4. 使用场景: SFINAE选择重载函数,constexpr if编译期分支,概念约束模板参数,类型转换和适配,实现泛型算法的类型检查。
// 判断是否是指针
template<typename T>
struct is_pointer : std::false_type {};
template<typename T>
struct is_pointer<T*> : std::true_type {};
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。
查看15道真题和解析