1、智能指针(RAII 自动资源管理)
类型 | 特点 | 创建方式 |
---|---|---|
unique_ptr |
独占所有权,不能复制,只能移动。适合独享资源(如文件、连接) | make_unique<T>(args...) |
shared_ptr |
多个指针共享所有权,引用计数为 0 时自动释放资源 | make_shared<T>(args...) |
weak_ptr |
弱引用,不增加引用计数,配合 shared_ptr 解决循环引用问题 |
由 shared_ptr 构造 |
cpp
std::unique_ptr<int> ptr1 = std::make_unique<int>(10); // 独占
std::shared_ptr<int> ptr2 = std::make_shared<int>(20); // 引用计数
std::weak_ptr<int> weak = ptr2; // 弱引用
if (auto spt = weak.lock()) { cout << *spt; } // 判断是否有效
2、Lambda 表达式(匿名函数)
语法结构: [捕获列表](参数列表) mutable -> 返回类型 {函数体}
捕获方式 | 含义 |
---|---|
[] |
不捕获任何变量 |
[=] |
按值捕获所有变量 |
[&] |
按引用捕获所有变量 |
[x] / [&x] |
按值/按引用捕获单个变量 |
[=, &x] |
值捕获其余,引用捕获 x |
[this] |
捕获当前对象指针 |
cpp
int a = 10;
auto f = [=]() { return a + 1; }; // 外部 a 不变
auto g = [&]() { a += 2; }; // 外部 a 改变
auto h = [a]() mutable { a += 5; }; // 外部 a 不变
用途:排序、自定义操作、回调、线程函数、STL 算法等。
3、mutable 关键字(突破 const)
应用位置 | 作用说明 |
---|---|
类成员 | 允许在 const 成员函数中修改此变量 |
lambda | 捕获按值的变量可在函数体中被修改 |
cpp
class A {
mutable int count = 0;
void f() const { count++; }
};
int a = 0;
auto h = [a]() mutable { a += 5; }; // 外部 a 不变
4、左值与右值 & std::move & std::forward
(1)左值和右值
类型 | 特点 | 示例 |
---|---|---|
左值 | · 具有地址,存储在内存中; · 可以出现在 ' = ' 左侧; · 可以取地址; · 变量、对象、数组元素都是左值; | int a = 1; // 变量是左值 a = 10; // 可以出现在 ' = ' 左侧 int *b = &a; // 可以取地址 |
右值 | · 通常没有地址,存储在寄存器或临时内存中 · 不可以出现在 ' = ' 左侧; · 不能取地址(右值可以绑定到const左值引用); · 字面量、表达式计算结果都是右值; | int x = 1 + 2; // (1 + 2) 为右值 int *p = &(1+2); // ❌ 右值不能取地址 |
(2)左值引用
cpp
int a = 1; // a是左值
int& c = a; // 左值引用
int& d = 1+2; // ❌ 右值不能绑定到左值引用
const int& y2 = 1 + 2; // 绑定到const 左值引用
(3)右值引用 & 移动语义
**std::move的作用:**将左值转换为右值,以触发移动语义,不会真正 ' 移动 ' 数据,只是改变对象的属性;用于触发移动构造和移动赋值,避免深拷贝,提高性能。
cpp
int&& a= 1; // 右值引用
int b = 10;
int&& c= std::move(b); // 将左值x用std::move转换为右值,绑定到c
std::string s1 = "abc";
std::string s2 = std::move(s1); // 触发移动构造,避免深拷贝
(4)完美转发
**std::forward的作用:**泛型函数在传递参数时,无法保留参数的左值/右值属性;导致额外的拷贝或移动构造;使用 std::forward 可避免这种情况,实现高效传参。
在模板函数中,将参数以其原始类型(左值/右值)传递给目标函数,称为完美转发。
-
依赖 C++11 的 万能引用(T&&);
-
通过 std::forward<T>(arg) 实现。
cpp
void target(int& x) { cout << "lvalue: " << x << endl; }
void target(int&& x){ cout << "rvalue: " << x << endl; }
template<typename T>
void wrapper(T&& v) {
target(std::forward<T>(v));
}
int main(){
int a = 5;
wrapper(a); // 调用 lvalue 版本
wrapper(10); // 调用 rvalue 版本
}
5、类型转换
类型 | 用途与说明 |
---|---|
static_cast<T> |
编译期安全转换,适用于基本类型转换和上行转换(派生类到基类)。 |
dynamic_cast<T> |
运行时安全下行转换(基类到派生类),依赖 RTTI,基类需包含虚函数。 |
const_cast<T> |
去除 const 限定,但源对象不能是 const 。 |
reinterpret_cast<T> |
强制类型转换,转换原生内存表示,用于指针和整数之间转换。 |
(1)static_cast静态转换
基本类型转换 和上行转换(派生类 --->基类)
cpp
/*基本数据类型转换*/
double d = 3.14;
int i = static_cast<int>(d);
cout << i << endl;
/*向上转换: 派生类 ----> 基类*/
class Base{
public:
int a;
Base() {}
Base(int val) : a(val){}
virtual ~Base() = default;
};
class Derived : public Base{
public:
int b = 100;
Derived(){};
Derived(int val) : b(val){
this->a = b;
}
};
// 向上转换: Derived --> Base
Derived derived(10);
Base* base = static_cast<Base*>(&derived);
cout << base->a << endl;
// 向下转换: Base --> Derived
// 使用static_cast<T>是❌的
Base base1(10);
Derived* derived1 = static_cast<Derived*>(&base1);
// 向下转换时,derived1指向的数据不包含Derived的特有部分,后续使用derived1可能会导致崩溃或错误
cout << derived1->b << endl;
(2)dynamic_cast动态转换
向下转换: 基类 ---> 派生类,注意:基类必须要有虚函数,否者dynamic_cast无法执行正确的运行时检查
cpp
class Base{
public:
virtual ~Base() = default; // 必须有虚函数
};
class Derived : public Base{
public:
void hello(){
cout << "hello!" << endl;
}
};
int main(){
// 向下转换: Base --> Derived
Base *base = new Derived();
Derived* derived = dynamic_cast<Derived*>(base);
if(derived){
derived->hello(); // 转换成功
}else{
cout << "conversion failed!" << endl;
}
delete derived;
return 0;
}
(3)const_cast
修改可变数据(去除const限定符):如果对象本身不是const,通过const_cast去除指针的const属性是运行的
cpp
void modify(const char* str){
// 形参str被const限定,不能修改
// str[0] = 'H';
// const_cast修饰字符指针是安全的
char* p = const_cast<char*>(str);
p[0] = 'H';
}
int main(){
// 字符串字面量即 "hello" 在 C++ 中是 const char[] 类型,即只读的
// 试图强转去掉 const,但如果底层的内存本身是只读的(如放在代码段的只读区),
// 就会导致运行时崩溃 或 未定义行为。
// const char* str = "hello";
// 想修改字符串内容,应使用可修改的数组
char str[] = "hello";
modify(str);
cout << str << endl;
// a本身是const
const int a = 10;
// 通过const_cast将地址转换为int*是未定义行为
int* b = const_cast<int*>(&a);
*b = 20; // 尝试修改只读数据
cout << a << endl; // 不会修改
return 0;
}
(4)reinterpret_cast重解释转换
指针与整数之间转换:
cpp
//指针与整数之间转换:
int a = 10;
// 将指针转换为整数
uintptr_t addr_a = reinterpret_cast<uintptr_t>(&a);
// 再转换回指针
int* ptr_a = reinterpret_cast<int*>(addr_a);
cout << *ptr_a << endl;
// ❌写法,不同类型指针互相转换
double d = 10;
// 将double指针转换为int指针
int* addr_d = reinterpret_cast<int*>(&d);
cout << *addr_d << endl; // 输出是不正确的
6、auto
& decltype
(1)auto
用于自动推断变量类型,必须要给值:
注意:const类型变量推断的时候还是要加const;
cpp
auto x = 3.14;
auto y = 1;
auto z = 'z';
vector<int> vec{1,2,3,4,5};
for(auto it = vec.begin(); it != vec.end(); ++it){
cout << *it << " ";
}
cout << endl;
for(auto &v : vec){
cout << v << " ";
}
const int b = 10;
const auto a = b;
(2)decltype
用于获取表达式的类型,不同与auto,不会立即对变量求值
后置返回类型:不知道函数返回值类型的场景,就可以使用后置返回类型
cpp
decltype(x) y; // double
template<typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b){
return a + b;
}
int main(){
cout << add(10, 10) << endl;
return 0;
}
(3)auto
和decltype配合使用场景
cpp
// 下面的场景,通过改变n来改变q是不行的
int q = 10;
int& p = q;
auto n = p;
cout << q << endl; // 10
n = 10;
cout << q << endl; // 10
// 通过用decltype来修饰auto,可以实现上述场景
int q = 10;
int& p = q;
decltype(auto) n = p;
cout << q << endl; // 10
n = 20;
cout << q << endl; // 20
7. std::function
(通用函数封装器)
std::function 是 C++11 引入的通用函数封装器,可以封装任意可调用对象,如 lambda、函数指针、成员函数、bind
表达式。
(1)基本用法
cpp
#include <functional>
#include <iostream>
using namespace std;
class A {
public:
void show(const string& msg) const {cout << "Msg: " << msg << endl;}
};
void hello(string s) { cout << "Hello " << s << endl; }
// 统一回调接口
void runCallback(const function<void(int)>& callback) {
callback(100);
}
int main(){
// 普通函数
function<void(string)> f1 = hello;
f1("World");
// Lambda表达式
function<int(int,int)> f2 = [](int a,int b){ return a+b; };
cout << f2(2,3) << endl;
// 成员函数绑定
A a;
auto f3 = std::bind(&A::show, &a, placeholders::_1);
f3(42);
// 回调函数绑定
runCallback([](int val) {
cout << "Callback with: " << val << endl;
});
}
(2)什么场景下用std::function
?
必须用 std::function 的场合 |
原因 |
---|---|
✅封装 lambda(捕获变量) | 函数指针不能表示带捕获的 lambda |
✅封装成员函数 | 需要对象上下文,函数指针无法存储 |
✅封装 bind / 仿函数 |
bind 生成的是复杂类型,函数指针无法兼容 |
✅存储多种函数类型 | 函数指针类型单一,不支持统一封装 |
✅参数回调统一接口 | 使用 std::function 可以实现统一的函数调用方式 |
a、捕获变量的 Lambda
cpp
int x = 10;
auto lambda = [x](int y) { return x + y; };
// std::function 可以封装捕获 lambda
std::function<int(int)> f = lambda;
cout << f(5) << endl; // 输出 15
// ❌ 函数指针不行:lambda 有捕获,不能转换成函数指针
b、成员函数绑定 + 参数绑定
cpp
class A {
public:
void show(const string& msg) const {
cout << "Msg: " << msg << endl;
}
};
int main(){
A a;
auto func = std::bind(&A::show, &a, std::placeholders::_1);
// std::function 封装 bind 后的结果
std::function<void(const string&)> f = func;
f("Hello");
// ❌ 函数指针无法绑定成员函数 + 对象一起
}
c、函数容器
cpp
vector<function<void(int)>> funcs;
funcs.push_back([](int x) { cout << "Lambda: " << x << endl; });
funcs.push_back([](int x) { cout << "Square: " << x * x << endl; });
// 调用全部
for (auto& f : funcs) {
f(3);
}
// ❌ 函数指针不支持放在容器里调用不同类型的函数。
8. nullptr
类型安全空指针
优势 | 说明 |
---|---|
✅ 类型安全 | nullptr 是一个真正的指针类型,避免与整数混淆 |
✅ 函数重载清晰 | 区分 int 和 void* 参数,解决函数重载歧义 |
✅ 更清晰的语义表达 | 明确表示"空指针",比 NULL 或 0 更直观 |
✅ 支持模板和泛型编程 | nullptr 在模板中不会引起类型推导错误 |
cpp
void f(int) { cout<<"int\n"; }
void f(char*){ cout<<"ptr\n"; }
int main(){
f(nullptr); // 调用 ptr 版本
f(NULL); // 输出 "int",因为 NULL 被视为 0
}