1. 列表初始化(统一初始化,大括号 {})
作用
C++11 引入统一的初始化语法 ,使用 {} 对变量、数组、类、容器进行初始化,替代传统 ()、=,语法统一。
核心优点
- 防止窄化转换 :不允许精度丢失的隐式转换(如
int a{3.14}直接编译报错),安全性更高。 - 语法统一 :普通变量、数组、STL 容器、类对象都能用
{}。 - 支持默认初始化 :
int a{};自动初始化为 0,避免随机垃圾值。
示例
cpp
int a{10};
double b{3.14};
int arr[]{1,2,3};
vector<int> vec{1,2,3};
2. auto:自动类型推导
作用
编译器根据右侧的值自动推导变量类型,不用手动写类型,简化代码。
规则
- 会忽略顶层 const 和引用 ,想要保留需手动加:
const auto、auto&、auto&&。 - 不能用于:函数参数、函数返回值、类成员变量。
- 用
{}初始化时,auto 会推导成std::initializer_list。
示例
cpp
auto a = 10; // int
auto b = 3.14; // double
auto& c = a; // int&
auto d{1,2,3}; // initializer_list<int>
3. decltype:获取表达式类型
作用
查询表达式 / 变量的类型,不计算表达式的值,常用于模板中。
和 auto 的区别
auto:根据初始化值推导变量类型;decltype:直接获取表达式本身的类型,不需要初始化。
规则
- 普通变量:
decltype(x)得到 x 的类型; - 加括号
decltype((x)):会推导成引用类型。
示例
cpp
int x = 10;
decltype(x) y = 20; // y是int类型
decltype((x)) z = y; // z是int&类型
4. 空值指针:nullptr(替代 C 语言NULL)
背景
C 语言用#define NULL 0表示空指针,本质是整数 0,会引发函数重载二义性问题。
C++11 引入nullptr
nullptr是空指针字面量 ,类型为std::nullptr_t,专门代表空指针,类型安全。
核心区别(必背)
NULL:是整数0,传参时优先匹配int类型函数;nullptr:只能匹配指针类型,不会和整数混淆,解决重载歧义。
示例
cpp
void fun(int);
void fun(char*);
fun(NULL); // 调用fun(int),不符合预期
fun(nullptr); // 调用fun(char*),正确
5. override 和 final 关键字
① override
- 作用 :强制检查虚函数重写是否合法,编译期校验。
- 规则:子类函数后加
override,编译器检查该函数是否正确重写父类虚函数;函数名、参数、返回值不对,直接报错。 - 意义:避免手写错函数签名,导致隐藏而非重写。
cpp
class Base { virtual void func(); };
class Son : public Base {
void func() override; // 正确重写
// void fun() override; 编译报错:父类没有该虚函数
};
② final
两种用法:
- 修饰虚函数:禁止子类重写该虚函数;
- 修饰类:禁止该类被继承。
cpp
// 用法1:禁止重写
class Base { virtual void func() final; };
// 用法2:禁止继承
class Son final : public Base {};
一句话总结
override = 强制校验重写 ;final = 禁止重写 / 禁止继承。
6. 默认成员函数控制:=default 和 =delete
C++11 可显式控制编译器是否生成默认函数(默认构造、拷贝构造、赋值重载、析构)。
① =default
- 作用:强制让编译器生成默认版本的成员函数,即使你写了其他构造函数。
- 场景:写了有参构造,仍想保留无参默认构造。
cpp
class A {
public:
A() = default; // 强制生成默认无参构造
A(int a);
};
② =delete
- 作用:禁用某个成员函数,调用时直接编译报错。
- 高频场景:禁止拷贝、禁止赋值。
cpp
class A {
public:
A(const A&) = delete; // 禁用拷贝构造,禁止拷贝对象
A& operator=(const A&) = delete; // 禁用赋值重载
};
核心区别
=default:启用默认函数;=delete:禁用函数。
7.新增容器
C++11 新增两类容器:序列式容器 (array、forward_list)、哈希关联式容器 (unordered_*系列)
序列式容器
1. array:静态顺序表
- 本质:固定大小的数组,栈上分配内存,大小编译期确定
- 对比普通数组:
- 支持迭代器、
size()、empty()等容器接口,使用更规范 - 不会退化为指针,类型安全
- 支持迭代器、
- 特点:连续内存、随机访问 O (1)、容量固定不可扩容
- 用法:
array<int,5> arr;(5 个 int 元素)
2. forward_list:单向链表(单向非循环链表)
- 本质:带头结点的单向链表,每个结点只存后继指针
- 对比
list(双向链表):- 内存占用更小,只存一个指针
- 只支持前向遍历,不支持反向迭代
- 插入 / 删除快,但无法访问前驱结点
- 适用:只需要单向遍历、节省内存的场景
哈希桶结构关联式容器(unordered 系列)
包含 4 个:unordered_map、unordered_set、unordered_multimap、unordered_multiset
底层结构
哈希表(数组 + 链表 / 红黑树) ,无序存储,查找平均 O (1)
与传统map/set(红黑树,有序,O (logn))核心区别
- 结构:unordered → 哈希表;map/set → 红黑树
- 有序性 :unordered无序 ;map/set按键有序
- 效率:unordered 查找更快(平均 O (1));map 稳定 O (logn)
- 底层冲突:哈希冲突用链地址法解决
4 个容器区别
unordered_set:键唯一,只存键,无序unordered_multiset:键可重复,只存键unordered_map:键唯一,键值对 <key,value>,无序unordered_multimap:键可重复,键值对
面试必背对比总结
- array vs vector:array 固定大小栈分配;vector 动态扩容堆分配
- forward_list vs list:forward_list 单向、省内存;list 双向、支持反向遍历
- unordered_map vs map:哈希表无序、O (1);红黑树有序、O (logn)
8.C++ 智能指针全解
1. 什么是智能指针
智能指针是封装了普通裸指针的类 ,利用RAII 思想 ,自动管理堆内存的申请与释放,防止内存泄漏,不用手动调用delete。
2. RAII(核心原理)
资源获取即初始化:
- 在构造函数中获取资源(申请内存)
- 在析构函数中释放资源(释放内存)
- 智能指针出作用域自动调用析构,内存自动释放
3. 智能指针通用实现原理
- 封装裸指针,重载
*、->运算符,实现指针的解引用、访问成员 - 依靠 RAII,出作用域自动释放内存
- 根据管理方式分为:独占式、共享式、弱引用式
4. C++98 auto_ptr
实现原理
所有权转移:拷贝 / 赋值时,把资源所有权从原对象转移到新对象,原对象置空。
缺陷
- 拷贝后原指针失效,极易出现空指针问题
- 不能放入容器(容器会频繁拷贝)
- C++11 已废弃,被
unique_ptr替代
5. unique_ptr(独占式智能指针)
实现原理
独占所有权 :一份资源只能被一个unique_ptr管理 ,禁止拷贝,只允许移动(std::move)
缺陷
无法多对象共享同一份资源
特点
开销最小、效率最高,优先使用
6. shared_ptr(共享式智能指针)
实现原理
引用计数机制:
- 维护 2 块内存:管理的资源 + 引用计数
- 拷贝时引用计数 + 1,析构时计数 - 1
- 计数减为 0 时,释放资源
缺陷
循环引用问题 :两个对象互相用shared_ptr指向对方,计数永远不为 0,内存泄漏
解决方式
使用 weak_ptr(弱智能指针):不增加引用计数,打破循环引用
三者一句话总结
unique_ptr:独占,不能拷贝,效率最高shared_ptr:共享,引用计数,会循环引用weak_ptr:配合 shared_ptr,解决循环引用
9.右值引用
1. 什么是右值引用
C++11 引入,用 && 表示,专门绑定右值的引用,用来实现移动语义,避免不必要的深拷贝,提升性能。
2. 右值引用和普通引用(左值引用)的区别
- 左值引用
&:只能绑定左值(有名字、可取地址) - 右值引用
&&:只能绑定右值(临时值、不可取地址) - 简单记:
&绑左值,&&绑右值
3. 右值引用能否引用左值?如何做?
可以 ,通过 std::move() 强制将左值转为右值,让右值引用绑定左值。例:
cpp
int a = 10;
int&& b = move(a); // move把左值a转成右值
4. move 函数
- 作用:强制类型转换 ,将左值无条件转为右值,本身不移动资源,只是转换语义。
- 本质:只是类型转换,实际移动由移动构造 / 移动赋值完成。
5. 左值和右值
- 左值:有变量名、可被取地址、可修改;如普通变量、函数返回的引用。
- 右值 :临时值、没有名字、不可取地址;如字面量
10、表达式a+b、函数临时返回值。
6. 移动语义
转移资源所有权,不拷贝资源。
- 深拷贝:复制一份完整数据,开销大
- 移动语义:直接把原对象的堆内存指针交给新对象,原对象置空,O (1) 开销,大幅提升效率。
7. 移动构造 和 移动赋值
移动构造函数
参数为右值引用,用临时对象的资源构造新对象,转移资源。
移动赋值运算符
参数为右值引用,将临时对象资源转移给当前对象。
编译器默认生成;类内写了拷贝构造 / 赋值,编译器不再默认生成。
8. 完美转发 std::forward
作用
在模板函数 中,保持参数原本的左 / 右值属性,原样转发给下层函数。
move:一律转成右值forward:左值传进来保持左值,右值传进来保持右值
用法
cpp
template<typename T>
void func(T&& t) {
test(forward<T>(t));
}
一句话总结
右值引用绑定临时值;move 转左值为右值;forward 保持属性;移动语义转移资源,代替深拷贝。
10.lambda 表达式
1. lambda 表达式概念
C++11 引入的匿名内联函数,可以直接在代码行定义临时函数,常用于回调、排序、遍历,不用单独定义函数,代码更简洁。
2. lambda 语法
cpp
[捕获列表](参数列表) mutable -> 返回值类型 { 函数体 }
[]:捕获外部变量():函数参数mutable:允许修改值捕获的变量->:显式指定返回值类型,可省略(编译器自动推导)
3. 捕获列表规则(高频考点)
[]:不捕获任何变量[=]:值捕获,所有外部变量拷贝一份,只读,不可修改[&]:引用捕获,所有外部变量按引用捕获,可修改原变量[this]:捕获当前类的 this 指针,访问类成员[=, &a]:混合捕获:默认值捕获,a 用引用捕获- 注意:值捕获的变量默认
const,需加mutable才能修改
4. lambda 和仿函数的区别
仿函数
重载operator()的类,需要显式定义类,可复用,类型可命名。
lambda
编译器自动生成匿名仿函数类 ,一次性使用,不能复用,语法更简洁。简单说:lambda = 语法糖版匿名仿函数。
5. 底层实现原理
编译器遇到 lambda,自动生成一个匿名类:
- 捕获的变量 → 变成匿名类的成员变量
- 函数体 → 重载
operator()运算符 - 引用捕获存引用,值捕获存拷贝值本质:lambda 就是编译器帮你写的仿函数。
一句话总结
lambda 是匿名内联函数 ,底层是自动生成的仿函数;通过捕获列表获取外部变量,支持值捕获 / 引用捕获,简洁灵活。
11.线程库
C++11 引入标准多线程库 ,头文件 <thread>,实现跨平台多线程编程,核心组件如下:
1.std::thread 线程类
- 创建线程 :
thread t(函数/可调用对象, 参数),线程创建后立即开始执行 - join():主线程阻塞,等待子线程执行完毕,回收资源
- detach():线程分离,后台独立运行,主线程不等待
- 注意:一个线程只能
join或detach一次
2.互斥锁(解决线程安全、数据竞争)
- std::mutex :基础互斥锁,手动
lock()/unlock() - std::lock_guard :RAII 自动锁,构造加锁、析构解锁,自动释放,避免死锁
- std::unique_lock:灵活锁,支持手动加解锁、延时加锁、所有权转移,可配合条件变量
3.条件变量 std::condition_variable
- 作用:线程间等待 / 唤醒,实现线程同步(生产者 - 消费者模型)
- 核心函数:
wait():线程阻塞,释放锁,等待被唤醒notify_one():唤醒 1 个等待线程notify_all():唤醒全部等待线程
- 必须配合
unique_lock使用
4.原子类型 std::atomic
- 作用:无锁并发,对基础类型(int、bool)原子操作,避免加锁开销
- 特点:操作不可分割,不会出现指令中断,线程安全
5.future & promise(获取线程返回值)
std::promise:线程内存储结果std::future:主线程获取异步结果,get()阻塞等待std::async:便捷创建异步任务,直接返回 future
6.线程安全问题 & 死锁
死锁产生 4 个必要条件
- 互斥条件
- 占有且等待
- 不可剥夺
- 循环等待
解决:破坏任意一个条件,最常用顺序加锁
一句话总结
thread 创建线程,mutex 保护共享数据,condition_variable 实现线程等待唤醒,atomic 无锁操作,future 获取返回值。