原文 https://www.cnblogs.com/linuxAndMcu/p/11600553.html
1. nullptr
(1) 作用:nullptr 的类型为 nullptr_t,能够隐式地转换为任何指针的类型,能和他们进行相等或者不等的比较。
简单说,nullptr目的是为了区分 空指针NULL 和 0。
(2) 解释:传统C++ 把NULL和0视为同一种东西,这样会导致出现违反直观的情况。比如:
void foo(char *); //构造函数char
void foo(int); //构造函数int
main() {
char* ch = NULL; //NULL被视为0
foo(ch); //调用 构造函数int,而不是调用 构造函数char,违反直观。
}
c++11 改用 char* ch = nullptr 之后,就会调用构造函数char,避免违法直观。
2. 类型推导
2.1 auto关键字
(1) 作用:可以让编译器自动分析初始值来判断变量所属的类型。必须确定初始值类型。
(2) 解释:典型的使用例子是迭代器:
//c++98:
for(vector<int>::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)
//c++11:
for(auto itr = vec.cbegin(); itr != vec.cend(); ++itr);
2.2 decltype关键字
(1) 作用:自动分析表达式判断它的类型,但不会去计算表达式的值。
(2) 解释:
auto x = 1; //自动推导为 int
auto y = 2;
decltype(x+y) z; //也自动推导为 int
2.3 auto 和 decltype 的区别
(1) 执行时间不同:
auto: 执行在程序运行期间。
decltype:执行在程序编译期间。(与sizeof()一样)
(2) 推导逻辑不同:
auto:通过编译器计算变量的初始值来推导类型;如果初始化语句中存在多个表达式且类型不相同时,会推导出“最宽泛”的类型。
decltype:分析表达式的结果来推导类型,但又不会执行将表达式的值计算出来。
(3) 顶层const处理逻辑不同:
auto: 会忽略顶层const,比如:const int m = 10; auto d = m; //d 的类型是int 不带 const
decltype:不会忽略顶层const。
(4) 推导获得引用类型逻辑不同:
auto:不会推导获得引用类型。
decltype:多层括号decltype((表达式)) ,返回的就是引用。 表达式是左值也会获得引用类型。
(5) 使用场景不同:
auto:适合简单的类型推导,比如迭代器 和 模板编程中简化模板函数或模板类的定义。
decltype:复杂表达式的类型推导,避免不必要的类型转换,特别是涉及到const和引用。
(6) 参考文章:
C++11中auto与decltype的区别与联系深入解析_auto和decltype区别-CSDN博客
auto 和 decltype的区别_decltype和auto的区别-CSDN博客
2.4 拖尾返回类型、auto 与 decltype 配合
(1) 作用:推导函数模板的返回类型。
(2) 解释: decltype(x+y) add(T x, U y); 这种自动推导函数返回类型的写法,会编译错误。因为编译器在读到 decltype(x+y) 时,x 和 y 尚未被定义,所以编译错误。
所以c++11引入了拖尾返回类型“ -> ” ,如下代码所示:
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) { //拖尾返回类型
return x+y;
}
这样就 避免了 编译期间需要确定模板参数的类型,获得自动推导能力。
3. 区间迭代 - 基于范围的 for 循环
(1) 作用:引入了基于范围的迭代写法,: key : array
(2) 解释: 代码如下:
c++98:
for(std::vector<int>::iterator i = arr.begin(); i != arr.end(); ++i) {
std::cout << *i << std::endl;
}
c++11:
for(auto i : arr) {
std::cout << i << std::endl;
}
4. 列表 初始化
(1) 作用:统一普通数组、POD(plain old data)、类对象的初始化格式: A a = {x} 或 A a {x}。 有没有 赋值符号 = 都可以。
(2) 解释:代码如下:
// 普通数组
int i_arr[3] = { 1, 2, 3 };
// POD类型:结构体
struct A
{
int x;
struct B
{
int i;
int j;
} b;
};
A a = { 1, { 2, 3 } };
// 类对象
class Foo
{
public:
Foo(int) {}
Foo(const Foo &);
}
Foo a = {123}; //调用构造函数 Foo(int) {}
5. 模板增强
5.1 外部模板
(1) 作用:显式的告诉编译器何时进行模板的实例化,避免重复实例化而导致的编译时间增加。
(2) 解释:传统 C++ 中,只要在每个编译文件中遇到被完整定义的模板,都会实例化。这样会重复实例化,导致编译时间增加。
C++11 引入了外部模板 extern,能够显式地告诉编译器何时进行模板的实例化。代码如下:
template class std::vector<bool>; // 强行实例化
extern template class std::vector<double>; // 不在该编译文件中实例化模板
5.2 尖括号 “>”
(1) 作用:连续的右尖括号 >> 将不再 仅被当做右移运算符 来进行处理。
(2) 解释:代码如下:
//c++98:
std::vector<std::vector<int>> wow; //编译不通过,需要改成 "int> > wow"
//c++11:
std::vector<std::vector<int>> wow; //编译通过
5.3 类型别名模板
(1) 作用:可以为模板定义一个新的名称,就和 typedef 结构体 一样。
(2) 解释:在传统 C++ 中,typedef 可以为类型定义一个新的名称,但却不能为模板定义一个新的名称。C++11 使用 using , 支持对模板定义一个新的名称。代码如下:
template <typename T>
typedef SuckType<int, T, 1> NewType; // 不合法
using NewType = SuckType<int, T, 1>; // 合法
5.4 默认模板参数
(1) 作用:可以为模板的参数,设定默认类型,使用时不需要每次都指定。
(2) 解释:代码如下:
template<typename T = int, typename U = int> //指定默认的 int类型
auto add(T x, U y) -> decltype(x+y) {
return x+y;
}
6. 构造函数
6.1 委托构造
(1) 作用:构造函数内部可以调用另一个构造函数 ,达到简化代码的目的。
(2) 解释:代码如下:
class Base {
public:
int value1,value2;
Base() { value1 = 1; }
Base(int value) : Base() { // 委托构造函数 Base()
value2 = 2;
}
};
(3) 注意:先执行目标构造函数,再执行委托构造函数。 委托构造函数在后。
6.2 继承构造
(1) 作用:派生类想要使用基类的构造函数,不再 需要在构造函数中显式声明。
(2) C++11 使用 using,让派生类直接继承基类的构造函数。
struct A
{
A(int i) {}
A(double d,int i) {}
};
//c++98:
struct B:A
{
B(int i):A(i) {}
B(double d,int i):A(d,i) {}
// ......等等好多个和基类构造函数对应的构造函数
};
//c++11:
struct C:A
{
using A::A; //继承基类的构造函数,一句话搞定
};
7. Lambda表达式
7.1 作用:
所谓 Lambda 表达式,就是提供了一个类似匿名函数的特性。
匿名函数就是需要定义一个函数,但是又不想费力去命名这个函数。
7.2 常用组成结构:
[capture list] (parameter list) { function body}
(1) capture list:表示捕获列表, 捕获 lambda 所在函数中的局部变量的列表,可以为空。(lambda表达式外部的变量)。 取以下值的意义分别是:
[] : 不捕获任何变量。
[&] : 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
[=] : 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
注意1: 值捕获的前提是变量可以拷贝
注意2: 拷贝的时间是在lambda表达式被创建时拷贝,不是调用时。
(2) parameter list:表示 lambda表达式(函数)的参数
(3) Function body: 表示函数体。
(4) 使用例子:
int a = 0;
auto f = [=] { return a; }; //定义lambda表达式 f, 拷贝捕获外部变量 a
cout << f() << endl; // 输出0 //调用lambda表达式 f,返回a的值并输出
a += 1; //修改变量a
cout << f() << endl; // 输出0 //由于捕获a是在创建lambda时,所以时候修改变量a,不会起作用。
7.3 完整组成结构:
[capture list] (parameter list) option -> return type { function body}
option: 表示 函数选项;可以填 mutable,exception,attribute。(选填)
mutable:lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法。
exception:lambda表达式是否抛出异常以及何种异常。
return type: 表示该 lambda 的返回类型
7.4 大致原理
(1) 定义一个 lambda 表达式后,编译器会自动生成一个匿名类,称为闭包类型。
(2) 运行时, lambda 表达式就返回一个匿名的闭包实例,是一个右值。
(3) 所以,lambda 表达式的结果就是一个个闭包。
(4) 对于传值方式捕获的变量,闭包中会添加这个变量的数据类型,作为非静态数据成员。
(5) 对于引用方式捕获的变量,闭包中是否添加这个变量的数据类型,与具体实现有关。
7.5 lambda 表达式是不能被赋值
lambda 表达式不能被赋值修改,但可以拷贝生成一个副本。
闭包类型禁用了赋值操作符,但是没有禁用复制构造函数
auto a = [] { cout << "A" << endl; };
auto b = [] { cout << "B" << endl; };
a = b; // 非法,lambda无法赋值
auto c = a; // 合法,生成一个副本
7.6 lambda 表达式可以赋值给函数指针
(1) lambda 表达式可以赋值给 类型匹配的 函数指针。
(2) 但使用函数指针并不是那么方便。所以 STL 定义在 < functional > 头文件提供了函数对象封装 std::function,类似于函数指针。它可以绑定任何类函数对象,只要参数与返回类型相同。
//返回 bool,且接收两个 int 的函数包装器:
std::function<bool(int, int)> wrapper = [](int x, int y) { return x < y; };
7.7 STL 算法搭配 lambda 表达式
大部分 STL 算法,可以非常灵活地搭配 lambda 表达式来实现想要的效果。例如:
int value = 3;
vector<int> v {1, 3, 5, 2, 6, 10};
//如果数组元素的值大于value,就返回true,并统计个数。
//coun_if()算法的第三个参数,输入lambda表达式。
int count = std::count_if(v.beigin(), v.end(), [value](int x) { return x > value; });
8. 新增容器
8.1 std::array
(1) 作用:提供比 vector 性能更高的顺序容器。
(2) array 和 vector 的区别:
相同:都是顺序容器,都是利用连续的内存空间来存储元素,都能用下标来访问元素。
不同:
vector无需指定大小;array需要指定大小,并且只能用常量数值来指定大小。
vector 容量大小可变;array容量大小不可变。
vector 作为函数参数时,可以被隐式转换成指针;array不能被隐式转换。
vector 依赖动态分配/释放内存,比较耗时;array的内存大小固定,编译时直接分配一段栈上/静态存储区内存即可,速度更快。
(3) 代码如下: std::array<int, 4> arr= {1,2,3,4};
8.2 std::forward_list
(1) 作用:单向链表
(2) 解释:std::forward_list 是单向链表, 使用方法和 std::list (双向链表)基本类似。
提供了 O(1) 复杂度的元素插入,不支持随机访问(这也是链表的特点),标准库容器中唯一一个不提供 size() 方法的容器。比 std::list 有更高的空间利用率。
8.3 无序容器
(1) 作用:C++11 引入了两组无序容器:
std::unordered_set,std::unordered_multiset
和std::unordered_map,std::unordered_multimap
。
(2) 解释: 无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(n)。
(3) unorderedmap和map的区别:
a. 底层实现方式:
map:使用红黑树(自平衡二叉查找树)实现,map中的元素按照键的大小有序排列
unordered_map:使用哈希表实现,不保证元素的顺序。
b. 性能表现:
map在插入和删除操作时需要维护红黑树的平衡,而unordered_map则只需要计算哈希值并将元素放入相应的桶中即可。
需要快速查找特定的元素时,unordered_map通常比map更快,因为哈希表在平均情况下提供O(1)的查找时间复杂度,红黑树的查找时间复杂度是O(logN)。
map:进行有序地遍历元素时,性能更好。
unordered_map:插入操作和查找时,性能更好。
c. 综上所述:
map:用于 有序地关联数据
unordered_map:用于 快速查找大规模数据
8.4 元组 std::tuple
(1) 作用:把多个数据组合成一个结构体数据。可以构造元组,获取元素和拆解元组。
(2) 解释:
a. 构造元组:auto student = std::make_tuple(3.8, 'A', "张三");
b. 获得元组某个位置的值:std::get<0>(student);
c. 拆解元组:std::tie(gpa, grade, name) = student ;
9. 正则表达式
(1) 作用:C++11 提供了正则表达式库 ,用于操作 std::string 对象
(2) 解释: 具体内容包括 std::regex 和 std::smatch,std::regex_match(),代码如下:
std::regex regex_str("([a-z]+)\\.txt"); //匹配串
std::string source_arr[] = {"foo.txt", "bar.txt"};
for (const auto &source: source_arr)
bool is_match = std::regex_match(source, regex_str); //成功返回true
std::smatch match_data;
for(const auto &source: source_arr) {
if (std::regex_match(source, match_data, regex_str)) {
if (match_data.size() == 2) {
// match_data 的第一个元素是 整个字符串
std::string match_full = match_data[0].str();
// match_data 的第二个元素是 匹配串里第一个括号表达式
std::string match_sub = match_data[1].str();
}
}
}
10. 语言级线程支持
10.1 std::thread
作用:线程
10.2 std::mutex/std::unique_lock
std::mutex 作用:互斥量
std::unique_lock 作用:用于管理对互斥量的锁定与解锁, 与std::lock_guard基本相同.
另外还支持 延迟锁定、条件等待、尝试锁定、锁定时长、使用权转移、手动解锁。
10.3 std::future/std::packaged_task
std::packaged_task 作用:异步任务提供者
std::future 作用:异步返回对象
10.4 std::condition_variable
作用:条件变量,用于阻塞线程等待从而实现线程同步。和GO的条件变量一样。
11. 右值引用和move语义
c++11 引入了右值引用
和移动语义
,为了避免无谓的复制,提高程序性能。
11.1 左值、右值
(1) 左值:表达式结束后依然存在的持久化对象
(2) 右值:表达式结束时就不再存在的临时对象
(3) 典型特性:左值表达式能做 取址 操作,右值不能。
11.2 右值引用
(1) 作用: 右值本来在表达式语句结束后就该终结了,而通过右值引用又重获新生,其生命期延迟至 右值引用类型变量 的生命期一样。
右值引用使用的符号是 &&。
(2) 目的:减少右值的内存拷贝。
(3) 解释:代码如下
class A {
public:
int a;
};
A getTemp()
{
return A();
}
A && a = getTemp(); //getTemp()的返回值是右值, 通过右值引用 a ,延长了生命周期
11.3 std::move
(1) 作用:显示地 把左值转化为右值, 让右值引用可以指向左值。避免赋值。
(2) 解释:想“借用”一个对象的数据而不是复制它时,可以使用std::move触发移动语义,提升性能。比如在容器中实现元素转移操作, 代码如下:
MyClass myObj;
std::vector<MyClass> vec;
// 使用 std::move 将 myObj 以移动语义添加到 vector 中
vec.push_back(std::move(myObj));
12. std::bind 函数
(1) 作用:把一个可调用对象与一些参数绑定,生成一个新的可调用对象。可以节省每次调用该对象时,都输入一大堆参数。
(2) 解释:
double divFunc(double x, double y) {return x/y;} //定义可调用对象 divFunc
auto fn_half = std::bind (divFunc, _1, 2); //绑定了第二个参数y,固定值为2
std::cout << divFunc(10) << '\n'; //只需输入第一个参数10即可,输出结果5 (10/2 = 5)
如果绑定的是对象成员函数的话,就这样调用:
//Obj是具有divFunc成员函数的类对象
auto fn_half = std::bind (&Obj::divFunc, &obj divFunc, _1, 2);
13. std :: function 类模板
(1) 作用: std :: function 是通用的多态函数包装器, 它的实例可以存储,复制和调用任何可调用的目标 。
可调用目标 包括函数,lambda 表达式,绑定表达式或其他函数对象,以及指向成员函数和指向数据成员的指针。
(2) 解释: 简单说就是一个函数签名模版,代码如下:
int f(int a, int b)
{
return a+b;
}
int main()
{
std::function<int(int, int)>func = f;
cout<<f(1, 2)<<endl; // 3
return 0;
}