一.列表初始化
1.1 { }初始化
在C++ 11 中扩大了 大括号{ } 的使用范围,我们可以使用大括号初始化内置类型对象和自定义类型对象,可以在使用时加上 等于= ,不加也可以,但是还是建议加上。
在初始化对象时我们就可以像以下方式初始化。
#include<iostream>
using namespace std;
struct point
{
point(int val_1, int val_2)
:_val_1(val_1)
,_val_2(val_2)
{
}
int _val_1;
int _val_2;
};
int main()
{
int x1 = 10;
int x2{ 11 };
int a1[10] = { 1 };
int a2[10]{ 1,2,3,4 };
point b1(1,2);
point b2{ 1,2 };
point * pp = new point[2]={{1,1},{2,2}};
}
1.2 std::initializer_list 容器
当我们在使用以下的方式进行初始化时,编译器会在 定义x1对象时报错,这时为什么呢。
struct Point
{
int _x;
int _y;
};
int main()
{
Point x1 {1,2,3};
vector<int> x2 ={1,2,3};
}
在底层中,对于C++11中的现有容器,编译器在编译时,会把 现有容器 花括号{ } 中的所有元素放入initializer_list 容器中,然后将initializer_list中的元素一个个取出来,再对目标容器进行初始化操作。
二.声明
2.1 decltype
auto类型可以自动的推演对象的类型,decltype函数也有差不多相同的功能,但是他俩不同的点就在于用的场景不太相同,如下图所示
在定义变量时,auto可以自动的推导变量的类型,但是在使用函数进行实例化的时候auto就没有办法使用了,所以这个时候我们会是用decltype函数。
注意在decltype函数的括号中一定要写一个变量或者对象。
2.2 nullptr
在C++ 98 和 C 语言中 NULL一直是使用宏替换来实现的,只要在程序中出现了null就会被编译器替换成 0 ,这么写在一些时候会带来一些bug,因为 0 可以是一个指针常量或者是一个整形常量,所以C++ 中新增了 nullptr 用来表示空指针。
三.左值、右值引用
3.1 概念
和 左移右移的概念一样,这里的左值和右值并不是单单的代表着一个数值在等号的左边或者在等号的右边。而代表这其他意思
左值:代表了一个变量、对象、指针,等可以被修改的对象,我们可以获取其地址,并且一般可以被赋值。
右值:代表一个数据的表达式(字面量、表达式返回值、函数返回值等),不可以对其取地址。所谓左值引用就是左值的别名,右值引用就是右值的别名。
3.2 区别
1.左值引用:
(1)左值引用只能引用左值,不能引用右值
(2)但是const 类型的左值引用既可以引用左值也可以引用右值。
2.右值引用
(1)只能引用右值不能引用左值
(2)右值可以引用std::move()后的左值
3.3 右值引用的意义
1.减少反复拷贝的次数
string func()
{
string str("Hello world");
return str;
}
int main()
{
string ret;
ret = func();
return 0;
}
以上的代码在调用func()函数时,构建str 对象时调用了一次构造函数,然后在使用return语句的时候编译器创建了一个临时变量其值和str的内容一样,main函数中的ret在接受 func()函数的返回值时又调用了拷贝构造。
这个程序一共调用了两次构造函数一次拷贝构造函数,这个过程无疑是造成很大时间和空间资源浪费的。
那么以下的方式就是让主函数的ret直接获取到 str 的资源,从而节省空间和时间。
// 移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 移动语义" << endl;
swap(s);
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}
这里在构造str对象时,会采用这里的移动构造的方式,首先构造一个string类型的对象,并且初始化,s是Hello world字符串的右值引用,等到string对象构建完毕,就把 s 中的资源全部换给了这里this指针指向的对象了str,随后的return 还是会创建一个临时对象只不过这个对象是const string类型的临时变量具有常性(将亡值)编译器会继续调用移动构造函数。
最后ret = func()时,会调用这里的移动赋值从而达到节省空间的作用。因为本身swap的交换是不需要付出什么代价的。
以上的写法其实只是为了说明移动构造和移动构造的原理,真正的程序不需要这么写,因为编译器会自动的进行优化的操作(只要编译器识别到了需要连续调用构造和拷贝构造时就会直接进行移动构造的操作)
3.4 C++ 11 中的新增接口
C++ 11 中新增了右值引用的方式来进行移动拷贝,每当识别到右值或者将亡值时就使用移动构造来减少深拷贝的发生。
3.5 将亡值和纯右值
纯右值:内置类型的右值
将亡值:自定义类型的右值2
3.6 完美转发
template<class T>
void Prefect_Forward(T&& t)
{
fun(t);
}
在模板中的 T&& 代表的是完美转发,并不是单单代表右值引用,而代表的是右值引用,它既能接受左值又能接受右值。在这里如果接受到的值是左值的话也可以称这种现象为引用折叠。
template<class T>
void Prefect_Forward(T&& t)
{
fun(t);
}
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{
Fun(t);
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
但是以上这种写法的结果全都会调用到左值引用的函数上,因为 T&& 虽然既可以接受左值又可以接受右值,但是T&& 本身的属性是左值,这就导致我用 t 去调用函数时,发去的参数全都是左值。
为什么这里的 T&& 类型的对象是左值属性的呢,因为不管是左值还是右值引用都是需要更改被引用类型对象的参数的,右值的属性的对象其数据都是无法修改的,所以这里T&&是左值的属性。
那么为了让 T&& 类型的对象去代表被引用对象的属性我们就需要用到完美转发 forward 函数,该函数让被T&&引用的对象保持自己原本的属性(左值或右值)
将代码更改为如下所示
template<class T>
void Prefect_Forward(T&& t)
{
forward<T>(t);
}
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{
Fun(t);
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
四.类的新功能
之前在介绍类时,我们知道编译器会给一个类自动生成6种默认的成员函数
1.构造函数 2.析构函数 3.拷贝构造
4.赋值重载 5.取地址重载 6.const 取地址重载
C++ 11 中又新增了俩函数分别是 移动构造函数和移动复制运算符重载
注意:
(1)如果我们自己没有实现移动构造、析构、拷贝、拷贝赋值重载的任意一个函数,编译器自己就会生成一个默认的移动构造和移动赋值。
(2)可以使用default关键字强制编译器自己生成默认函数
(3)可以使用delete关键字强制编译器不生成默认函数。
五. lambda 表达式
以下代码中定义了一个商品属性,我们想要按照商品的价格排序时,我们可以自己写一个仿函数来进行排序。
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
struct Goods
{
Goods(string name, double cost, int evaluate)
:_name(name)
, _cost(cost)
, _evaluate(evaluate)
{
}
string _name;
double _cost;
int _evaluate;
};
struct Good_Sort_Less
{
bool operator()(const Goods first, const Goods second)
{
return first._cost > second._cost;
}
};
int main()
{
vector<Goods> goodslist = { {"麦当劳",50,4},{"袁记云饺",30,5},{"肯德基",60,3} };
cout << "排序前: " << endl;
for (auto e : goodslist)
{
cout << e._name << " 价格 " << e._cost << " 评价星级 " << e._evaluate << endl;
}
sort(goodslist.begin(), goodslist.end(), Good_Sort_Less());
cout << "排序后:" << endl;
for (auto e : goodslist)
{
cout << e._name << " 价格 " << e._cost << " 评价星级 " << e._evaluate << endl;
}
}
但是仿函数有的时候写的太烦了,C++就借鉴了python的lambda表达式,我们可以把代码修改为如下所示
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
struct Goods
{
Goods(string name, double cost, int evaluate)
:_name(name)
, _cost(cost)
, _evaluate(evaluate)
{
}
string _name;
double _cost;
int _evaluate;
};
//struct Good_Sort_Less
//{
// bool operator()(const Goods first, const Goods second)
// {
// return first._cost > second._cost;
// }
//};
int main()
{
vector<Goods> goodslist = { {"麦当劳",50,4},{"袁记云饺",30,5},{"肯德基",60,3} };
auto Good_Sort_Less = [](const Goods first, const Goods second) {return first._cost > second._cost; };
cout << "排序前: " << endl;
for (auto e : goodslist)
{
cout << e._name << " 价格 " << e._cost << " 评价星级 " << e._evaluate << endl;
}
sort(goodslist.begin(), goodslist.end(), Good_Sort_Less);
cout << "排序后:" << endl;
for (auto e : goodslist)
{
cout << e._name << " 价格 " << e._cost << " 评价星级 " << e._evaluate << endl;
}
}
lambda表达式的书写表达形式如下所示
[capture list] (parameter list) mutable -> return type { function body }
这图我偷的这个哥们的博客深入浅出 C++ Lambda表达式:语法、特点和应用_c++ lamda函数作为函数参数-CSDN博客
这里可以把 lambda 表达式看成一个语法糖,在C++中其实就是拿仿函数实现的,其中编译器会把parameter list 里面的变量都定义成成员变量,把函数体function body 定义成仿函数中的()运算符重载里面函数体的内容。
mutable 可以看成把类中的成员变量使用const修饰的作用。
一般的都会使用auto类型的变量去接受lambda表达式,然后再通过这个auto 类型的对象对lambda表达式来进行调用。
捕获列表说明:
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
注意:
a. 父作用域指包含lambda函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量在这里插入代码片
[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复。
d. lambda表达式之间不能相互赋值,即使看起来类型相同,底层是不同的类类型。
六.包装器
function 包装器是C++中functional头文件中的一个类模板。
我们在 定义完这里以后可以传输函数指针、仿函数、lambda 给这个包装器,都可以
甚至可以这么整