C++11特性
都什么年代了,还在整C++11?八股害人啊
一:C++11的“新”特性
nullptr
一个新关键字,用于表示指针指向no value,可以被自动转化为各种指针类型,但是不会被转化为整数
比NULL好,因为NULL的本质就是整数0
auto
通过auto声明变量、对象,可以自动推到其类型,在处理表达式时有奇效
auto l = [](int x) -> {...}; |
一致性初始化
一致性初始化(Uniform Initiallization),简单来说就是可以用大括号来做初始化
int v[] {1, 2, 3}; |
但是这个操作不支持窄化(narrowing),即
int x = 5.3; //x == 5 |
新的for循环
for(auto& item: lists){...} |
左值右值
左值
左值:占用一定内存,具有可辨认身份(可以被取地址、引用)的对象(表达式结束后仍然存在的持久对象),C++中绝大多数变量都是左值
int i = 1; // i 是左值 |
右值
右值:左值以外的所有对象,分为亡值(xvalue, eXpiring Value,任务完成后就会被销毁)和纯右值(prvalue,Pure Rvalue,在表达式结束后就会被销毁)
亡值尽管也很快销毁,但是在生命周期内也是具有可辨识身份的,因此属于广义左值
int i = 2; // 2 这种字面量,是纯右值 |
不能对右值赋值
i + 2 = 4; // 错误,不能对右值赋值 |
不能对右值取地址
int *p = &(i + 2); // 错误,不能对右值取地址 |
int square1(int& a) {return a * a;} |
常量左值引用
为什么可以const int &a = 2;
呢?这里一个常量左值引用(a)被绑定到了一个右值(2)上,这导致右值2
的生命周期变得和a
相同
const int &a = 2; // 可以 |
转移语意
转移语意(move semantic),本质是所有权的转移,将即将销毁的对象转移给他人,避免非必要的拷贝和临时对象
比如一个函数定义为
void fun(const T& v){...} |
当我们调用时
T t; |
我们发现,第一种调用时,直接传了引用,没有拷贝和临时变量,而第二种,实际上是
T temp = t+1; //T temp(t+1)或者 T temp.T(t+1) |
总之是调用了拷贝构造函数和生成了临时变量,这不是我们想要的,于是引入了左右值和转移语意的概念,然后就诞生了一种新的调用方式:
fun(std::move(t+1)); |
std::move
的作用是将其参数t+1
变成一个右值引用(rvalue reference),是一个T&&
的类型
一旦一个类型被“标记”为右值,就意味着这是一个临时对象,你可以随意使用它的内容和资源
然后我们可以优化一下这个函数的定义
class T{ |
右值被move以后,就变成有效但不确定的状态
字符串字面量
Raw String Literal
以R开头的字符串,可以换行,不需要转义,用于正则表示式有奇效
R"(\\n)"; //等于 "\\\\n" |
Encoded String Literal
用于国际化
noexcept
让函数无法抛出异常,遇到未定义事件会直接abort
,这相当于假定了这个函数是绝对不会出现异常的,标准库中大量函数均有这种要求
noexcept后面可以跟一个bool条件,为true时就不抛异常
void fun() noexcept; |
constexpr
用于让表达式核定与编译期,能助力TMP编程
constexpr int square(int x){ |
//以前的写法 |
新的模板
不定个数的参数
void f(int i){ |
模板别名
template <typename T> |
Lambda
允许函数的定义式被用作一个参数、local对象
定义与调用
[]{ |
含参
auto l2 = [](const std::string& s){ |
返回值
[]() -> int { |
外部作用域
分值传递和引用传递两种,值传递不能进行修改
int x = 0; |
mutable
这个关键词是const的反义词,意思是可变的,于是让值传递也可变
int x = 0; |
decltype
自动推导表达式的类型,大号typeof
const int &i = 1; |
推断返回类型
可以将返回类型的声明放在参数列之后
template <typename T1, typename T2> |
带领域的枚举
promise与future
std::promise
意思为共享状态,是C++11提供的在两个异步任务间传递数据的方式,常与std::future
一起使用
void producer(std::promise<int>& p) |
std::thread
用于表示一个执行线程,这个线程可以选择在主线程执行join()
或者另一个线程中执行detach()
。若使用
join()
,则主线程会等待子线程执行完毕后再继续执行,若使用detach()
,则主线程会直接执行,而子线程会在后台执行,两者不会相互影响。
二:一般概念
可被调用的对象
Callable Object,一种对象,可以通过某些方式调用其某些函数,它可以是
- 函数
- 指向成员函数的指针
- 函数对象
- lambda表达式
三:通用工具
pair
本质是一个struct
int add(pair<int, int> p){ |
tuple
大号pair,可以有多个值
int f(tuple<int, int, int> t){ |
智能指针
智能指针智能在,它能知道自己是不是指向某物的最后一个指针
- shared_ptr:共享式拥有
- 多个指针可以指向一个资源,通过引用计数法GC
- 为了解决引用计数法的缺点(比如循环引用),提供weak_ptr等辅助类
- unique_ptr:独占式拥有
- 同一时间只能有一个unique_ptr指向某个资源,可以进行拥有权的移交
极值
Numeric Limit
用于得到当前平台下,一些数值类型的长度(大小)
Trait
是一种技术方案,用来为同一类数据提供统一的操作函数,核心就是使用另外的模版类type_traits
存储不同数据类型的type
,这样就可以兼容各种数据类型
外覆器
Reference Wrapper
允许函数模板可以操作引用,不需要写特化版本
具体有两个函数
- ref:隐式转化为
T&
- cref:隐式转化为
const T &
template <typename T> |
Function Type Wrapper
允许将可调用对象当作最高级对象(first-class object)
vector<function<void(int, int)>> tasks; //一个存储多个可调用对象的vector |
辅助函数
- min
- max
- swap
- operator
==
!=
>
<
>=
<=
编译期分数运算
ratio<5, 5> one; |
四:STL
STL是C++标准库的核心,是一个泛型(generic)程序库,由三部分组成
- 容器(Container)
- 迭代器(Iterator)
- 算法(Algorithm)
有序容器
顺序与插入顺序有关,与元素值无关,常常通过array、linked list实现
- array
- vector
- deque
- list
- forward_list
关联式容器
在内部进行排序的集合,位置取决于value,常常通过二叉树实现
- set
- multiset(mult的意思是元素可以重复)
- map
- multimap(mult的意思是key可以重复)
无序容器
元素位置无关紧要,重要的是元素是否在容器内,常常通过哈希表实现
- unordered_set
- unordered_multiset
- unordered_map
- unordered_multmap
让一个类可以作为unordered_map的key
核心是特化std
class Student |
std::unordered_map<Student, int> student_score_map; |
其他容器
- string
- 寻常的数组(一种type,而非class)
迭代器
迭代器是一个可以遍历STL容器全部、部分元素的对象
操作
*
:取元素++
:迭代器前进至下一个元素- 注意,
++i
比i++
效率高一点点,因为后者要创建临时对象
- 注意,
==
、!=
:判断两个迭代器是否指向同一个位置=
:赋值
种类
R/W | 读写次数 | 跳转 | 举例 | |
---|---|---|---|---|
输入/输出迭代器 | 只读/只写 | 能且仅能读写一次 | i++ | istream_iterators、ostream_iterators |
前向/双向迭代器 | 读写 | 能读写多次 | i++ | STL的set、map |
随机访问迭代器 | 读写 | 能读写多次 | i += n | vector、deque、string、array |
从上到下,迭代器能力越来越强(他们间有继承关系,前向 is a 输入)
算法
大多为非成员函数,思想是泛型编程(而不是OOP)
函数对象
一个行为像函数的对象,思想是泛型编程
class X{ |
- 函数对象是一个带状态的函数
- 函数对象有自己的类型
- 函数对象速度通常比普通函数快(编译期间有更好的优化)