语法糖
nullptr
null在c++中本质是0,在函数重载中和0存在歧义,nullptr解决这个问题
auto
自动推导类型,遍历STL容器很方便,不用声明迭代器。
注意:1.auto声明的变量必须要初始化,否则编译器不能判断变量类型;2.auto不能被声明为返回值,auto不能作为形参,auto不能被修饰为模板参数
auto不影响编译速度,因为编译本来就要右侧推导判断与左侧是否匹配
lambda
lambda表达式是匿名函数,语法规则
[捕获区](参数区){代码区};
捕获的意思即为lambda内部获取变量时的方式,例如,值捕获时拷贝一份,有两份,引用捕获可以改变绑定值,只有一份,具体方式有如下几种
- [a,&b] 其中 a 以复制捕获而 b 以引用捕获。
- [this] 以引用捕获当前对象( *this )
- [&] 以引用捕获所有用于 lambda 体内的自动变量,并以引用捕获当前对象,若存在
- [=] 以复制捕获所有用于 lambda 体内的自动变量,并以引用捕获当前对象,若存在
- [] 不捕获,大部分情况下不捕获就可以了
一般使用场景: sort等自定义比较函数,用thread简单的线程
范围for循坏
for (auto i : it)
初始化列表
T t{123...}
好处
- 方便,且基本上可以替代括号初始化
- 可以使用初始化列表接受任意长度
- 可以防止类型窄化,避免精度丢失的隐式类型转换
- 从浮点类型到整数类型的转换
- 从 long double 到 double 或 float 的转换,以及从 double 到 float 的转换,除非源是常量表达式且不发生溢出
- 从整数类型到浮点类型的转换,除非源是其值能完全存储于目标类型的常量表达式
- 从整数或无作用域枚举类型到不能表示原类型所有值的整数类型的转换,除非源是其值能完全存储于目标类型的常量表达式
右值引用和移动语义
右值引用
C++中的变量要么是左值、要么是右值。通俗的左值定义指的是非临时变量,而右值指的是临时对象。左值引用的符号是一个&,右值引用是两个&&
- 右值引用的出现是为了实现移动语义,顺便解决完美转发的问题,其意义在于扩充了值语义,帮助Modern C++可以全面地应用RAII。
- 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率
- 能够更简洁明确地定义泛型函数
移动语义
std::move()
转移语义可以将资源(堆、系统对象等)从一个对象转移到另一个对象,这样可以减少不必要的临时对象的创建、拷贝及销毁。移动语义与拷贝语义是相对的,可以类比文件的剪切和拷贝。在现有的C++机制中,自定义的类要实现转移语义,需要定义移动构造函数,还可以定义转移赋值操作符。
以string类的移动构造函数为例
MyString(MyString&& str) { std::cout << "Move Ctor source from " << str._data << endl; _len = str._len; _data = str._data; str._len = 0; str._data = NULL;
}
和拷贝构造函数类似,有几点需要注意:
- 参数(右值)的符号必须是&&
- 参数(右值)不可以是常量,因为我们需要修改右值
- 参数(右值)的资源链接和标记必须修改,否则右值的析构函数就会释放资源。转移到新对象的资源也就无效了。
- 标准库函数std::move — 将左值变成一个右值
编译器只对右值引用才能调用移动构造函数,那么如果已知一个命名对象不再被使用,此时仍然想调用它的移动构造函数,也就是把一个左值引用当做右值引用来使用,该怎么做呢?用std::move,这个函数以非常简单的方式将左值引用转换为右值引用。
完美转发
std::forward()
- 如果参数是 左值引用,直接以 左值引用 的形式,转发给下一个函数
- 如果参数是 右值引用,要先 “还原” 为 右值引用 的形式,再转发给下一个函数
智能指针
shared_ptr
基于引用计数的智能指针,会统计当前有多少个对象同时拥有该内部指针;当引用计数降为0时,自动释放
weak_ptr
基于引用计数的智能指针在面对循环引用的问题将无能为力,因此C++11还引入weak_ptr与之配套使用,weak_ptr只引用,不计数
unique_ptr
遵循独占语义的智能指针,在任何时间点,资源智能唯一地被一个unique_ptr所占有,当其离开作用域时自动析构。资源所有权的转移只能通过std::move()而不能通过赋值
多线程编程
线程
std::thread
- 创建std::thread,一般会绑定一个底层的线程。若该thread还绑定好函数对象,则即刻将该函数运行于thread的底层线程。
- 线程相关的很多默认是move语义,因为在常识中线程复制是很奇怪的行为。
joinable()
- 是否可以阻塞至该thread绑定的底层线程运行完毕(倘若该thread没有绑定底层线程等情况,则不可以join)
join()
- 本线程阻塞直至该thread的底层线程运行完毕。
detach()
- 该thread绑定的底层线程分离出来,任该底层线程继续运行(thread失去对该底层线程的控制)
互斥量
std::mutex
- 互斥体,一般搭配锁使用,也可自己锁住自己(lock(),unlock())。
- 若互斥体被第二个锁请求锁住,则第二个锁所在线程被阻塞直至第一个锁解锁。
std::lock_guard
- 简单锁,构造时请求上锁,释放时解锁,性能耗费较低。适用区域的多线程互斥操作。
std::unique_lock
- 更多功能也更灵活的锁,随时可解锁或重新锁上(减少锁的粒度),性能耗费比前者高一点点。适用灵活的区域的多线程互斥操作
原子变量
可以利用原子类可实现无锁设计
std::atomic_flag
- 它是一个原子的布尔类型,可支持两种原子操作。(实际上mutex可用atomic_flag实现)
test_and_set()
- 如果atomic_flag对象被设置,则返回true; 如果atomic_flag对象未被设置,则设置之,返回false
clear()
- 清除atomic_flag对象
std::atomic
- 对int, char, bool等基本数据类型进行原子性封装(其实是特化模板)
store()
- 修改被封装的值
load()
- 读取被封装的值
条件变量
std::condition_variable
条件变量一般是用来实现多个线程的等待队列,即主线程通知(notify)有活干了,则等待队列中的其它线程就会被唤醒,开始干活。
wait(std::unique_lock& lock, Predicate pred = {return true;})
pred()为true时直接返回,pred()为false时,lock必须满足已被当前线程锁定的前提。执行原子地释放锁定,阻塞当前线程,并将其添加到等待*this的线程列表中。
notify_one()/notify_all():激活某个或者所有等待的线程,被激活的线程重新获得锁.