C++11 新增关键字总结与归纳
一、auto
关键字(自动类型推导)
-
用途:
由编译器自动推断变量类型,简化代码编写。 -
语法:
auto 变量名 = 初始值; // 类型由初始值推导
-
示例:
auto x = 42; // x 推导为 int auto it = map.begin(); // it 推导为迭代器类型
-
限制:
-
不能用于函数参数。
-
不能定义类的非静态成员变量。
-
不能定义数组(如
auto arr[] = {1, 2, 3};
非法)。 -
不能用于模板参数(如
Test<auto> C2;
非法)。
-
二、nullptr
关键字(空指针常量)
-
用途:
明确表示空指针,解决NULL
(可能被定义为0
或(void*)0
)的歧义。 -
示例:
void func(int* ptr); func(nullptr); // 明确调用指针版本的重载函数Test test; test.TestWork(NULL); // 可能调用 int 版本(若 NULL 定义为 0) test.TestWork(nullptr); // 明确调用 int* 版本
-
优势:
-
类型安全,避免重载函数时的歧义。
-
三、统一初始化语法(初始化列表)
-
用途:
支持使用花括号{}
初始化容器、类成员、数组等,使代码更简洁。 -
示例:
int arr[]{1, 2, 3}; // 数组初始化 vector<int> v{1, 2, 3}; // vector 初始化 map<string, int> m{{"c1", 28}, {"c2", 31}}; // map 初始化 class Test {int num{10}; // 类成员初始化 };
-
特点:
-
适用于所有标准容器和自定义类型(需支持
std::initializer_list
)。
-
四、基于范围的 for
循环
-
用途:
简化遍历数组、容器、字符串等序列的操作。 -
语法:
for (auto& 元素 : 容器) { /* 操作 */ }
-
示例:
map<string, float> fruits{{"Apple", 8.2}, {"Banana", 3.7}}; for (auto& fruit : fruits) {cout << fruit.first << " : " << fruit.second << endl; }
-
注意:
-
容器需支持迭代器(即提供
begin()
和end()
方法)。
-
五、static_assert
(编译期断言)
-
用途:
在编译阶段检查条件,若不满足则立即报错。 -
语法:
static_assert(常量表达式, "错误信息");
-
示例:
static_assert(sizeof(int*) == 8, "64 位系统不支持!");
-
特点:
-
条件必须是编译期可计算的常量表达式。
-
适用于检查平台特性、类型大小等。
-
关键对比与注意事项
关键字/特性 | 核心作用 | 典型场景 | 注意事项 |
---|---|---|---|
auto | 自动类型推导 | 简化迭代器、复杂类型声明 | 不可用于函数参数或非静态成员变量 |
nullptr | 明确表示空指针 | 重载函数中区分指针与整型 | 替代 NULL ,提升类型安全 |
统一初始化语法 | 统一容器、类成员的初始化方式 | 初始化列表、类成员默认值 | 需容器支持 initializer_list |
范围 for | 简化遍历操作 | 遍历数组、容器、字符串 | 容器需提供迭代器接口 |
static_assert | 编译期检查条件 | 验证类型大小、平台特性 | 条件必须是编译期常量表达式 |
左值和右值总结与归纳
一、左值(lvalue)
-
定义:
可出现在赋值语句左侧的表达式,表示对象的身份(内存位置)。 -
特性:
-
可寻址:可通过
&
获取地址。 -
可修改(除非被
const
限定)。
-
-
示例:
int x = 5; // x 是左值 x = 10; // x 可被赋值 int* p = &x; // 获取 x 的地址
二、右值(rvalue)
-
定义:
不能出现在赋值语句左侧的表达式,通常是临时值或字面量。 -
分类:
-
纯右值(prvalue):如字面量
42
、函数返回的临时对象。 -
将亡值(xvalue):即将被销毁但可通过移动语义转移资源的对象(如
std::move
的返回值)。
-
-
示例:
int func() { return 42; } // 返回的 42 是右值 int x = func(); // 右值赋给左值 x int&& rr = 42; // 右值引用绑定字面量
三、右值引用(Rvalue Reference)
-
语法:
类型&& 变量名 = 右值;
-
特性:
-
必须初始化,且通常只能绑定右值。
-
允许修改绑定的右值(延长其生命周期)。
-
-
示例:
int&& num2 = 10; // 右值引用绑定字面量 num2++; // 允许修改(输出 num2 = 11)
四、移动语义(Move Semantics)
-
目的:
避免不必要的深拷贝,通过转移资源提升性能。 -
实现方式:
-
移动构造函数:接受右值引用参数,直接“窃取”资源。
-
移动赋值运算符:类似逻辑。
-
-
示例:
// 移动构造函数 String::String(String&& s) {this->str = s.str; // 转移资源s.str = nullptr; // 置空源对象指针 }
-
运行结果对比:
-
无移动语义:触发深拷贝,频繁分配/释放内存。
-
有移动语义:直接转移资源,减少拷贝开销。
-
五、std::move
函数
-
作用:
将左值强制转换为右值引用,触发移动语义。 -
注意:
-
转换后的对象可能处于“有效但未定义”状态(如指针置空)。
-
适用于生命周期即将结束的对象。
-
-
示例:
String s1("putty"); String s2(std::move(s1)); // 强制转为右值,调用移动构造函数
六、关键总结
概念 | 特点 | 应用场景 |
---|---|---|
左值 | 可寻址、可修改、有持久性 | 变量、对象实例、赋值操作左侧 |
右值 | 临时性、不可寻址、不可直接修改 | 字面量、函数返回的临时对象 |
右值引用 | 绑定右值,允许修改右值,延长生命周期 | 移动语义、完美转发 |
移动语义 | 通过转移资源避免拷贝,提升性能 | 大型对象传递、资源管理类(如容器) |
std::move | 将左值转为右值引用,强制触发移动语义 | 优化资源转移、避免冗余拷贝 |
七、注意事项
-
移动后的对象状态:
使用std::move
后,原对象资源可能被转移,需谨慎操作。 -
编译器优化:
返回值优化(RVO)可能跳过拷贝/移动构造函数,需通过-fno-elide-constructors
禁用优化观察行为。 -
代码健壮性:
实现移动语义时,需确保源对象处于安全状态(如指针置空)。
C++ Lambda 表达式总结与归纳
一、基本语法
Lambda 表达式的基本语法如下:
[捕获列表](参数列表) mutable -> 返回类型 { 函数体 };
-
捕获列表(Capture List):指定外部变量如何被捕获(按值
[x]
或按引用[&x]
)。 -
参数列表(Parameters):与普通函数参数列表一致。
-
mutable:允许修改按值捕获的变量(默认按值捕获的变量是
const
)。 -
返回类型:可省略,编译器根据
return
语句自动推导。 -
函数体:实现 Lambda 的逻辑。
二、捕获方式
Lambda 表达式通过捕获列表访问外部变量,捕获方式分为以下几类:
捕获形式 | 说明 |
---|---|
[] | 不捕获任何外部变量。 |
[x, &y] | 按值捕获 x ,按引用捕获 y 。 |
[=] | 按值捕获所有外部变量(隐式值捕获)。 |
[&] | 按引用捕获所有外部变量(隐式引用捕获)。 |
[=, &x] | 按引用捕获 x ,其余变量按值捕获。 |
[&, x] | 按值捕获 x ,其余变量按引用捕获。 |
[this] | 捕获当前类的 this 指针。 |
三、核心特性与示例
-
值捕获与引用捕获
-
值捕获:外部变量的值在 Lambda 创建时拷贝,后续修改不影响 Lambda 内部值。
int a = 100; auto lambda = [a] { return a + 10; }; a = 200; // lambda 内部 a 仍为 100
-
引用捕获:Lambda 内部直接操作外部变量的引用。
int a = 100; auto lambda = [&a] { a += 10; }; lambda(); // a 变为 110
-
-
mutable
关键字
允许修改按值捕获的变量(默认不可修改):int x = 10; auto lambda = [x]() mutable { x++; }; // 合法
-
省略格式
-
省略返回类型:编译器根据
return
语句推导。auto lambda = [](int a, int b) { return a + b; };
-
省略参数列表:无参 Lambda。
auto lambda = [] { cout << "Hello"; };
-
-
在 STL 算法中的应用
vector<int> vec = {1, 2, 3, 4, 5}; // 遍历并打印元素 for_each(vec.begin(), vec.end(), [](int x) { cout << x << " "; }); // 查找第一个大于 3 的元素 auto it = find_if(vec.begin(), vec.end(), [](int x) { return x > 3; });
四、隐式捕获与显式捕获
-
隐式捕获
-
[=]
:按值捕获所有外部变量。 -
[&]
:按引用捕获所有外部变量。
int x = 10, y = 20; auto lambda1 = [=] { return x + y; }; // 按值捕获 x 和 y auto lambda2 = [&] { x++; y++; }; // 按引用捕获 x 和 y
-
-
混合捕获
int x = 10, y = 20; auto lambda = [=, &x] { y++; }; // x 按引用捕获,y 按值捕获
五、注意事项
-
生命周期问题:
按引用捕获的变量需确保在 Lambda 执行时仍有效。 -
mutable
的局限:
仅允许修改按值捕获的变量,不影响外部原始变量。 -
性能优化:
避免过度使用[&]
或[=]
,显式指定捕获变量更安全高效。
总结对比表
特性 | 值捕获 | 引用捕获 |
---|---|---|
语法 | [x] | [&x] |
修改外部变量 | 不可修改(需 mutable ) | 可直接修改 |
生命周期影响 | 安全(拷贝值) | 需确保变量存活 |
性能 | 可能产生拷贝开销 | 无拷贝开销 |
C++ 可调用对象与 std::function
、std::bind
总结与归纳
一、可调用对象(Callable Objects)
可调用对象是指可以像函数一样被调用的实体,包括以下类型:
类型 | 定义与示例 | 调用方式 |
---|---|---|
普通函数 | 传统定义的函数。 | add(10, 20); |
成员函数 | 类的成员函数,需通过对象调用。 | Calc calc; calc.add(10, 20); |
Lambda 表达式 | 匿名函数,可捕获外部变量。 | auto lambda = [](int a, int b) { return a + b; }; |
函数对象(仿函数) | 重载了 operator() 的类对象。 | Add adder; adder(10, 20); |
成员函数指针 | 指向类成员函数的指针,需结合对象调用。 | int (Calc::*funcPtr)(int, int) = &Calc::add; (calc.*funcPtr)(10, 20); |
可转换函数指针的类 | 类通过转换运算符返回函数指针,使对象可被直接调用。 | Calc calc; calc(10, 20); |
二、std::function
包装器
std::function
是一个通用的可调用对象包装器,可以存储、复制和调用任何可调用对象(除类成员函数指针外),提供统一的调用接口。
语法与用法
#include <functional>
std::function<返回类型(参数列表)> 变量名;
示例:
// 包装普通函数
std::function<int(int, int)> func1 = add;
cout << func1(10, 20); // 输出 30// 包装 Lambda 表达式
auto lambda = [](int a, int b) { return a + b; };
std::function<int(int, int)> func2 = lambda;// 包装函数对象
Add adder;
std::function<int(int, int)> func3 = adder;// 包装绑定后的成员函数(需结合 std::bind)
Calc calc;
auto bound_func = std::bind(&Calc::add, &calc, std::placeholders::_1, std::placeholders::_2);
std::function<int(int, int)> func4 = bound_func;
特点
-
类型擦除:隐藏具体类型,统一调用接口。
-
兼容性:支持普通函数、Lambda、函数对象等(成员函数需配合
std::bind
)。 -
延迟执行:可存储可调用对象,在需要时调用。
三、std::bind
绑定器
std::bind
用于将可调用对象与其参数绑定,生成新的可调用对象。主要功能包括:
-
参数绑定:固定部分参数,生成参数更少的可调用对象。
-
参数重排序:通过占位符
_1, _2...
调整参数顺序。
语法与用法
#include <functional>
auto 新可调用对象 = std::bind(原可调用对象, 绑定参数列表);
示例:
// 绑定普通函数的部分参数
auto bound_add = std::bind(add, 10, std::placeholders::_1);
cout << bound_add(20); // 输出 30(等效于 add(10, 20))// 绑定成员函数
Calc calc;
auto bound_member = std::bind(&Calc::add, &calc, std::placeholders::_1, std::placeholders::_2);
cout << bound_member(10, 20); // 输出 30// 绑定 Lambda 表达式
auto lambda = [](int a, int b) { return a + b; };
auto bound_lambda = std::bind(lambda, std::placeholders::_2, std::placeholders::_1);
cout << bound_lambda(20, 10); // 输出 30(参数顺序交换)
占位符
-
std::placeholders::_1
:表示新可调用对象的第一个参数。 -
std::placeholders::_2
:表示第二个参数,依此类推。
四、关键对比与注意事项
特性 | std::function | std::bind |
---|---|---|
核心功能 | 统一存储和调用各类可调用对象。 | 绑定参数或调整参数顺序,生成新可调用对象。 |
支持类型 | 普通函数、Lambda、函数对象、绑定后的成员函数等。 | 所有可调用对象,包括成员函数。 |
典型场景 | 回调函数、事件处理、通用接口封装。 | 参数适配、延迟执行、函数组合。 |
注意事项 | 无法直接包装成员函数指针,需结合 std::bind 。 | 绑定对象的生命周期需确保有效(避免悬空指针)。 |
五、应用场景与最佳实践
-
回调机制:
使用std::function
存储回调函数,支持动态替换。std::function<void(int)> callback; callback = [](int x) { cout << "Callback: " << x; }; callback(42); // 输出 Callback: 42V
-
参数适配:
通过std::bind
适配接口参数。void log(int level, const string& msg); auto warn_log = std::bind(log, 2, std::placeholders::_1); warn_log("Disk full!"); // 等效于 log(2, "Disk full!")
-
成员函数封装:
将成员函数绑定到对象,生成可调用对象。class Timer { public:void start(int interval) { /* ... */ } }; Timer timer; auto start_timer = std::bind(&Timer::start, &timer, 1000); start_timer(); // 调用 timer.start(1000)
-
资源管理:
确保std::bind
绑定的对象在调用时仍有效(如使用智能指针)。
C++ 智能指针总结与归纳
一、智能指针概述
智能指针是用于管理动态分配内存的类模板,通过自动释放内存减少内存泄漏和悬空指针风险。C++11 支持以下类型:
-
unique_ptr
:独占所有权,不可拷贝,可移动。 -
shared_ptr
:共享所有权,基于引用计数。 -
weak_ptr
:弱引用,不增加引用计数,解决shared_ptr
循环引用问题。 -
auto_ptr
(已废弃):所有权转移语义,存在潜在问题。
二、unique_ptr
(独占指针)
-
特点
-
资源唯一归属,离开作用域自动释放。
-
禁止拷贝,允许移动(通过
std::move
)。
-
-
示例
unique_ptr<String> smart1(new String("hello")); unique_ptr<String> smart2 = move(smart1); // 合法,转移所有权 // smart1 变为空,smart2 持有资源
-
注意事项
-
禁止赋值或拷贝:
smart1 = smart2
或unique_ptr<String> smart3(smart1)
会编译错误。 -
优先用于单一所有权场景(如工厂模式返回对象)。
-
三、shared_ptr
(共享指针)
-
特点
-
允许多个指针共享同一对象,通过引用计数管理生命周期。
-
引用计数为 0 时释放资源。
-
-
核心操作
shared_ptr<String> s1(new String("hello")); shared_ptr<String> s2 = s1; // 引用计数+1 cout << s1.use_count(); // 输出 2
-
循环引用问题
-
场景:两个对象互相持有对方的
shared_ptr
,导致引用计数无法归零。 -
解决方案:将其中一个指针改为
weak_ptr
。
class AA {shared_ptr<BB> m_bb_ptr; // 错误:导致循环引用weak_ptr<BB> m_bb_ptr; // 正确:使用 weak_ptr };
-
四、weak_ptr
(弱指针)
-
特点
-
不增加引用计数,不控制对象生命周期。
-
需通过
lock()
转换为shared_ptr
访问资源。
-
-
示例
shared_ptr<String> s_ptr(new String("hello")); weak_ptr<String> w_ptr = s_ptr; if (!w_ptr.expired()) {auto ptr = w_ptr.lock(); // 转换为 shared_ptrptr->show(); }
-
应用场景
-
解决
shared_ptr
循环引用。 -
缓存或观察者模式中避免资源滞留。
-
五、auto_ptr
(已废弃)
-
问题
-
所有权转移时原指针置空,易导致悬空指针。
auto_ptr<String> smart1(new String("hello")); auto_ptr<String> smart2 = smart1; // smart1 变为空 smart1->show(); // 运行时错误!
-
-
替代方案:使用
unique_ptr
。
六、关键对比与选择建议
类型 | 所有权模型 | 拷贝/移动 | 适用场景 |
---|---|---|---|
unique_ptr | 独占 | 仅移动(std::move ) | 单一所有者,资源明确归属。 |
shared_ptr | 共享 | 允许拷贝 | 多所有者,需共享资源。 |
weak_ptr | 弱引用(不拥有) | 需转换为 shared_ptr | 解决循环引用或观察者模式。 |
七、注意事项
-
避免裸指针初始化多个
shared_ptr
String* str = new String("hello"); shared_ptr<String> s1(str); shared_ptr<String> s2(str); // 错误:重复释放!
-
优先使用
make_shared
auto s_ptr = make_shared<String>("hello"); // 更高效且安全
-
循环引用检测:使用
weak_ptr
替代shared_ptr
成员变量。
总结
-
unique_ptr
:轻量级,适用于单一所有权场景。 -
shared_ptr
:灵活共享资源,需注意循环引用。 -
weak_ptr
:辅助shared_ptr
,解决循环引用问题。 -
避免使用
auto_ptr
,优先选择现代智能指针。
- 这是本人的学习笔记不是获利的工具,小作者会一直写下去,希望大家能多多监督我
- 文章会每攒够两篇进行更新发布
- 感谢各位的阅读希望我的文章会对诸君有所帮助