初始化列表
底层是c++11新增类型initializer_list,他提供了构造函数来初始化。STL里的容器提供了initializer_list版本的构造函数。使用花括号初始化变量和对象{}。甚至可以省略等于号(=)。
下图,列举了三个容器实现initializer_list版的构造函数,其他就不一一列举了
查询网站:Reference - C++ Reference
使用场景:
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
using namespace std;
int main()
{
//一切皆可以用花括号初始化{}
vector<int> v = { 1,2,3,4,5 };
pair<string, int> p = { "string",1 };
set<string> s = { "string" };
list<int> li = { 1 };
//内置类型
int i = 1;
int j = { 1 };
int k{ 1 };
return 0;
}
下面这段代码能打印出他的类型
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
using namespace std;
int main()
{
auto li = { 1,2,3,4 };
cout << typeid(li).name() << endl;
return 0;
}
自动推导
decltype,关键字decltype将变量声明为表达式的指定类型。
使用场景:
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
using namespace std;
// decltype的一些使用使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
decltype(t1 * t2) ret;
cout << typeid(ret).name() << endl;
}
int main()
{
const int x = 1;
double y = 2.2;
decltype(x * y) ret; // ret的类型是double
decltype(&x) p;
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
F(1, 'a');
return 0;
}
新增容器
<array> | 静态数组 |
<forward_list> | 单链表 |
<unordered_map> | 哈希表实现map |
<unordered_set> | 哈希表实现set |
array
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
#include <array>
using namespace std;
int main()
{
array<int, 10> arr1;
//arr1[11]; //强制检查越界
int arr2[] = { 0 };
arr2[11]; //这里不会强制检查
return 0;
}
forward_list
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <list>
#include <array>
#include <forward_list>
using namespace std;
int main()
{
forward_list<int> s;
s.push_front(1);
s.push_front(2);
s.push_front(3);
s.push_front(4);
//没有直接提供尾插的接口 为了效率的提升
auto it = s.begin();
while (it != s.end())
{
cout << *it << endl;
it++;
}
return 0;
}
unordered_map和unordered_set和map/set使用一模一样,只是底层使用了哈希表实现,这里不演示使用了。
左值引用和右值引用
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们 之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
什么是左值?什么是左值引用?
左值是一个数据的表达式,可以对左值取地址,给左值赋值。const修饰后的左值,不能改变他的值。左值引用就是给左值取别名,给左值引用。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int i = 1; //i是左值
int* ptr = &i;//给左值取地址
i = 3;//给左值赋值
int& j = i;//给左值取别名 就是左值引用
const int k = 10;//k是const修饰的左值
//k = 1; const修饰后的左值不允许改变
const int& l = k;//const修饰左值的引用
return 0;
}
什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引 用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能 取地址。右值引用就是对右值的引用,给右值取别名。
右值引用的语法是 类型&& 比左值引用多一个&
int&& j = 10;
右值引用使用 右值引用能否给左值取别名
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int i = 2 * 3;//2*3这个表达式就是右值
int&& j = 10;//给10取别名 右值引用 右值引用的语法是 类型&& 比左值多一个&
//右值引用 能否给左值取别名呢?
//int&& k = i; i是左值 右值引用不能直接给左值取别名
int&& l = move(i); //move之后的左值 右值引用可以取别名
return 0;
}
左值引用和右值引用比较
左值引用总结:
-
左值引用只能引用左值,不能引用右值。
-
但是const左值引用既可引用左值,也可引用右值。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
//int& i = 2; 权限的放大
const int& j = 2; //2是常量 不能改变 权限的平移
return 0;
}
右值引用总结:
-
右值引用只能右值,不能引用左值。
-
但是右值引用可以move以后的左值。
左值引用使用场景
做参数和返回值都可以提高效率,但不过局部变量就不能使用左值引用,只能使用值拷贝,这是左值引用的短板。
右值引用使用场景
右值引用的产生是为了弥补左值引用的短板,目的是提高效率,主要的使用场景就是移动构造和移动赋值。那什么是移动构造和移动赋值呢?这里先简单理解下,那移动构造举例,当你使用将亡值构造对象的时候,必须要使用值拷贝做一个中介完成构造。使用右值引用做参数,编译器做了特别处理,把将亡值识别成了右值,重载了一个构造函数,使用swap,交换资源。这样就避免了多余的值拷贝,提高效率。移动赋值也是这个原理。
移动构造和移动赋值使用右值引用可以提高效率,本质是资源转移
c++98
c++11
上面的代码在vs2022上是不会调用移动构造的,编译器做了优化,会直接调用构造函数构造s。效率更高一点。vs2019上才会调用移动构造。
右值引用进一步剖析
右值引用属于左值属性,不属于右值属性。
证明
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void _Ref(int& i)
{
cout << "左值引用" << endl;
}
void _Ref(int&& i)
{
cout << "右值引用" << endl;
}
int main()
{
int&& i = 10;
int j = 1;
int& k = j;
_Ref(i);
_Ref(k);
return 0;
}
为什么右值引用属于左值,不属于右值呢?右值引用的产生主要就是完成移动构造和移动赋值,实现资源转换,如果定义成右值,那就无法实现资源转换。因此,c++组委会只能定义成左值。
完美转发
模板的右值引用既可以接受右值,也可以直接接受左值,不需要move函数转换。这就是完美转发,模板&&,万能引用。
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template<typename T>
void _PerfectForward(T&& t)
{
cout << "完美转发" << endl;
}
int main()
{
_PerfectForward(10);
int i = 10;//i是左值
_PerfectForward(i);
_PerfectForward(move(i));
return 0;
}
forward<T>是一个函数模板,作用:完美转发在传参的过程中保留对象原生类型属性
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void _Ref(int& i)
{
cout << "左值引用" << endl;
}
void _Ref(int&& i)
{
cout << "右值引用" << endl;
}
template<typename T>
void _PerfectForward(T&& t)
{
_Ref(forward<T>(t)); //保持变量的原来属性
}
int main()
{
_PerfectForward(10);//右值
int i = 10;
_PerfectForward(i);//左值
_PerfectForward(move(i));//右值
return 0;
}
新的类功能
默认成员函数:
构造函数 | c++98 |
析构函数 | c++98 |
拷贝构造函数 | c++98 |
拷贝赋值重载 | c++98 |
取地址重载 | c++98(不重要) |
const 取地址重载 | c++98(不重要) |
移动构造 | c++11 |
移动赋值 | c++11 |
移动构造和移动赋值规则:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任 意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类 型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中 的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内 置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋 值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造 完全类似)
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
default和delete关键字:
强制生成默认函数的关键字default 和 禁止生成默认函数的关键字delete
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Person
{
public:
Person() {};
Person(string& name,int age)
:_name(name),
_age(age)
{
}
Person(Person&& p) = default; //强制生成移动构造(默认函数)
Person(const Person& p) = delete;//禁止生成拷贝构造(默认函数)
private:
string _name;
int _age;
};
可变参数模板
可变参数在c语言就出现过,就是我们熟悉printf和scanf函数,他么使用就是可变参数。在c++中我们把可变参数提升到了模板级别,叫可变参数模板。
//Args是一个模板参数包
//args是一个函数形参参数包
//声明一个参数包 Args ..args 这个参数包可以包含0到任意个参数
template<class ...Args>
void CppPrint(Args...args)
{
}
展开参数包有两种方式:函数递归终止和逗号表达式
函数递归终止
#include <iostream>
#include <vector>
#include <string>
using namespace std;
//Args是一个模板参数包
//args是一个函数形参参数包
//声明一个参数包 Args ..args 这个参数包可以包含0到任意个参数
template<class T>
void CppPrint(T _t)
{
cout << _t << endl;
}
template<class T,class ...Args>
void CppPrint(T _t, Args...args)
{
cout << _t << " ";
CppPrint(args...);
}
int main()
{
CppPrint(1);
CppPrint(1,2);
CppPrint(1,2,3);
return 0;
}
逗号表达式
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args),0)... };
//(PrintArg(args),0)...展开就是
//(PrintArg(arg1),0) (PrintArg(arg2),0) (PrintArg(arg3),0)
cout << endl;
}
应用
他会避免多余的构造函数,会把你的参数,一直传下去,调用构造函数。对于内置类型来看没啥用,对于自定义类型,方便你传值。这里优化构造,其实也没有多优化。毕竟实现了移动构造,调用一次移动构造,消耗极低。
Lambda表达式
语法:
1.捕捉列表 -- 捕捉外界变量
- [x] 传值捕捉x变量
- [&x] 引用捕捉x变量
- [=] 传值捕捉所有变量
- [&] 引用捕捉所有变量
2.mutable 取消捕捉变量const修饰
3.参数:(无参可省略)
4.返回值: (一般都可省略)
5.函数体: 不可以省略
Lambda定义,用auto来推导他的类型
使用:和仿函数一样
#include <iostream>
using namespace std;
int main()
{
auto add = [](int x, int y)->int {return x + y; };
int ret = add(1, 2);
cout << ret << endl;//ret = 3;
return 0;
}
参数和返回值省略
#include <iostream>
using namespace std;
int main()
{
auto add1 = [](int x, int y){return x + y; };//返回值可省略
auto func = []{};//没有参数 参数也可以省略
return 0;
}
捕捉变量使用场景
#include <iostream>
using namespace std;
void Func()
{
cout << "全局变量Func" << endl;
}
auto sub = []{ cout << "全局变量sub" << endl; };
int main()
{
//lambda表达式add的函数体中可以使用其他函数吗?
auto add = []
{
Func();
sub();
};
add();
return 0;
}
#include <iostream>
using namespace std;
int main()
{
auto sub = [] { cout << "局部变量sub" << endl; }; //局部变量
auto add = []
{
sub();
};
add();
return 0;
}
lambda表达式里默认不可以使用局部变量,可以使用全局变量。上面的代码会直接报错,无法运行。那如何使用局部变量呢?使用捕捉列表
#include <iostream>
using namespace std;
int main()
{
auto sub = [] { cout << "局部变量sub" << endl; };
auto add1 = [sub] //捕捉sub变量 值拷贝
{
sub();
};
add1();
int i = 0;
auto add2 = [=] //捕捉所有变量 值拷贝
{
sub();
//i++; 值拷贝默认捕捉变量都是const修饰
cout << i << endl;
};
add2();
auto add3 = [&] //引用捕捉所有变量
{
i++;//是否为const修饰取决于变量本身
};
add3();
cout << i << endl;
return 0;
}
mutable使用场景
他的作用就是取消从const修饰的功能,值拷贝使用。
#include <iostream>
using namespace std;
int main()
{
int a = 1;
auto func1 = [=]() mutable {a++; }; //能修改 不影响外界值
func1();
cout << a << endl;
auto func2 = [&]() {a++; }; //能修改 影响外界值
func2();
cout << a << endl;
return 0;
}
lambda的底层
lambda的底层是仿函数:
#include <iostream>
using namespace std;
int main()
{
auto add1 = [](int x, int y) {return x + y; };
add1(1, 2);
auto add2 = [](int x, int y) {return x + y; };
add2(1, 2);
// add1 = add2 不可以 原因:类型不同
return 0;
}
这里从汇编就可以看得出来
包装器
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
function使用:
#include <iostream>
#include <vector>
#include <string>
#include <functional>
using namespace std;
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
// 函数名 函数指针
cout << useF(f, 11.11) << endl;
// 函数对象
cout << useF(Functor(), 11.11) << endl;
// lamber表达式
cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
cout << endl;
//把可调用对象存储到一个容器中
//包装器
vector<function<double(double)>> v = { f,Functor(),[](double d)->double { return d / 4; } };
for (auto& e : v)
{
cout << useF(e, 11.11) << endl;
}
return 0;
}
包装器可以把不同类型包装成同一个类型,所以上面的useF函数经过包装器之后就之后生成一份,不会生成三份。
bind绑定参数
配合function使用,改变参数位置
bind使用
#include <iostream>
#include <vector>
#include <string>
#include <functional>
using namespace std;
int _sub(int x, int y)
{
return x - y;
}
int main()
{
function<int(int, int)> func1 = bind(_sub, placeholders::_2, placeholders::_1);
int ret = func1(5, 10);//5 - 10 ?
cout << ret << endl;//5
return 0;
}
bind绑定参数除了通过placeholders指定,还可以直接上传参数
bind(_fun,1, placeholders::_2, placeholders::_1);