c++11新特性
前言
C++11是C++程序设计语言标准的一个新的版本,在2011年由ISO批准并发布。C++11新标准从而代替了原来的C++98和C++03.。C++11标准是对C++的一次巨大的改进和扩充。在核心语法,STL标准模板等方面增加众多新功能,新亮点。
关键字以及新语法
auto关键字以及用法
auto关键字可以帮助我们分析表达式所属的类型。和以前的一些类型说明符明显不同的是,auto关键字可以让编译器自动分析某个初始值来判断它所属的类型。当然,使用auto关键字必须确定初始值。
1 | vector<int> vec; |
注:auto作为函数返回值时,只能用于定义函数,不能用于声明函数。
decltype关键字以及用法
decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 sizeof 很相似:decltype(表达式)
在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
有时候,我们可能需要计算某个表达式的类型,例如:
1 | auto x = 1; |
另外,decltype还可以配合auto实现拖尾返回类型,加法函数例子如下:
1 | template<typename R, typename T, typename U> |
使用这个模板函数的时候,必须明确指出返回类型R,所以可以使用decltype推到x+y的类型,写出这样的代码:decltype(x+y) add(T x, U y);
但是由于编译器读到 decltype(x+y) 时,x 和 y 尚未被定义,为了解决这个问题,C++11 还引入了一个叫做拖尾返回类型(trailing return type),利用 auto 关键字将返回类型后置:
1 | template<typename T, typename U> |
从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法:
1 | template<typename T, typename U> |
nullptr关键字及用法
在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。C++11 引入了 nullptr 关键字,专门用来区分空指针、0。
nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。
for循环语法
1 | std::vector<int> arr(5, 100); |
STL容器
C++11在STL容器方面也有所增加,给人的感觉就是越来越完整,越来越丰富的感觉,可以让我们在不同场景下能选择跟具合适的容器,提高我们的效率。
std::array
1 | std::array<int, 4> arr= {1,2,3,4}; |
std::forward_list
std::forward_list 是一个列表容器,使用方法和 std::list 基本类似。
和 std::list 的双向链表的实现不同,std::forward_list 使用单向链表进行实现,只能从前往后遍历,提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率,因为forward_list不用保存size以及比list保存更少的节点。
std::tuple
元组的使用有三个核心的函数:
std::make_tuple: 构造元组
std::get: 获得元组某个位置的值
std::tie: 元组拆包
1 | //构造 |
无序容器
C++11 引入了两组无序容器:
std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。
无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant)。
智能指针
点击原文阅读。
其他
std::bind
std::bind函数是一个函数适配器,是用来绑定函数调用的某些参数的。std::bind它可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体。它绑定的参数的个数不受限制,绑定的具体哪些参数也不受限制,由用户指定,要么被绑定到placeholders(占位符,如_1, _2, …, _n)。
1 | //绑定一个成员函数 |
总结:std::bind绑定了部分参数,如例子中,绑定了参数100,最后生成的新的可调用对象(newFunc)的参数列表只剩下一个参数(std::placeholders::_1)。
std::function
类模版std::function
是一种通用、多态的函数封装。通过std::function
对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,**形成一个新的可调用的std::function
对象**;让我们不再纠结那么多的可调用实体。一切变的简单粗暴。
语法:std::function<函数返回值(形参1,形参2,……)>
std::function可以封装以下几种函数:
- 普通函数
- lambda表达式
- 函数指针
- 仿函数(functor 重载括号运算符实现)
- 类成员函数
- 静态成员函数
1 | //普通函数 |
下面通过一个例子说明std::function
与std::bind
是怎么实现函数回调功能的。
1 |
|
std::move
https://www.cnblogs.com/keepsimple/p/3250245.html
std::transform
作用:大小写转换、两个数组元素分别相加
https://www.jianshu.com/p/cbe722ca4276
std::future
std::condition_variable
条件变量(condition variable)是利用共享的变量进行线程之间同步的一种机制,当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。典型的场景包括生产者-消费者模型,线程池实现等。
主要成员函数如下:
1 | 通知 |
为了防止竞争,条件变量总是和一个互斥锁结合一起使用,如std::unique_lock<std::mutex>
wait()函数可依次拆分为三个操作:释放互斥锁、等待在条件变量上、再次获取互斥锁,即wait函数都在会阻塞时,自动释放锁权限,相当于调用unique_lock的成员函数unlock(),以便其他线程能有机会获得锁。这就是条件变量只能和unique_lock一起使用的原因,否则当前线程一直占有锁,线程被阻塞。
什么是虚假唤醒?
举个例子,我们现在有一个生产者-消费者队列和三个线程。
1) 1号线程从队列中获取了一个元素,此时队列变为空。
2) 2号线程也想从队列中获取一个元素,但此时队列为空,2号线程便只能进入阻塞(cond.wait()),等待队列非空。
3) 这时,3号线程将一个元素入队,并调用cond.notify()唤醒条件变量。
4) 处于等待状态的2号线程接收到3号线程的唤醒信号,便准备解除阻塞状态,执行接下来的任务(获取队列中的元素)。
5) 然而可能出现这样的情况:当2号线程准备获得队列的锁,去获取队列中的元素时,此时1号线程刚好执行完之前的元素操作,返回再去请求队列中的元素,1号线程便获得队列的锁,检查到队列非空,就获取到了3号线程刚刚入队的元素,然后释放队列锁。
6) 等到2号线程获得队列锁,判断发现队列仍为空,1号线程“偷走了”这个元素,所以对于2号线程而言,这次唤醒就是“虚假”的,它需要再次等待队列非空。
总结:虚假唤醒就是当一个正在等待条件变量的线程由于条件变量被触发而唤醒时,却发现它等待的条件(共享数据)没有满足(也就是没有共享数据)。
条件变量正确使用方式如下:
1 | //全局变量 |
std::vector的push_back与emplace_back区别
在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程(优先会使用移动构造,没有移动构造才会使用拷贝构造),所以emplace_back的效率更高。