C++11新特性
文章目录
- 说在前面
- 花括号{}初始化
- new的列表初始化
- STL相关容器的列表初始化
- 相关语法格式
- 容器列表初始化的底层原理
- forward_list和array
- 与类型相关的新特性
- decltype
- 左值引用和右值引用
- 什么是左值,什么是右值
- 左值和右值的本质区别
- 右值引用
- 如何理解右值引用
- std::move
- 移动构造,移动赋值
- 万能引用和完美转发
- 模板的可变参数
- default和delete的新用法
- lambda表达式
- 语法格式
- 捕获列表
- 底层原理
- 包装器
- std::bind
- 总结
说在前面
作为新年的第一篇博客。在这里先祝大家新年快乐!今天我们要介绍的是C++11 首先,如同一个人从一个一无所知的婴儿成长成为一个社会的精英是需要不断发展的。C++自从诞生的那一刻起,一直都在不断进步,不停更新。对于C++来说,有两个比较重要的时间点。一个就是C++98,这个版本的C++引入了强大的STL。而另外的一个时间点就是我们接下来介绍的C++11 接下来,我们就来看一看C++11更新了哪些有用的东西
花括号{}初始化
在C++11里面,所有的东西都可以使用{}进行初始化,你可能会看到很多有关C++11的书籍里面可能都会有如下的demo代码
#include<iostream>
/** C++11 特性* */
void test1()
{//C/C++98里面定义变量常用的方式int i=5;//C++11定义变量常用方式 int x={3};int y{3};} int main()
{test1();return 0;
}
不仅仅是内置类型,自定义类型也可以使用如上的方式进行初始化
//自定义类型也可以支持{}初始化
struct Date
{Date(int year=2023,int month=1,int day=22):_year(year),_month(month),_day(day){}int _year;int _month;int _day;};void test2()
{//C++98Date d1(2022,1,23);//c++11Date d3{2022,1,25};
}
实际上,花括号初始化的本质也是调用了构造函数,我们可以对构造函数添加打印语句:
//自定义类型也可以支持{}初始化
struct Date
{explicit Date(int year=2023,int month=1,int day=22):_year(year),_month(month),_day(day){std::cout<<"Date(int year,int month,int day)"<<std::endl;}int _year;int _month;int _day;};
void test2()
{//C++98Date d1(2022,1,23);//C++11// Date d2={2022,1,24};//还可以这样写 Date d3{2022,1,25};
}
而因为增加了{}初始化的方式,那么就能够解决在C++98里面使用new构造一块连续的空间的同时无法顺带初始化的问题!
new的列表初始化
在C++98里面,对于一块连续空间的申请和初始化,我们只能分开进行:
void test3()
{ //C++98里面申请空间和初始化必须分开//int* p=new int[4];//for(int i=0;i<4;++i) p[i]=i+1;//C++11允许这么做,这里直接在int* p=new int[4]{1,2,3,4};for(int i=0;i < 4;++i){std::cout<<p[i]<<" ";}std::cout<<std::endl;delete[] p;
}
从C++11以后,我们就可以使用花括号来初始化对应new的连续的数组空间!不过相对来说,这种方式用的还是相对比较少。毕竟更多的情况下,我们都是使用的STL提供的容器。所以这里我们知道有这样一种用法就好了
STL相关容器的列表初始化
STL里面的容器也提供了对应的列表初始化的方式,真可谓是一切都可列表初始化,所以我们才会说到了C++11以后。一切皆可使用列表初始化! 下面我们就来看一看相应的语法格式。
相关语法格式
这里我们用vector来进行演示。基本上我们之前学习的容器都可以使用这样的初始化方式!
//自定义类型也可以支持{}初始化
struct Date
{Date(int year=2023,int month=1,int day=22):_year(year),_month(month),_day(day){std::cout<<"Date(int year,int month,int day)"<<std::endl;}//为了方便观察效果void PrintDate(){std::cout<<_year<<" "<<_month<<" "<<_day<<'\n';}int _year;int _month;int _day;};
那么,为什么能够这样给容器初始化呢?在处理容器的{}初始化的时候,编译器做了哪些工作呢?下面我们就来探究一下:
容器列表初始化的底层原理
首先,官方文档就是我们学习C++11最好的资料之一,我们先来看看C++11种vector对应发生了什么变化:
首先,上面两个对应的构造函数是后面的知识点。实际上,真正能够让我们使用花括号初始化对应容器元素的就是最后一个构造函数! 下面我们就通过代码来看一看{}究竟被编译器做了怎样的处理:
void test()
{ auto l={1,2,3,4,5,77};//获取l变量的类型,并以字符串的形式打印std::cout<<typeid(l).name()<<std::endl;
}
这段代码是在Linux上获取的,不过对应的就是文档手册里面的新的构造函数的参数的类型initializer_list<T>! 在C++11以后,{}被编译器识别成为新的initializer_list类型,只要支持这个构造函数就可以使用花括号进行初始化! 所以为了让我们自己实现的vector也能够支持{}初始化,我们也添加这个构造函数
namespace chy
{//vector是一个模板类template<typename T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//...//给我们自己实现的vector添加对应的构造函数 //这个时候我们自己实现的vector也就可以支持{}初始化的方式了!vector( std::initializer_list<T> li){ //initiallizer支持范围forfor(auto& e: li){push_back(e);}}private:iterator _start;iterator _finish;iterator _end_of_stroge;};
}
其他的容器也是一样的道理,这里我就不一一演示了。 另外,C++11还新增加了几个容器。我们之前介绍的哈希相关的容器就是C++11新增的,接下来我们将介绍两个C++11里面存在感比较低的两个容器—> forward_list和array
forward_list和array
C++11新增加了forward_list和array这两个新容器,下面我们先来看forward_list。首先先来看文档对应的说明:
首先,不得不说有的时候C++对于类的命名不是特别能够让人见名知义。这个forward_list其实就是我们之前在数据结构那里学习的单链表!下面,我们就来简单使用以下forward_list
//需要包含这个头文件
#include<forward_list>
void test()
{std::forward_list<int> fl;int a[]={1,2,3,4,5};//没有提供尾插接口for(auto e:a){fl.push_front(e);}//也支持范围forfor(auto e:fl){std::cout<<e<<" ";}std::cout<<'\n';
}
说实话,这个容器的作用远远不及list,即使是我们手写一个简单的单链表也是可以的!不过这个容器的倒是可以作为哈希桶悬挂的子节点结构!
接下来,我们来看一看另外一个容器array.首先我们先来看文档对应的说明:
从文档来看,array就是一个固定大小的泛型线性数组。下面我们就来简单使用一下array:
void test()
{//array的简单使用std::array<int,5> a={1,2,3,4,5};for(auto e:a){std::cout<<e<<" ";}std::cout<<'\n';
}
使用array要注意如下的几个问题:
1.array是一个静态数组,不能对array插入元素
2.array会对越界进行检查
说实话,C++11这个array确实是没什么太大的作用,它所有的功能,vector基本都有!所以这个容器大家仅仅就是作为一个了解就可以了。不用过分去深究这个容器,相应 的forward_list也是如此。
与类型相关的新特性
C++11还增加了一些有关类型推断的新特性。比如我们经常使用的auto就是在C++11以后才增加的类型推导的能力。 接下来介绍一个C++11新增加的一个和类型相关的特性的一个操作符---->decltype
decltype
这个操作符能够把表达式的类型返回给一个变量,测试的代码如下:
void test()
{decltype(10+20)x=10;std::cout<<"x变量的类型为: "<<typeid(x).name()<<std::endl;
}
可以看到这个操作符确实把类型返回了。注意,这个操作符和sizeof操作符一样,并不会去计算对应括号的表达式,仅仅就是返回对应的表达式的类型
左值引用和右值引用
前面讲的特性大多是是C++11相对来说属于锦上添花的。而右值引用可以说式C++11相对来说非常具有革命性意义的一个新特性。它让C++的高效又上了一个台阶! 在正式介绍右值引用之前,我们有必要来明确一个概念:什么是左值 ? 什么是右值?
什么是左值,什么是右值
首先,从字面上的意思来看:左值就是出现在表达式左边的值,右值就是出现在表达式右边的值。比如下面的代码:
//左值和右值
void test()
{//从字面意义上来看,这里的a是左值int a=10+20;//10+20就是一个右值//但是下面怎么解释int b=a;//a不是左值?怎么出现在了右边
}
最后一个例子不难看出:这样的理解是不正确的,如果这样子理解左值和右值就太肤浅了。 那么左值和右值的本质区别到底是什么呢?或者说,左值具有什么特征,右值又有什么特征呢?
左值和右值的本质区别
明确地给出一个结论:左值是可以取地址地值!右值是不能取地址的值!也就是说:通过能否取地址就可以看出:左值是真正分配了计算机内存空间的值!而右值仅仅只是一个临时量!
右值引用
在C++98以前,我们所使用的引用都叫做左值引用。从C++11开始,我们就要把引用分为左值引用和右值引用了。下面我们就来通过代码来看这两个引用:
void test()
{int x=10;//rx是x的别名,是左值int& rx=x;//右值引用使用两个&&int&& rrx=10; //10是右值,所以rrx是右值引用
}
无论右值引用还是左值引用,只要是引用,那么都是被引用对象的别名!而右值引用自身是一个左值,因为我们可以对右值引用的那个别名取地址了!
void test()
{int x=10;//rx是x的别名,是左值int& rx=x;//右值引用使用两个&&int&& rrx=10; //10是右值,所以rrx是右值引用std::cout<<"rrx的地址是:" <<&rrx<<std::endl;
}
如何理解右值引用
从前面的结论可以看出:本来即将消亡的右值,通过右值引用的方式,计算机也确实为了它开辟一块内存空间存储对应的值! 也就是这个别名本质是一个左值!所以右值引用是一个左值,这个左值是右值的别名! 左值引用只能引用左值!而右值引用只能引用右值
std::move
那么有的时候,我们需要把左值转换成为右值。c++11的设计者也考虑到这个点了,所以提供了一个接口:std::move—>可以把左值转换成右值。下面我们来看一段代码:
void fun(int& rx)
{std::cout<<"fun(int&)"<<std::endl;
}
void fun(int&& rx)
{std::cout<<"fun(int&&)"<<std::endl;
}
void test()
{int x=10;fun(x);fun(std::move(x));
}
所以我们就可以总结如下:
左值引用只能引用左值,const左值引用可以引用左值也可以引用右值
右值引用可以引用右值和move以后的左值
移动构造,移动赋值
那么C++11推出右值引用难道仅仅是我们前面那样使用吗?答案显然不是!在C++98的时候,C++一直都很害怕这样的场景:
class Solution {
private:
//获取 对应的keyboard里面的行size_t getRow(char ch,vector<string>& v){for(int i=0;i<v.size();++i){if(v[i].find(tolower(ch))!=string::npos){return i;}}return v.size();}
public:vector<string> findWords(vector<string>& words){vector<string> keyboard;keyboard.push_back("qwertyuiop");keyboard.push_back("asdfghjkl");keyboard.push_back("zxcvbnm");vector<string> res;for(int i=0;i<words.size();++i){size_t pos=getRow(words[i][0],keyboard);//pos=3,说明不在这一行,继续寻找下一行//cout<<pos<<" ";if(pos==3) continue;else { // cout<<pos<<" ";//找到了对应行bool flag=true;for(char ch:words[i]){if(keyboard[pos].find(tolower(ch)) == string::npos){flag=false;break;}}if(flag) res.push_back(words[i]);}}return res;}
};
通过前面的学习我们知道,传值返回会发生拷贝。而对于vector这样的类,发生的是深拷贝!一旦这个vector管理的对象特别多,并且每个对象又要进行深拷贝的情况下。传值返回的效率就是一场灾难! C++是一门十分注重效率的编程语言,而右值引用便是为了解决这个问题应运而生的!
我们知道右值编译器是没有为其分配空间的,那么对于自定义类型的右值,C++又将其称为将亡值 既然是将亡值,那么就意味着这个自定义类型的对象会马上调用析构函数清理自己了。但是这个时候这个自定义类型身上的资源又是我所需要的。下面就有如下两种做法:
1.在对象析构之前,我深拷贝一个副本,然后这个对象自行销毁
2.既然这个将亡对象有我想要的资源,那么不妨把它的资源为我所用!然后再让其自行销毁即可!
显然我们都会选择第二种方式。那么c++11以后,就新增加了两个相应的默认成员函数:移动构造和移动赋值
class A
{
public:A():_p(nullptr),_size(0){}A(int size):_p(nullptr),_size(size){_p=new int[size];}//拷贝构造A(const A& a):_p(nullptr){_p=new int[a._size];for(int i=0;i < a._size;++i){_p[i]=a._p[i];}_size=a._size;std::cout<<"A(const A& a)"<<std::endl;}//移动构造 A(A&& aa):_p(nullptr){std::swap(_p,aa._p);std::swap(_size,aa._size);std::cout<<"A( A&& aa)"<<std::endl;}~A(){delete _p;_p=nullptr;}private:int* _p;int _size;
};
接下来我们使用gdb进行调试观察结果:
接下来就要开始构造a3了,使用的是移动构造:
可以看到,这里的a3和a1的成员发生了交换。相比于先前,我们只能,这种场景我们只能进行深拷贝。当对象很大的时候,效率非常低!而有了C++11的移动构造以后,传值返回的效率得到了大幅度的提升!
而对于传值返回的情况,现代的编译器也会进行一定的大胆的优化,假设我们现在有一个to_string函数:
//一个我们自己写的to_string函数
namespace chy
{string to_string(int val){string str;while(val){//...复杂的转换逻辑}return str;}
}
那么在C++98的时候,传值返回的情况如下:
那么C++11有了移动构造了以后,编译器的传值返回情况又不一样了
对应的有了移动构造,就会有移动赋值:
A& operator=(A&& aa){std::swap(_p,aa._p);std::swap(_size,aa._size);return *this;}
既然是作为默认成员函数,那么在特定的条件下,编译器就会生成对应的默认移动构造和移动赋值,需要满足如下三个特性:
1.没有显式提供拷贝构造函数
2.没有显式提供赋值重载
3.没有显式提供析构函数
只有满足如上三条件,编译器才会生成默认的移动构造和移动赋值函数!
默认生成的移动构造对于内置类型浅拷贝,对于自定义类型,如果提供了移动构造函数,那么就会调用移动构造 不过,多数情况下生成的默认移动构造没什么价值,所以这两个默认成员函数一般都需要手动提供。
需要注意的是:在c++11以后,原先将单个元素插入容器的函数接口都提供了对应的右值版本,目的就是为了能够进一步提高运行效率!
万能引用和完美转发
而当模板和右值引用结合起来的时候,又会发生不一样的效果:
template<typename T>
void solve(T&& t)
{ std::cout<<"调用!"<<std::endl;
}void test()
{A a1;A& a2=a1;// 左值引用传参solve(a2);// 右值引用传参 solve(std::move(a1));
}
我们可以看到,无论是左值还是右值,这个模板函数都能匹配! 这个就是右值引用和模板结合到了一起以后。既能够接受左值,也可以接受右值,因此我们就把这个特性叫做万能引用。 但是这个万能引用有一个很不好的缺陷,接下来我们来通过一段代码进行演示:
template<typename T>
void solve(T&& t)
{ T tmp(t);
}void test()
{A a1;// 右值引用传参 solve(std::move(a1));
}
我们发现,move以后的a1本来应该是一个右值,结果传参了以后变成了一个左值,万能引用就会出现引用折叠的问题。使用万能引用了以后,所有的传递的T&&参数都会变成左值!
,但是有的时候我们希望能够把原来的特性保持下去,所以c++11又提供了一种新的方式---->完美转发
template<typename T>
void solve(T&& t)
{ //使用forward保持特性T tmp(std::forward<T>(t));
}void test()
{A a1;solve(std::move(a1));
}
这里依旧调用的是移动构造函数,很好保持了原来右值的特性。所以如果要保持对应的原来的值的特性,最好使用forward完美转发。
模板的可变参数
接下来我们来讲一讲模板的可变参数,在C语言阶段,我们经常使用如下的函数:
int printf(const char* format,...);
函数声明里面带有省略号的我们称之为可变参数!在C++11里面,引入了可变模板参数!也就是说,以后一个模板函数或者模板类,不仅类型可变,参数的数量也是可以变化的! 下面我们就来看demo代码:
template<typename T>
void PrintArgs(const T& val)
{std::cout<<val<<std::endl;
}
template<typename T,typename... Args>
void PrintArgs(const T& val,Args... args)
{ std::cout<<val<<" typeinfo is "<<typeid(val).name()<<std::endl;//可以使用sizeof获取传递参数的个数 std::cout<<"param num is "<<sizeof...(args)<<std::endl;//参数解包PrintArgs(args...);
}
void test()
{// A a1;// solve(std::move(a1));//可以传任意类型,任意数目的参数 PrintArgs(1,2.3,'c',std::string("hello world"));std::cout<<'\n';PrintArgs(1.1,2);
}
可变参数模板是通过递归实现的,允许有0个或者是1个递归参数包,我们这里使用的是模板的最佳匹配原则来确定递归的出口,如果这类换成一个最佳匹配的函数也可以!
前面这种写法相对比较繁琐,有的大神还设计出了这样的写法:
//大神的写法
template<class T>
void PrintArgs(T t)
{ std::cout<<std::endl;
}
template<class ... Args>
void PrintArgs(Args... args)
{std::cout<<"param num is "<<sizeof...(args)<<std::endl;//使用数组进行解包 ,利用逗号表达式的特性进行参数包解析int a[]={(PrintArgs(args...),0)};
}void test()
{PrintArgs(1,2.3,'c',std::string("hello world"));
}
注意:可变参数递归解包是在编译的时候完成的,不能通过运行时的if逻辑来递归!
有了可变模板参数的知识铺垫,那么我们就可以很好讲解c++11里面容器新增的emplace_back和emplace接口:
emplace系列接口使用的都是可变参数模板,对应如果传递的参数能够匹配到自定义类型的构造函数,就会变成直接构造!提高效率,不过有了移动构造以后,emplace接口带来的效率提升也不是特别明显,所以这个接口了解即可
default和delete的新用法
在C++11里面,又对default和delete这两个关键字进行了功能的扩展。在类和对象章节我们介绍过,只有在不提供任何构造函数的情况下,编译器才会生成默认构造函数! 但是在c++11如果已经提供了其他构造函数的情况下,仍然想使用编译器生成的默认构造函数,可以使用default关键字
class A
{
public:
//强制编译器生成默认构造函数A()=default;A(int size):_p(nullptr),_size(size){_p=new int[size];}//拷贝构造A(const A& a):_p(nullptr){_p=new int[a._size];for(int i=0;i < a._size;++i){_p[i]=a._p[i];}_size=a._size;std::cout<<"A(const A& a)"<<std::endl;}//移动构造 A(A&& aa):_p(nullptr){std::swap(_p,aa._p);std::swap(_size,aa._size);std::cout<<"A( A&& aa)"<<std::endl;}~A(){delete _p;_p=nullptr;}private:int* _p;int _size;
};
不仅是默认构造函数,其他构造函数都可以使用default强制编译器默认生成
不仅仅可以强制编译器生成,我们还可以删除默认成员函数:使用delete关键字
比如删除拷贝构造函数:
class A
{
public://强制编译器生成默认构造函数A() = default;A(int size):_p(nullptr), _size(size){_p = new int[size];}//拷贝构造A(const A& a) = delete;//移动构造 A(A&& aa):_p(nullptr){std::swap(_p, aa._p);std::swap(_size, aa._size);std::cout << "A( A&& aa)" << std::endl;}~A(){delete _p;_p = nullptr;}private:int* _p;int _size;
};
int main()
{ A a2(a1);return 0;
}
lambda表达式
接下来我们来讲lambda表达式。最早引进lambda表达式的语言式python,后面其他语言看到这个语言很好用,于是都纷纷加到了标准库里面。c++11以后同样也支持这样的用法
比如我们上京东购物,有时想按照商品价格排序,有时候想按照实际排序,有各种各样的排序需求。在c++98里面,我们就要写不同的仿函数来实现,一旦随着项目的扩展,这些仿函数就会越来越多,非常难维护
所以我们就希望能够达到一种排序规则专为一种排序方法所用!而lambda表达式恰好就是这样的一个工具,下面我们就来看lambda表达式的语法
语法格式
lambda表达式子的语法特征:
//lambda表达式的语法格式
[捕捉列表](函数参数){ 函数体}
接下来我们就结合具体的样例进行分析
struct Student
{Student(int age,int score,const std::string& name):_age(age),_score(score),_name(name){}int _age;int _score;std::string _name;void print(){std::cout<<_name<<" " << _age <<" "<<_score<<std::endl;}
};
void test()
{Student a[] ={{15,23,"aa"},{14,55,"cc"},{13,88,"bb"} };//显式使用lambdaauto l=[](const Student& s1,const Student& s2){ return s1._age < s2._age ;};std::sort(a,a+3,l);for(auto e : a){e.print();}
}
但其实,lambda更多情况下用的是这样的:
std::sort(a,a+3,[](const Student& s1,const Student& s2){ return s1._score < s2._score ;});for(auto e : a){e.print();}
捕获列表
首先我们要知道一个点。lambda是一个局部匿名函数,既然是函数,那么就代表者,对应的内部是一个全新的作用域!内部作用域不能直接使用外部的变量!
如果我们想要使用对应的变量,就需要进行捕获!
void test()
{//lambda捕获列表//可以使用[]捕获对应的元素int a=1,b=2;auto l=[a,b](){ return a+b; };std::cout<<l()<<std::endl;
}
这种值的捕获方式是值捕获,默认是对捕获的值带了一个const,想要更改值捕获的话就要使用mutable关键字,不过即使更改内部的a,b也对外面没有影响!所以这个mutable几乎没有
,不过我们还可以使用引用捕获
int a=1,b=2;int ret=0;//auto l=[a,b](){ return a+b; };//std::cout<<l()<<std::endl;//引用捕获auto l=[&ret,a,b](){ret =a+b; return 0;};l();std::cout<<"ret="<<ret<<std::endl;
引用捕捉和值捕捉可以混合来,下面这三种写法都是可以的
//=表示用值捕捉所有变量
auto l1=[=](){};
//表示所有变量引用捕获
auto l2=[&](){};
//ret引用捕获,其他变量值捕获
auto l3=[&ret,=](){};
关于lambda的介绍我们就暂时讲到这里。下面我们来看一看这个lambda对于编译器来说究竟是什么?
底层原理
我们将编译的汇编代码进行研究:
可以看到,lambda函数被编译器处理以后就是一个仿函数!这个仿函数是lambda+一个唯一的uuid!
所以即使两个lambda在我们看来一模一样。它们也是不同的类型 值得一提的是,lambda函数还可以和函数指针相互赋值,不过我们并不建议这么去做!
包装器
学到现在,c++11有三个可以进行调用的东西:
1.函数指针
2.仿函数对象
3.lambda
而如果当这些函数统统编程模板的时候。同样的模板就会实例化出三分代码,显然这三份代码还是有一定的冗余!毕竟对于我们都是希望使用对应的()的功能! 因此C++11提供了函数包装器:
void func()
{std::cout<<"func pointer"<<std::endl;
}
struct Func
{void operator()(){std::cout<<"Func()"<<std::endl;}
};
auto f=[]() -> void {std::cout<<"lambda"<<std::endl;};void test()
{std::vector<std::function<void()>> fuctions;fuctions.push_back(func);fuctions.push_back(Func());fuctions.push_back(f);for(auto& uf : fuctions){uf();}std::cout<<'\n';
}
也就是说:但凡是返回值和参数都相同的函数,我们都可以直接使用一个function对象来接收!接下来我们改造一下曾经的逆波兰表达式的题目,用C++11的方式改造:
class Solution {
private:
//纯c++11的方式
unordered_map<string,function<int(int,int)>> hash=
{{"+" ,[](int a,int b){return a+b;}},{"-" ,[](int a,int b){return a-b;}},{"*",[](int a,int b){return a*b;}},{"/",[](int a,int b){return a/b;}}
};
public:int evalRPN(vector<string>& tokens){stack<long long > st;for(const auto& str:tokens){if(hash.find(str)==hash.end()){st.push(stoll(str));}else {long long right=st.top();st.pop();long long left=st.top();st.pop();long long ret=hash[str](left,right);st.push(ret);}}return static_cast<int>(st.top());}
};
std::bind
最后我们介绍的是C++11引入的bind函数,对应的函数原型如下
:
template <class Fn, class... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args>/* unspecified */ bind (Fn&& fn, Args&&... args);
这个函数有如下的两个作用:
1.将一个参数固定,也就是绑定一个固有的参数
2.调整参数的顺序
接下来我们来看示例代码:
int Sub(int a,int b)
{return a-b;
}
class A
{
public:int ASub(int a,int b){return a-b;}
};
void test()
{std::function<int(int,int)> f(&A::Asub);
}
这段代码是错误的 ! 原因是:成员函数有一个隐含的this指针!那么有没有方式可以不需要这个this指针呢?答案是肯定的,解决的方法就是使用std::bind函数
class A
{
public:A(int a=0):_a(a){}int ASub(int x,int y){return (x-y)*_a;}
private:int _a;
};
void test()
{ // std::bind(&A::ASub,A(),std::placeholders::_1,std::placeholders::_2);// std::function<int(int,int)> f(&A::ASub); 错误,非静态成员函数有this指针 // 解决方式:使用std::bind绑定this指针 // 为了演示效果,给A加一个成员 std::function<int(int,int)> f1(std::bind(&A::ASub,A(),std::placeholders::_1,std::placeholders::_2));std::cout<<f1(2,3)<<std::endl;std::function<int(int,int)> f2(std::bind(&A::ASub,A(2),std::placeholders::_1,std::placeholders::_2));std::cout<<f2(2,3)<<std::endl;}
bind还有一个作用就是改变参数的位置。而我们代码里面的_1和_2就是我们要显式传递的参数的位置,也就是占位符!可以通过占位符来改变参数顺序:
void test()
{ std::function<int(int,int)> f1(std::bind(Sub,std::placeholders::_1,std::placeholders::_2)); std::function<int(int,int)> f2(std::bind(Sub,std::placeholders::_2,std::placeholders::_1));std::cout<<f1(2,3)<<std::endl;std::cout<<f2(2,3)<<std::endl;
}
不过相对而言,这个调整顺序并不是很常用。
总结
以上就是C++11的部分新特性的介绍。而C++11更多的新特性,例如智能指针我们会在后续的文章里面重点介绍。
相关文章:
C++11新特性
文章目录说在前面花括号{}初始化new的列表初始化STL相关容器的列表初始化相关语法格式容器列表初始化的底层原理forward_list和array与类型相关的新特性decltype左值引用和右值引用什么是左值,什么是右值左值和右值的本质区别右值引用如何理解右值引用std::move移动…...
【宝塔部署SpringBoot前后端不分离项目】含域名访问部署、数据库、反向代理、Nginx等配置
一定要弄懂项目部署的方方面面。当服务器上部署的项目过多时,端口号什么时候该放行、什么时候才会发生冲突?多个项目使用redis怎么防止覆盖?Nginx的配置会不会产生站点冲突?二级域名如何合理配置?空闲的时候要自己用服…...
从0到1一步一步玩转openEuler--11 openEuler基础配置-设置磁盘调度算法
11 openEuler基础配置-设置磁盘调度算法 文章目录11 openEuler基础配置-设置磁盘调度算法11.1 设置磁盘调度算法11.1.1 临时修改调度策略11.1.2 永久设置调度策略11.1 设置磁盘调度算法 本节介绍如何设置磁盘调度算法。 11.1.1 临时修改调度策略 例如将所有IO调度算法修改为…...
河道治理漂浮物识别监测系统 yolov7
河道治理漂浮物识别监测系统通过yolov7网络模型深度视觉分析技术,河道治理漂浮物识别监测算法模型实时检测着河道水面是否存在漂浮物、水浮莲以及生活垃圾等,识别到河道水面存在水藻垃圾等漂浮物,立即抓拍存档预警。You Only Look Once说的是…...
微信小程序 java ssm Springboot学生作业提交管理系统
系统具有良好的集成性,提供标准接口,以实现与其他相关系统的功能和数据集成。开放性好,便于系统的升级维护、以及与各种信息系统进行集成。功能定位充分考虑平台服务对象的需求。 一个微信小程序由.js、.json、.wxml、.wxss四种文件构成&…...
实战项目-课程潜在会员用户预测(朴素贝叶斯&神经网络)
目录1、背景介绍2、朴素贝叶斯2.1 模型介绍2.2 模型实现3、人工神经网络1、背景介绍 目标:将根据用户产生的数据对课程潜在的会员用户(可能产生购买会员的行为)进行预测。 平台的一位注册用户是否购买会员的行为应该是建立在一定背景条件下…...
ESP32设备驱动-定时器与定时器中断
定时器与定时器中断 文章目录 定时器与定时器中断1、ESP32定时器介绍2、定时器相关API介绍3、软件准备4、硬件准备3、代码实现有时需要按时发生某些事情,这就是计时器和计时器中断发挥作用的地方。 定时器是一种中断。 它就像一个简单的时钟,用于测量和控制时间事件,提供精确…...
【JavaScript 逆向】安居客滑块逆向分析
声明本文章中所有内容仅供学习交流,相关链接做了脱敏处理,若有侵权,请联系我立即删除!案例目标验证码:aHR0cHM6Ly93d3cuYW5qdWtlLmNvbS9jYXB0Y2hhLXZlcmlmeS8/Y2FsbGJhY2s9c2hpZWxkJmZyb209YW50aXNwYW0以上均做了脱敏处…...
【STM32】【HAL库】遥控关灯1主机
相关连接 【STM32】【HAL库】遥控关灯0 概述 【STM32】【HAL库】遥控关灯1主机 【STM32】【HAL库】遥控关灯2 分机 【STM32】【HAL库】遥控关灯3 遥控器 需求 主机需要以下功能: 接收来自物联网平台的命令发送RF433信号给从机接收RF433信号和红外信号驱动舵机动作 方案设计…...
Java 初始化块
文章目录1、初识初始化块2、实例初始化块和构造器3、类初始化块1、初识初始化块 Java 使用构造器来对单个对象进行初始化操作,使用构造器先完成对整个 Java 对象的状态初始化,然后将 Java 对象返回给程序,从而让该 Java 对象的信息更加完整。…...
超详细讲解长度受限制的字符串函数(保姆级教程!!!)
超详细讲解长度受限制的字符串函数(保姆级教程!!!)长度受限制的字符串函数strncpy函数strncpy函数的使用strncpy函数的模拟实现strncat函数strncat函数的使用strncat函数的模拟实现strncmp函数strncmp函数的使用strncm…...
【c#】c#常用小技巧方法整理(4)——cmd命令提示符,c#调用cmd
CMD命令是一种命令提示符,CMD是command的缩写,位于系统System32的目录下,是大多数Windows操作系统中可用的命令行解释器应用程序。用于执行输入的命令。其中大多数命令通过脚本和批处理文件自动执行任务,执行高级管理功能…...
在项目中遇到的关于form表单的问题
前言 以下内容都是基于element Plus 和 vue3 一个form-item校验两个下拉框 有时候不可避免会遇到需要一个form-item校验两个下拉框的情况,比如: 这种情况下传统的校验已经无法实现,需要通过form表单提供的自定义校验来实现。以上面的必填…...
德国奔驰、博世和保时捷的员工年薪有多少?
点击 欧盟IT那些事 关注我们公告:因企鹅审核规定,本公众号从《德国IT那些事》更名为《欧盟IT那些事》。从职场新人到总裁,一个个盘。位于德国斯图加特的梅赛德斯-奔驰集团及其子公司梅赛德斯-奔驰是世界最知名的汽车制造商之一。奔驰车代表着…...
Mybatis与微服务注册
目录 一,Springboot整合MybatisPlus 创建商品微服务子模块 二,SpringBoot整合Freeamarker 三、SpringBoot整合微服务 &gateway&nginx 整合微服务之商品服务zmall-product 创建并配置网关gateway服务 安装配置SwitchHosts 安装配置Windo…...
JAVA练习47-合并两个有序数组
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、题目-合并两个有序数组 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 总结 前言 提示:这里可以添加本文要记录的大概内容: 2月11日练习…...
右键菜单管理 - Win系统
右键菜单管理 - Win系统前言软件工具管理右键菜单360右键管理右键管家前言 Windows系统可以借助软件工具对右键菜单进行管理,可对指定的右键菜单进行删除和恢复。下面以Win10系统为例介绍管理方法。 注意:使用本文提及的工具将某软件的右键菜单删除后&…...
背包问题求方案数、具体方案
背包问题求方案数、具体方案01背包问题求体积恰好等于V的方案数完全背包问题求体积恰好等于V的方案数01背包问题求最优选法的方案数完全背包问题求最优选法的方案数01背包问题求具体方案01背包问题求体积恰好等于V的方案数 原题链接AcWing278. 数字组合 考虑状态表示&#x…...
电商导购CPS,淘宝联盟如何跟单实现用户和订单绑定
前言 大家好,我是小悟 做过自媒体的小伙伴都知道,不管是发图文还是发短视频,直播也好,可以带货。在你的内容里面挂上商品,你自己都不需要囤货,如果用户通过这个商品下单成交了,自媒体平台就会…...
【Shell1】shell语法,ssh/build/scp/upgrade,环境变量,自动升级bmc,bmc_wtd,
文章目录1.shell语法:Shell是用C语言编写的程序,它是用户使用Linux的桥梁,硬件>内核(os)>shell>文件系统1.1 变量:readonly定义只读变量,unset删除变量1.2 函数:shell脚本传递的参数中包含空格&am…...
刷题记录:牛客NC208250牛牛的最美味和最不美味的零食
传送门:牛客 题目描述: 牛牛为了减(吃)肥(好),希望对他的零食序列有更深刻的了解,所以他把他的零食排成一列,然后对每一 个零食的美味程度都打了分,现在他有可能执行两种操作&…...
微搭低代码从入门到精通08-轮播容器
我们上一篇讲解了基础布局组件,讲解了普通容器和文本组件的用法,本篇我们继续介绍布局组件。 小程序中经常会有个功能是轮播图展示的功能,多张图片可以顺序进行切换。我们学习使用轮播容器的时候,先考虑切换的图片从哪来…...
分类预测 | MATLAB实现SSA-CNN麻雀算法优化卷积神经网络多特征分类预测
分类预测 | MATLAB实现SSA-CNN麻雀算法优化卷积神经网络多特征分类预测 目录分类预测 | MATLAB实现SSA-CNN麻雀算法优化卷积神经网络多特征分类预测分类效果基本介绍模型描述程序设计参考文献分类效果 基本介绍 1.Matlab实现SSA-CNN麻雀算法优化卷积神经网络多特征分类预测&…...
华为10年经验测试工程师,整理出来的python自动化测试实战
前言 全书共分11章,第一章是基础,了selenium家谱,各种组件之间的关系以及一些必备知识。第二章告诉如何开始用python IDLE写程序以及自动化测试环境的搭建。第三章是webdriver API,我花了相当多时间对原先的文档,冗余…...
OpenCV杂谈 - 如何导出图像到内存中其他结构
前言 最近在net环境使用OpenCV,记录些疑难杂点. 一、OpenCV主要结构 Mat 二、Cols,Rows 和 Width,Hight 三、导入\导出到内存中其他结构 四、按矩形 在Mat之间复制 总结 一、OpenCV主要结构 Mat Mat是OpenCV中的主要结构. 主要有两个用途. 1 存储图片信息,2 存…...
Session与Cookie的区别(四)
咖啡寄杯的烦恼 虽然店里生意还可以,但小明无时无刻不想着怎么样发大财赚大钱,让店里的生意变得更好。 他观察到最近好多便利商店开始卖起了咖啡,而且时不时就买一送一或是第二件半价,并且贴心地提供了寄杯的服务。 寄杯就是指说你…...
Linux 文件锁 - fcntl
什么是文件锁? 即锁住文件,不让其他程序对文件做修改! 为什么要锁住文件? 案例,有两个程序,都对一个文件做写入操作。 #include <unistd.h> #include <stdio.h> #include <stdlib.h> …...
CellularAutomata元胞向量机-2-初等元胞自动机MATLAB代码分享
%% 二维元胞自动机%imagesc(a)的色度矩阵a中0->256由蓝变黄% 规则,先把中间点置为1,每一时间每一点如果%周围八个点和为偶数,则变为0,为奇数则变为 1% 颜色控制clc, clearMap [1 1 1; 0 0 0];% [0 0 0] is black, [1 1 1] is …...
OpenStack云平台搭建(6) | 部署Neutron
目录 1.在控制节点登录数据库配置 2.要创建服务证书,完成这些步骤 3.创建网络服务API端点: 4.安装网络组件 5.配置neutron组件 6.配置 Modular Layer 2 (ML2) 插件 7.配置Linuxbridge代理 8.配置DHCP代理 9.配置元数据代理 10.编辑/etc/nova/no…...
Lesson 05.Configuring the Oracle Network Environment
Lesson 05. Configuring the Oracle Network Environment 文章目录Lesson 05. Configuring the Oracle Network Environment1. 监听程序的配置文件有哪些,如何命名,保存在什么位置?2. Oracle 网络的服务名称文件是如何命名的,需要…...
重庆哪家公司做网站好/易推客app拉新平台
php图片转换成二进制的方法:首先获取需要转换的图片;然后使用fopen函数读取图片信息;接着使用“fread($fp, filesize($img));”方法将图片转换成二进制数据即可。这次记录的东西很简单,就是把图片转成二进制数据存到数据库中&…...
网站建设模块是什么意思/今天的最新消息新闻
1. super关键字的作用 如果一个类从另一个类继承,我们new这个子类的示例对象的时候,这个子类对象类里面会有一个父类对象,怎么去引用这个父类对象呢? java中使用super来引用,super是当前对象里面的父对象的引用。 2…...
我要外包网站/百度高级搜索指令
HashMap是什么? HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 Java的HashMap Java的HashMap主要由两种数据结构组成ÿ…...
做网站go和python/网站如何推广出去
我看了下没发现什么问题,现把代码发出来求高手解答#regionprivatevoidExportToExcel()privatevoidExportToExcel(){Response.Clear();Response.Buffertrue;Response.Charset"utf-8"...我看了下没发现什么问题,现把代码发出来求高手解答#region…...
企业网站开发前台模块设计/江西百度推广公司
这篇文章,我们来学习一下java中的两个重要接口:Comparable与Comparator 目录 1.概述 2.Comparable接口 2.1 具体讲解 2.2 源码分析 3.Comparator接口 3.1 具体讲解 3.1源码分析 4.二者的异同点 4.1相同点: 4.2 不同点 5.总结 1.概…...
在线a视频网站一级a做片/seo关键词使用
1.什么是分治算法? 分治法(Divide-and-Conquer)是一种很重要的算法。 分治就是“分而治之”的意思,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单的直接求解&…...