目录
一、统一的列表初始化
二、声明
三、右值引用和移动语义
四、可变参数模板
五、lamda表达式
六、function包装器和bind
一、统一的列表初始化
1.1{}初始化
在C++98中,标准允许使用花括号{}对数组大概结构体元素及进行统一的列表初始化设定
C++11扩大了用大括号扩起的列表(初始化列表)的使用范围,使其可用于所有的内置范例和用户自界说范例使用初始化列表时,可添加等号(=),也可以不添加
一切都可以用列表初始化
- int x = 0;
- int y{ 1 };
- int z = { 2 };
- int a[]{ 1,2,3 };
- int a1[5]{ 0 };
复制代码- struct add
- {
- add(int x, int y)
- :_x(x)
- ,_y(y)
- {
- }
- int _x;
- int _y;
- };
复制代码- add p{ 1,2 };
- add p1(3, 4);
- add p2={ 3,4 };
- const add& pp2 = { 3,4 };
- add* p3 = new add[2]{ p,p1 };
- add* p4 = new add[2]{ {4,5},{6,7} };
复制代码- add p1(3, 4);
- add p2={ 3,4 };
复制代码 这两个可不一样,上面谁人是直接构造,而下面谁人雷同构造实例化出一个对象,再把这个对象拷贝构造给p2,但是连续的构造和拷贝构造就会被编译器直接优化成构造
- vector<int> v = { 1,2,3,4,5,6 };
- list<int> l = { 1,2,3,4,5,6 };
复制代码 那这种列表初始化有差别于上面写的谁人,上面谁人是固定的参数数目,这个初始化的数目是磨练改变的,缘故原由就是C++11引入了一个叫initializer_list(可变参数列表)
1.2initializer_list
其实就是开好一段空间,把你列表里面的值存进去,让start指向你的开头,finish指向你的结尾
- auto l1 = { 1,2,3,4,5,6 };
- cout << typeid(l1).name() << endl;
复制代码
那initializer_list是怎么去初始化vector大概list的呢,可以一个一个的插入,也可以迭代器区间去初始化
二、声明
C++11提供了多种简化声明的方式,尤其是在使用模板的时候
2.1auto
在C++98中去偷是一个存储范例的阐明符,表明变量是局部存储范例,但是局部域中界说局部的变量默认就是自动存储范例,以是auto就没什么价值了,但是C++11中auto原来的用法,将其用于实现自动范例推断,这样要求必须进行表现初始化,让编译器将界说对象的范例设置为初始化值的范例
- int main()
- {
- int i = 10;
- auto p = &i;
- vector<pair<string, string>> dict = { {"apple","苹果"},{"banana","香蕉"} };
- auto it = dict.begin();
- cout << typeid(p).name() << endl;
- cout << typeid(it).name() << endl;
- }
复制代码
看识别到这么长的范例还是非常实用的
2.2decltype
typeid只能把范例以字符串的方式打印出来,但是不可以让它作为范例去界说变量
- int main()
- {
- int i = 1;
- double j = 1.1;
- cout << typeid(i).name() << endl;
- cout << typeid(j).name() << endl;
- auto ret = i * j;
- vector<decltype(ret)> v;
- cout << typeid(v).name() << endl;
- return 0;
- }
复制代码 如果我们需要用ret的范例去实例化vector
decltype可以推导对象的范例。这个范例是可以用来模板实参,大概再界说对象的
2.3 nullptr
由于C++NuLL被界说成字面量0,这样就可能会带来一些标题,因为0既能指针常量,又能表现整型变量,以是处于清楚和安全的角度思量,C++11新增了nullptr,用于表现空指针
2.4范围for循环
这个在前面STL文章已经充分解说了,这里不做太多赘述
2.5智能指针
这个也是一个大模块,以是会针对这个专门写一篇文章
2.6STL的新容器
map和set的哈希表上一篇文章我们已经做了非常细致的解说
array就是静态数组,至于这个东西有什么用呢,大家都知道[]会对越界访问进行检查,就是有了一个更严酷的规范相当于
- int main()
- {
- array<int, 10> arr;
- arr[10];
- return 0;
- }
复制代码 像这里我们运行一下直接就报错了
但是这个还是很鸡肋的,不如用vector
forward_list就是单链表,只支持头插头删的可以用它
三、右值引用和移动语义
3.1左值引用和右值引用
传统的C++语法中就有引用的语法,而C++11新增了的右值引用语法特性,之前我们了解的引用就叫左值引用。无论是左值引用和右值引用,都是给对象取别名
那什么是左值引用呢
左值是一个表现数据的表达式,我们可以获取它的地址,一样平常可以对它赋值,左值可以出现赋值符号的左边,右值不能出如今赋值符号左边。界说const修饰符后的左值,不能给他赋值,但是可以取它 的地址。左值引用就是给左值的引用,给左值取别名
- int main()
- {
- int i = 0;
- int j = i;
- int* p = &i;
- int* d = new int(0);
- return 0;
- }
复制代码 能被取地址的就叫左值,左值可以出如今左边也可以出如今右边
什么是右值引用呢
右值也是一个表现数据的表达式,如:表达式返回值,函数返回值等等,右值可以出如今赋值符号的右边,但是不能出如今赋值符号的左边,右值不能取地址,右值引用就是对右值的引用,给右值取别名
像这些都是右值,它们都是临时对象
那左值能否给右值取别名呢
- int& r = 10;
- const int& r1 = 10;
复制代码 直接的是不可的,临时常量是不能被修改的,你转为平常对象是权限的放大以是要加const
右值引用可以给右值取别名
- int&& r1 = 10;
- int&& r2 = i + j;
复制代码 T&&,这个&&可以看作右值引用符号
右值引用不能给左值取别名,但是右值引用可以给move(左值)取别名
那么为什么要有左值引用和右值引用呢,引用的本质标题都是淘汰拷贝
- string& add(string x, string y)
- {
- return x+y;
- }
- int main()
- {
- string ret ;
- ret = add("1", "2");
- return 0;
- }
复制代码 像这种环境就不可以用左值引用返回,你x+y是临时变量,临时变量出了作用域就烧毁了,自然就不能取别名
但是没有引用以后你x+y要走一个拷贝构造一个临时对象复制一下这个x+y的资源,再把这个临时对象赋值给ret,这期间要用两次深拷贝
有人说用右值引用可不可以,右值引用改变不了这个对象的生命周期,改变不了它出了作用域就烧毁的事实,这就有了移动构造和移动赋值
3.2移动构造和移动赋值
C++把右值分为纯右值和将亡值
纯右值 内置范例右值
将亡值 自界说的右值
这个成本是很大的
我们移动构造就是实现一个右值引用的构造,构造一个临时对象指向和你x+y一样的资源,然后我们同样的实现一个右值引用的赋值,让ret也指向这个资源,把ret不要的资源换给临时对象让它一并带走
以是我们之前说的move本质就是把左值酿成右值的属性,调用移动赋值淘汰深拷贝
- String(String&& s)
- : _data(s._data)
- , _size(s._size)
- {
- s._data= nullptr;
- s._size= 0;
- }
- String& operator=(String&& s) {
- if (this != &other)
- {
- delete[] _data;
- _data= s._data;
- _size= s._size;
- s._data= nullptr;
- s._size= 0;
- }
- return *this;
- }
复制代码- int main()
- {
- string ret ;
- ret = add("1", "2");
- string arr=move(ret);
- return 0;
- }
复制代码
C++11里面的swap也支持了移动构造和移动赋值
STL的容器在C++11都支持了移动构造和移动赋值
插入也支持了移动构造和移动赋值
注意:右值被右值引用引用以后的属性是左值
- int main()
- {
- int&& r = 10;
- int p = r;
- cout << p << endl;
- }
复制代码 这是编译器的逼迫修改
3.3函数模板:万能引用
- void Func(int&& x)
- {
- cout << "右值引用" << endl;
- }
- void Func(int& x)
- {
- cout << "左值引用" << endl;
- }
- template <class T>
- void Print(T&& x)
- {
- Func(x);
- }
- int main()
- {
- int a = 0;
- Print(a);
- Print(std::move(a));
- }
复制代码- void Print(T&& x)
- {
- Func(x);
- }
复制代码 但是这里的func因为是右值被右值引用以是x酿成了左值,如果我们move改一下又全部酿成了右值
- void Print(T&& x)
- {
- Func(move(x));
- }
复制代码
- void Print(T&& x)
- {
- Func(forward<T>(x));
- }
复制代码 以是库里面就有了forward这个完美转化,保持实参的属性
如果它不是模板是平常函数就不能左值是左值引用右值是右值引用了
不是模板就只是右值引用了
而且C++类中的默认成员函数也到场了移动构造函数和移动赋值运算符重载
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
如果你没有自己实现移动构造函数,且没有实现析构函数,拷贝构造,拷贝赋值重载(都没写)的恣意一个。那么编译器会自动天生一个默认移动构造。默认天生的移动构造函数,对于内置范例会执行逐成员按字节拷贝,自界说范例成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造
移动赋值也是跟上面一样
如果你提供了移动构造和移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
看起来很严酷现实上也是很合理的,因为你如果自己实现了析构函数,就代表你要完成深拷贝,那默认提供给你的移动构造也是完不成什么东西的
3.4default关键字
- class People
- {
- public:
- People(const char* name = "", int age = 0)
- :_name(name)
- ,_age(age)
- {
- cout << "构造" << endl;
- }
-
- People(const People&p)
- :_name(p._name)
- , _age(p._age)
- {
- cout << "拷贝构造" << endl;
- }
- //People& operator=(const People& p)
- //{
- // if (this != &p)
- // {
- // _name = p._name;
- // _age = p._age;
- // }
- // cout << "拷贝赋值" << endl;
- // return *this;
- //
- //}
- People& operator=(const People& p) = default;
- private:
- string _name;
- int _age;
- };
- int main()
- {
- People p("人");
- People pp;
- pp = p;
- return 0;
- }
复制代码 这个default就是让编译器逼迫天生好比说这个拷贝赋值
3.5delete关键字
- People& operator=(const People& p) = delete;
复制代码
相反的这个delete就是不让它天生
四、可变参数模板
就是可以传入恣意多个参数
Args是一个模板参数包,args是一个函数形参参数包
声明一个参数包Args...args,这个参数包可以包罗0到恣意个模板参数
- template<class ...Args>
- void List(Args... args)
- {
- cout << sizeof...(args) << endl;
- }
- int main()
- {
- List(1);
- List(1,1.1);
- List(1,1.1,'x');
- }
复制代码- cout << sizeof...(args) << endl;
复制代码 这个...是规定
可以盘算你的参数包有几个参数
那我们想要解析这个参数包该怎么办呢
- void _List()
- {
- cout << endl;
- }
- template<class T,class ...Args>
- void _List(const T& val, Args... args)
- {
- cout << val << " ";
- _List(args...);
- }
- template<class ...Args>
- void List(Args... args)
- {
- _List(args...);
- }
- int main()
- {
- List(1);
- List(1,1.1);
- List(1,1.1,'x');
- }
复制代码
这算是一个编译时的递归推演
假设一开始我们有三个参数包,传给list,然后_list会把它分成一个参数包和两个参数包,打印第一个,再分解剩下两个参数包,又会分成一个和另一个,到0个就不会继续往下推了,然后调用换行就结束了
第一个模板参数一次解析后去参数值
- template<class T>
- int Print(T t)
- {
- cout << t << " ";
- return 0;
- }
- template<class ...Args>
- void List(Args... args)
- {
- int arr[] = { Print(args)... };
- cout << endl;
- }
- int main()
- {
- List(1);
- List(1,1.1);
- List(1,1.1,'x');
- }
复制代码 也就相当于这里你要初始化arr,知道它的大小,你就必须让编译器强行解析参数包,参数包有几个参数,Print就依次推演天生几个
五、lamda表达式
- struct Books
- {
- string _name; // 名字
- double _price; // 价格
- int _evaluate; // 评价
- //...
- Books(const char* str, double price, int evaluate)
- :_name(str)
- , _price(price)
- , _evaluate(evaluate)
- {
- }
- };
- struct ComparePriceLess
- {
- bool operator()(const Books& gl, const Books& gr)
- {
- return gl._price < gr._price;
- }
- };
- struct ComparePriceGreater
- {
- bool operator()(const Books& gl, const Books& gr)
- {
- return gl._price > gr._price;
- }
- };
- int main()
- {
- vector<Books> v = { { "哈利波特", 22.1, 5 }, { "西游记", 30, 4 }, { "红楼梦", 32.2, 3 }, { "三国演义", 41.5, 4 } };
- sort(v.begin(), v.end(), ComparePriceLess());
- sort(v.begin(), v.end(), ComparePriceGreater());
- }
复制代码 像这种类进行比力的时候我们都要专门写一个仿函数来支持它,但是如果参数过多大概仿函数的名字写的比力令人混淆就会引出很多的标题以是引入了lamda表达式
5.1lambda表达式书写格式:
[capture-list] (parameters) mutable-> return-type{statement}
[capture-list]:捕捉列表。该列表总是出如今lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
(parameters):参数列表。与平常函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
mutable:默认环境下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)
->return-type:返回值范例。用追踪返回范例形式声明函数的返回值范例,没有返回值时此部分可以省略。返回值范例明确环境下,也可省略,由编译器对返回范例进行推导。
{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕捉到的变量。
打×的可以不写,打勾×的可写可不写
- auto f=[](int x)->int {cout << x << endl; return 0; };
- f(50);
- auto f1 = [](int x)
- {
- cout << x << endl; return 0;
- };
- f1(20);
复制代码 这是一个函数对象,我们可以用auto来自动推导它的范例,返回值也可以不写,lamda也会自动推导出它的返回值
因为它团体而言是一个语句以是也要加;
- sort(v.begin(), v.end(), [](const Books& b1, const Books& b2)->bool {return b1._price < b2._price; });
复制代码 我们上面的比力函数就不消写仿函数了,这样也清楚明了,是按价格的升序比力
这里就不消加;上面谁人因为他是一条语句的结束,但是这里它是一个对象
- auto f=[](int x)->int {cout << x << endl; return 0; };
- f(50);
- cout << typeid(f).name() << endl;
- auto f1 = [](int x){cout << x << endl; return 0;};
- cout << typeid(f1).name() << endl;
复制代码
这个类的名字 <lamda_uuid> uuid是根据算法来的
但是f不能赋值给f1因为它们两个是差别的范例
5.2捕捉列表阐明
捕捉列表描述了上下文中那些数据可以被lamda使用,以及使用的方式传值还是传引用
[var]:表现值传递方式捕捉变量var。
[=]:表现值传递方式捕捉所有父作用域中的变量(成员函数包罗this指针)。
[&var]:表现引用传递捕捉变量var。
[&]:表现引用传递捕捉所有父作用域中的变量(成员函数包罗this指针)。
[this]:表现值传递方式捕捉当前的this指针。
注意:
父作用域指的是包罗lambda函数的语句块。
语法上捕捉列表可由多个捕捉项组成,并以逗号分割。好比[=, &a, &b]。
捕捉列表不允许变量重复传递,否则会导致编译错误。好比[=, a]重复传递了变量a。
在块作用域以外的lambda函数捕捉列表必须为空,即全局lambda函数的捕捉列表必须为空。
在块作用域中的lambda函数仅能捕捉父作用域中的局部变量,捕捉任何非此作用域大概非局部变量都会导致编译报错。
lambda表达式之间不能相互赋值,即使看起来范例雷同。
- int x = 1, y = 2;
- auto f = [](int& i, int& j)
- {
- int tmp = i;
- i = j;
- j = tmp;
- };
- f(x, y);
复制代码 我们可以传参已往使用
- auto f1 = [x, y]()
- {
- int tmp = x;
- x = y;
- y = tmp;
- };
- f1();
复制代码
我们捕捉过来的就酿成了这个类的成员变量但是成功成员变量是const修饰的const修饰的就不能修改以是我们就要加一个mutable让它酿成可变的
- x = 0, y = 1;
- auto f1 = [x, y]()mutable
- {
- int tmp = x;
- x = y;
- y = tmp;
- };
- f1();
- cout << x << " " << y << " " << endl << endl;
复制代码 但是我们运行以后会发现它们两个根本就没有互换
- x = 0, y = 1;
- cout << &x << " " << &y << endl;
- auto f1 = [x, y]()mutable
- {
- cout << &x << " " << &y << endl;
- int tmp = x;
- x = y;
- y = tmp;
- };
- f1();
- cout << x << " " << y << " " << endl << endl;
复制代码
相当于一种传值传参,
- x = 0, y = 1;
- cout << &x << " " << &y << endl;
- auto f1 = [&x, &y]()mutable
- {
- cout << &x << " " << &y << endl;
- int tmp = x;
- x = y;
- y = tmp;
- };
- f1();
- cout << x << " " << y << " " << endl << endl;
复制代码 加一个以引用捕捉就好了,拿外面的x,y来初始化自己
这里指出捕捉的变量是自己所处的父作用域
- int x = 1, y = 2, z = 3;
- auto f2 = [=, &z]()
- {
- z++;
- cout << x << endl;
- cout << y << endl;
- cout << z<< endl;
- };
- f2();
复制代码 =就即是是全部传值捕捉
- class A
- {
- public:
- void func()
- {
- auto f2 = [=]()
- {
- cout << a1 << endl;
- cout << a2 << endl;
- };
- f2();
- }
- private:
- int a1=1;
- int a2=1;
- };
复制代码 这个就是捕捉this指向的成员变量
lamda的底层原理其实是仿函数
六、function包装器和bind
6.1funtion包装器
包装器包装的其实是可调用对象分别是:
函数指针
仿函数
lambda
函数指针的缺点是太复杂,看不懂
仿函数的缺点是太重了写一个比力还要去专门实现一个类
lamda的缺点是无法搞范例,范例是匿名的,差别的时间点差别的编译器范例都是不一样的
- //类模板原型如下
- template<class T>function;
- template<class Ret,class...Args>
- class function<Ret(Args...)>;
复制代码 Ret:被调用函数的返回范例
Args...被调用函数的形参
- void swap_func(int& i, int& j)
- {
- int tmp = i;
- i = j;
- j = tmp;
- }
- struct Swap
- {
- void operator()(int& i, int& j)
- {
- int tmp = i;
- i = j;
- j = tmp;
- }
- };
- int main()
- {
- int x = 1, y = 2;
- auto swaplamda = [](int& i, int& j)
- {
- int tmp = i;
- i = j;
- j = tmp;
- };
- function<void(int&, int&)> f1 = swap_func;
- cout << x << " " << y << endl << endl;
- function<void(int&, int&)> f2 = Swap();
- cout << x << " " << y << endl << endl;
- function<void(int&, int&)> f3 = swaplamda;
- cout << x << " " << y << endl << endl;
- }
复制代码 我们可以对任何可调用对象进行包装
funtion把上面三种比力方法包装成一种统一的范例,如今我们来看一种实用例子
- map<string, function<void(int&, int&)>> cmdop = {
- {"仿函数",Swap()},
- {"函数指针",swap_func},
- {"lamda",swaplamda}
- };
- cmdop["函数指针"](x, y);
- cout << x << " " << y << endl << endl;
- cmdop["仿函数"](x, y);
- cout << x << " " << y << endl << endl;
- cmdop["lamda"](x, y);
- cout << x << " " << y << endl << endl;
复制代码 6.2包装成员函数
成员函数取地址比力特殊,要加一个类域和取地址
- class Func
- {
- public:
- static int plusi(int a, int b)
- {
- return a + b;
- }
- double plusd(double a, double b)
- {
- return a + b;
- }
- };
- int main()
- {
- function<int(int, int)> f1 = &Func::plusi;
- cout << f1(1, 2) << endl;
- function<double(Func*,double, double)> f2 = &Func::plusd;
- Func fu;
- cout<<f2(&fu, 1.1, 2.2)<<endl;
- function<double(Func, double, double)> f3 = &Func::plusd;
- cout << f3(Func(), 1.1, 2.2) << endl;
- return 0;
- }
复制代码 不是静态的成员函数参数里面都会包罗一个隐藏的this指针以是我们要加进去
最后一个是一个是指针去调用里面的成员函数,最后一个是对象去调用里面的成员函数
但是成员函数我们每次都要传第一个成员参数,但是我们能不能不传啊,以是就有了bind
6.3bind
它也在function头文件里面
可以调解次序也可以调解个数
bind是一个函数模板,它就像一个函数包装器(适配器),担当一个可调用对象,天生一个新的可调用对象来适应原对象的参数列表
调解参数次序
- int Sub(int a, int b)
- {
- return a - b;
- }
- int main()
- {
- function<int(int, int)> f1 = Sub;
- cout << f1(5, 2) << endl;
- function<int(int, int)> f2 = bind(Sub,placeholders::_2,placeholders::_1);
- cout << f2(5, 2) << endl;
- return 0;
- }
复制代码
placeholder相当于一个标识符
调解参数个数,有些参数可以bind时写死
- function<int(int)> f3 = bind(Sub, 20, placeholders::_1);
- cout << f3(5) << endl;
复制代码 这就能解决我们上面总是要传成员参数的标题了
- function<double(double, double)> f4 = bind(&Func::plusd,Func(),placeholders::_1,placeholders::_2);
- cout << f4(1.1, 2.2) << endl;
复制代码- void Print(int a, int b, int c)
- {
- cout << a << endl;
- cout << b << endl;
- cout << c << endl;
- }
- int main()
- {
- function<void(int, int)> f1 = bind(Print, placeholders::_1, 10, placeholders::_2);
- f1(1, 2);
- }
复制代码 也可以bind中心谁人参数,这里传的placeholde_1/2r代表的是第一个实参还是第二个实参
希望对大家有所帮助
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |