深入浅出C++ ——C++11
文章目录
- 一、C++11简介
- 二、列表初始化
- 二、声明
- 四、范围for循环
- 五、STL中的变化
- 六、右值引用和移动语义
- 1. 什么是左值?什么是左值引用?
- 2. 左值引用与右值引用比较
- 3. 右值引用使用场景和意义
- 4. 完美转发
- 新的类功能
- 默认成员函数
- 类成员变量初始化
- default
- delete
- final与override关键字
- 可变参数模板
- 递归函数方式展开参数包
- 初始化列表展开参数包
- emplace
- lambda表达式
- lambda表达式概念
- 捕获列表说明
- 仿函数/函数对象与lambda表达式
- 包装器
- function包装器
- bind
一、C++11简介
1998年是C++标准委员会成立的第一年,本来计划以后每5年视实际需要更新一次标准,C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是2007年发布,所以最初这个标准叫C++07。但是到06年的时候,官方觉得2007年肯定完不成C++ 07,而且官方觉得2008年可能也完不成。最后干脆叫C++0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的时候也没完成,最后在2011年终于完成了C++标准。所以最终定名为C++11。
从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。https://en.cppreference.com/w/cpp/11 相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,是C++的重点内容。
二、列表初始化
在C++98中,标准允许使用花括号{ }对数组或者结构体元素进行统一的列表初始值设定。C++11扩大了用大括号括起的列表/初始化列表的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号,也可不添加。
int main()
{int x = 1;int y = {2};int z {3};//自定义类型也支持列表初始化,是调用构造函数Date d1(2023,2,24);// C++11支持的列表初始化,这里会调用构造函数初始化Date d2 = {2023,2,24};Date d2{2023,2,24};return 0;
}
普通对象用花括号初始化很怪,能看懂即可,不建议使用。列表初始化真正的作用是给容器初始化,如下所示:
int main()
{vector<int> v1 = {1,2,3,4};vector<int> v2{1,2,3,4};list <int> lt1={1,2,3,4};list <int> lt2{1,2,3,4};return 0;
}
std::initializer_list
C++11提供了initializer_list类,此类型用于访问C++初始化列表中的值,该列表是const T类型的元素列表。也就是说初始化列表其实是有类型的,它的类型就是initializer_list<T>
。
int main()
{auto il = { 10, 20, 30 };cout << typeid(il).name() << endl; //class std::initializer_list<int>return 0;
}
C++11将所有的容器都支持了initializer_list这样的构造函数,所以可以用容器都列表初始化,例如:C++11支持list (initializer_list<value_type> il)
,这样初始化容器对象就更方便了。同时,也可以作为operator=的参数,这样就可以用大括号赋值。
vector<int> v = { 1,2,3,4 };
list<int> lt = { 1,2 };// 这里{"sort", "排序"}会先初始化构造一个pair对象
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };// 使用大括号对容器赋值
v = {10, 20, 30};
模拟实现的vector也支持{}初始化和赋值
template<class T>
class vector {
public:typedef T* iterator;vector(initializer_list<T> l){_start = new T[l.size()];_finish = _start + l.size();_endofstorage = _start + l.size();iterator vit = _start;typename initializer_list<T>::iterator lit = l.begin();for (auto e : l){*vit++ = e;} }vector<T>& operator=(initializer_list<T> l) {vector<T> tmp(l);std::swap(_start, tmp._start);std::swap(_finish, tmp._finish);std::swap(_endofstorage, tmp._endofstorage);return *this;}
private:iterator _start;iterator _finish;iterator _endofstorage;
};
二、声明
auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。
int i = 10;
auto p = &i;
map<string, string> dict = { {"a", "1"}, {"b", "2"} };
auto it = dict.begin();cout << typeid(i).name() << endl; //int
cout << typeid(p).name() << endl; //int*
cout << typeid(it).name() << endl; //map<string, string>::iterator
decltype
关键字decltype 将变量的类型声明为表达式指定的类型,与auto的区别,auto定义的变量取决于等号右侧,而decltype定义的变量取决于等号左侧。
int x = 10;decltype(x) y1 = 20.22;
cout << typeid(y1).name() << endl; //int
nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
四、范围for循环
for (declaration : expression)
{// 循环体
}
declaration 表示遍历声明,在遍历过程中,当前被遍历到的元素会被存储到声明的变量中。expression 是要遍历的对象,它可以是表达式 、容器、数组、初始化列表等。范围for的底层原理就是被替换为了迭代器,实现了迭代器就是实现了范围for。
五、STL中的变化
C++11中的一些新容器,但是实际最有用的是unordered_map和unordered_set。
array
array可以认为是一个静态的顺序表,使用上跟数组基本没有差别。因为C语言数组中对于越界的判定(抽查)存在误差,C++11希望用户使用array容器来替换数组,容器化可以严格检查越界读写,因为检查是调用的operator[]去检查。实际上C语言数组已经习惯了,并且用array不如用vector,因为array在栈上而vector在堆上,不用考虑栈溢出,所以array很少用。
forward_list
forward_list可以认为是一个单链表,list是双向链表,forward_list可以减少空间,如果不是非常特殊的需求,实际开发中也不会用forward_list。
容器内部的变化
- 容器内部都支持了initializer_list构造,用来支持列表初始化。
- 增加了cbegin和cend的系列接口,实际中也没什么用。
- 增加了移动构造和移动赋值,在一定场景下可以提高效率。
- 增加了右值引用参数的插入,提高了效率。
六、右值引用和移动语义
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,无论左值引用还是右值引用,都是给对象取别名。
1. 什么是左值?什么是左值引用?
左值是一个表示数据的表达式,左值可以获取它的地址的值。除const修饰符后的左值不能赋值(可以取地址)外,其余情况左值可以赋值。
int main()
{// 以下的p、b、c、*p都是左值int* p = new int(0);int b = 1;const int c = 2;// 以下几个是对上面左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& pv = *p;return 0;
}
什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,最重要的是右值不能取地址,且不能放到赋值符号的左边。
int main()
{double x = 1.1, y = 2.2;// 以下几个都是常见的右值,不能取地址10;x + y;f(x, y);// 以下几个都是对右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = f(x, y);// 不能放到赋值符号的左边// 这里编译会报错:error C2106: “=”: 左操作数必须为左值10 = 1;x + y = 1;f(x, y) = 1;return 0;
}
2. 左值引用与右值引用比较
左值引用
- 左值引用只能引用左值,不能引用右值。
- 但是const左值引用既可引用左值,也可引用右值(因为右值是不能被改变的的)。
template<class T>
void Func(const T&x) //x既能接收左值,也能接收右值,体现了const左值引用引用右值的价值。
{}int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a; // ra1为a的别名//int& ra2 = 10; // 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}
右值引用
- 右值引用只能右值,不能引用左值。
- 但是右值引用可以move以后的左值。
int main()
{// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”, message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
}
3. 右值引用使用场景和意义
引用的价值是为了减少拷贝,尤其是深拷贝。左值引用做参数和做返回值都可以提高效率,但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回,传值返回会导致至少1次拷贝构造(编译器可能做优化)。例如模拟实现的to_string函数,只能传值返回,进行拷贝构造,在屏幕中打印“深拷贝”。
//拷贝构造
string (const string&s):str(nullptr)
{cout<<"深拷贝"<<endl;string tmp(s._str);swap(tmp);
}Jared::string to_string(int value)
{Jared::string str;////return str;
}
int main()
{Jared::string ret1 = Jared::to_string(1234);return 0;
}
右值引用的出现使得C++增加了两个函数,移动构造和移动赋值,可以解决上述问题。如果一个需要深拷贝的类实现了移动构造和移动赋值,就不怕传值返回的情况了。
移动构造
// 移动构造
string(string&& s):_str(nullptr),_size(0),_capacity(0)
{cout << "string(string&& s) -- 资源转移" << endl;swap(s);
}
将引动构造加入到类中,再运行上面Jared::to_string的调用,这里没有调用深拷贝的拷贝构造,而是调用了移动构造,在屏幕中打印资源转移。移动构造中没有新开空间,拷贝数据,所以效率提高了。C++11将右值进行了划分,内置类型的右值称为纯右值,自定义类型的右值称将亡值。执行return str的时候,str被识别(编译器识别返回值类型来调用拷贝构造或移动构造) 为将亡值(右值),从而调用移动构造。Jared::string ret1 = Jared::to_string(1234);
在这里又是一次移动构造,将将亡值赋值给ret1,但是编译器优化后变为以此移动构造。
移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
移动赋值
// 移动赋值
string& operator=(string&& s)
{cout << "string& operator=(string&& s) -- 移动语义" << endl;swap(s);return *this;
}int main()
{Jared::string ret1;ret1 = Jared::to_string(1234); //不仅拿到了1234资源,还把自己的资源还给将亡值,当它带走return 0;
}
可以看到调用了一次移动构造和一次移动赋值。Jared::to_string函数中会先用str生成构造生成一个临时对象,但是编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后把这个临时对象做为Jared::to_string函数调用的返回值赋值给ret1,这里调用的移动赋值。
STL容器插入接口函数也增加了右值引用版本
通常插入的函数接口,在C++11后都支持右值版本,插入的过程中,如果传递的是右值对象,那么就进行资源转移,从而减少拷贝。
int main()
{list<bit::string> lt;Jared::string s1("1111");// 这里调用的是拷贝构造lt.push_back(s1);// 下面调用都是移动构造lt.push_back("2222");lt.push_back(std::move(s1));return 0;
}
std::move()函数
当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
int main()
{Jared::string s1("hello world");// 这里s1是左值,调用的是拷贝构造Jared::string s2(s1);// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造// 但是这里要注意,一般是不要这样用的,因为我们会发现s1的// 资源被转移给了s3,s1被置空了。Jared::string s3(std::move(s1));return 0;
}
4. 完美转发
模板中的万能引用
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; }// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{Fun(std::forward<T>(t));
}
在模板中,比较特殊(属于语法特性),T&& 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;
}
如果不用std::forward(t),输出的结果都是左值引用/const左值引用,t全被被看为左值。而使用std::forward(t) 可以保证在传参的过程中保持了t的原生类型属性。
新的类功能
默认成员函数
C++11 新增了两个默认成员函数:移动构造函数和移动赋值运算符重载。
- 如果没有实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造。
- 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
- 默认移动赋值跟上面移动构造完全类似。
- 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
- 拷贝对象需要深拷贝的时候,需要自己写移动构造和移动赋值(大多数情况),比如string、vector、list。
类成员变量初始化
C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。
default
default关键字会强制生成默认函数的关键字,假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成默认的构造函数了,那么我们可以使用default关键字显示指定构造生成。
class Person
{
public:Person()= default; Person(const Person& p):_name(p._name),_age(p._age){}
private:bit::string _name;int _age;
};
delete
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。例如,不想让person对象拷贝,使用delete限制拷贝构造函数的生成。
class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p) = delete;
private:bit::string _name;int _age;
};
final与override关键字
final与override关键字在继承和多态章节已经进行了详细讲解。
可变参数模板
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。
案例:
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
上面的参数args前面有省略号,所以它就是一个可变模版参数,把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。语法不支持使用args[i]这样方式获取可变参数,也就是无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
template <class ...Args>
void ShowList(Args... args)
{//sizeof...(args)可以帮我们获取参数包的参数个数cout<<sizeof...(args)<<endl;
}
int main()
{ShowList();ShowList(1);ShowList(1,'A','acd');return 0;
}
递归函数方式展开参数包
// 递归终止函数
template <class T>
void ShowList()
{cout << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(const T& value, Args... args)
{cout << value <<" ";ShowList(args...);
}
初始化列表展开参数包
这种展开参数包的方式,不需要通过递归终止函数,是直接在函数体中展开的,这种就地展开参数包的方式实现的关键是初始化列表,通过初始化列表来初始化一个变长数组。在构造int数组的过程中就将参数包{printarg(args)…}会展开成(printarg(arg1)),(printarg(arg2)), (printarg(arg3))…,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
template <class T>
int PrintArg(T t)
{cout << t << " ";return 0;
}
// 展开函数
template <class... Args>
void ShowList(Args... args)
{int arr[] = {PrintArg(args)...};cout << endl;
}
emplace
C++中emplace系列的接口支持模板的可变参数,并且万能引用。
int main()
{std::list< std::pair<int, char> > mylist;// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别mylist.emplace_back(10, 'a');mylist.emplace_back(20, 'b');mylist.emplace_back(make_pair(30, 'c'));mylist.push_back(make_pair(40, 'd'));mylist.push_back({ 50, 'e' });for (auto e : mylist)cout << e.first << ":" << e.second << endl;return 0;
}
pushback是构造+拷贝构造(左值)/移动构造(右值)。emplace_back支持可变参数,直接构造。效率稍微高一些,但是pushback支持移动构造后,差别也不是很大。
lambda表达式
lambda表达式也叫做匿名函数。之前提到的函数指针、仿函数/函数对象是能像函数一样使用的对象,lambda表达式也可以同样做到。
- 在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。
#include <algorithm>
#include <functional>
int main()
{int array[] = {4,1,8,5,3,7,0,9,2,6};// 默认按照小于比较,排出来结果是升序std::sort(array, array+sizeof(array)/sizeof(array[0]));// 如果需要降序,需要改变元素的比较规则std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());return 0;
}
- 如果待排序元素为自定义类型,需要用户定义排序时的比较规则:
struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价Goods(const char *str, double price, int evaluate): _name(str), _price(price), _evaluate(evaluate){}
};
struct ComparePriceLess
{bool operator()(const Goods &gl, const Goods &gr){return gl._price < gr._price;}
};
struct ComparePriceGreater
{bool operator()(const Goods &gl, const Goods &gr){return gl._price > gr._price;}
};
int main()
{vector<Goods> v = {{"苹果", 2.1, 5}, {"香蕉", 3, 4}, {"橙子", 2.2, 3}, {"菠萝", 1.5, 4}};sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());
}
随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。
lambda表达式概念
int main()
{vector<Goods> v = {{"苹果", 2.1, 5}, {"香蕉", 3, 4}, {"橙子", 2.2, 3}, {"菠萝", 1.5, 4}};sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2){ return g1._price < g2._price; });sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2){ return g1._price > g2._price; });sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2){ return g1._evaluate < g2._evaluate; });sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2){ return g1._evaluate > g2._evaluate; });
}
上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函数。
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type {statement}
-
[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
-
(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
-
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
-
->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
-
{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
-
在lambda函数定义中,参数列表和返回值类型都是可省略部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
案例:
int main()
{// 两数相加的lambdaauto add1 = [](int a, int b) -> int{ return a + b; };cout << add1(1, 2) << endl;// 交换变量的lambdaint x = 0;int y = 1;auto swap1 = [](int&x1,int&x2){int tmp=x1;x1=x2;x2=tmp;};swap1(x,y);// 不传参数交换xy的lambda----捕捉列表// 但是默认捕捉过来的变量不能修改,可以加mutale修改变量// 但是是传值捕捉,实参不会改变auto swap2 = [x,y]()mutable{int tmp=x;y=x;x=tmp;};
}
通过上述例子可以看出,lambda表达式实际上可以理解为匿名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。
捕获列表说明
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
- [var]:表示值传递方式捕捉变量var
- [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]:表示值传递方式捕捉当前的this指针
注意事项:
-
a. 父作用域指包含lambda函数的语句的当前栈帧。
-
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 -
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复 -
d. 在块作用域以外的lambda函数捕捉列表必须为空。
-
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,或者全局变量。
-
f. lambda表达式之间不能相互赋值,即使看起来类型相同。
仿函数/函数对象与lambda表达式
class Rate
{
public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){return money * _rate * year;}private:double _rate;
};
int main()
{// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lamberauto r2 = [=](double monty, int year) -> double{return monty * rate * year;};r2(10000, 2);return 0;
}
函数对象/又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象。从使用方式上来看,函数对象与lambda表达式完全一样。函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。
包装器
function包装器
function包装器也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。通过下面的程序验证,我们会发现useF函数模板实例化了三份(因为打印出来的count都是1,并且地址也都不相同),会导致模板的效率低下。
// useF可能是函数指针、函数对象(仿函数对象)、是lamber表达式对象
// 所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下!
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;return 0;
}
头文件<functional>// 类模板原型如下
template <class T> function;
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:Ret: 被调用函数的返回类型Args…:被调用函数的形参
#include <functional>
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()
{// 函数名std::function<double(double)> func1 = f;cout << useF(func1, 11.11) << endl;// 函数对象std::function<double(double)> func2 = Functor();cout << useF(func2, 11.11) << endl;// lamber表达式std::function<double(double)> func3 = [](double d) -> double{ return d / 4; };cout << useF(func3, 11.11) << endl;return 0;
}
包装器可以很好的解决该的问题,此时模板就不会实例化三份useF函数,而打印的count地址都是同一个。由于函数调用可以使用函数名、函数指针、函数对象或有名称的lambda表达式,可调用类型太丰富导致模板的效率极低,而包装器使用统一的方式保存一个可调用对象,用于解决函数调用效率低的问题。
- 但是注意类的成员函数(静态成员函数除外)在在使用包装器的时候,要传递类型,等号右边要使用引用。
例如:
// 函数名(函数指针)
std::function<int(int, int)> func1 = f;
cout << func1(1, 2) << endl;
// 函数对象/仿函数
std::function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl;
// lamber表达式
std::function<int(int, int)> func3 = [](const int a, const int b)
{return a + b; };
cout << func3(1, 2) << endl;
// 类的成员函数
std::function<double(Plus, double, double)> func4 = &Plus::plusd;
cout << func4(Plus(), 1.1, 2.2) << endl;
包装器的其他一些场景
class Solution
{
public:int evalRPN(vector<string> &tokens){stack<int> st;map<string, function<int(int, int)>> opFuncMap ={{"+", [](int i, int j){ return i + j; }},{"-", [](int i, int j){ return i - j; }},{"*", [](int i, int j){ return i * j; }},{"/", [](int i, int j){ return i / j; }}};for (auto &str : tokens){if (opFuncMap.find(str) != opFuncMap.end()){int right = st.top();st.pop();int left = st.top();st.pop();st.push(opFuncMap[str](left, right));}else{st.push(stoi(str));}}return st.top();}
};
bind
std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器,接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。
// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
案例:
表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1,
placeholders::_2);
应用
- 绑定死固定的参数
#include <functional>
int Plus(int a, int b)
{return a + b;
}
class Sub
{
public:int sub(int a, int b){return a - b;}
};int main()
{function<int(int,int)> funPlus = Plus;function<int(Sub,int,int)> funSub = &Sub::sub;map<string,function<int(int,int)>> funcMap={{"+",funPlus},{"-",funSub }};return 0;
}
在该情况中,使用map传+的时候需要传入两个参数,传-的时候需要传入三个参数,这就导致无法正确使用map(主要是不知道传几个参数合适)。此时就需要绑定,绑定了第一个参数,此时只需要在传两个参数即可,这是就可以方便的使用map了。
int main()
{function<int(int,int)> funPlus = Plus;//function<int(Sub,int,int)> funSub = &Sub::sub;function<int(int,int)> funSub = bind(&Sub::sub,Sub(),placeholders::_1,placeholders::_2);map<string,function<int(int,int)>> funcMap={{"+",funPlus},//{"-",funSub}{"-",funSub}};return 0;
}
- 参数调整顺序
std::function<int(int, int)> func4 = std::bind(&Sub::sub, s,
placeholders::_2, placeholders::_1);
cout << func3(1, 2) << endl;
cout << func4(1, 2) << endl;
相关文章:
深入浅出C++ ——C++11
文章目录 一、C11简介二、列表初始化二、声明四、范围for循环五、STL中的变化六、右值引用和移动语义1. 什么是左值?什么是左值引用?2. 左值引用与右值引用比较3. 右值引用使用场景和意义4. 完美转发 新的类功能默认成员函数类成员变量初始化defaultdele…...
智能座舱3.0阶段,看全球巨头如何打造更具“价值”的第三空间
面向中国这一全球最大的汽车电动化与智能化单一市场,作为全球第七大汽车技术供应商的FORVIA佛瑞亚集团开始全面发力。 在2023年上海国际车展上,FORVIA佛瑞亚携旗下佛吉亚与海拉一系列突破性技术和互动体验亮相,展示了对电气化与能源管理、安…...
【Linux】入门介绍
🌱博客主页:大寄一场. 🌱系列专栏:Linux 😘博客制作不易欢迎各位👍点赞⭐收藏➕关注 目录 前言 Linux背景介绍 1.发展史 UNIX发展的历史 Linux发展历史 2. 开源 3. 官网 4. 企业应用现状 5. 发行版…...
【Python】序列类型②-元组
文章目录 1.元组简介2.元组的定义2.1定义只有一个元素的元组 3.元组的下标访问4.元组的常用方法5.使用in判断是否存在元素6.多元赋值操作 1.元组简介 元组和列表一样可以存放多个,不同数据类型的元素 与列表最大的不同就是:列表是可变的,而元组不可变 2.元组的定义 元组的定义:…...
循环的数字
循环的数字 题目描述 你曾经因为看见一样的东西一遍又一遍地重复、循环而对电视节目感到厌烦么?好吧,虽然我并不关心电视节目的好坏,不过有时却也很像那样不断循环的数字。 让我们假定两个不同的正整数 ( n , m ) (n, m) (n,m) 是循环的&…...
MySQL查询之聚合函数查询
0. 数据源 student.sql文件。 /*Navicat Premium Data TransferSource Server : localhost_3306Source Server Type : MySQLSource Server Version : 80016Source Host : localhost:3306Source Schema : testdbTarget Server Type : MySQLTa…...
普通2本,去过字节外包,到现在年薪25W+的测试开发,我的2年转行心酸经历...
个人简介 我是一个普通二本大学机械专业毕业,17年毕业,19年转行,目前做IT行业的软件测试已经有3年多,职位是高级测试工程师,坐标上海… 我想现在我也有一点资格谈论关于转行这个话题;希望你在决定转行之前…...
util.callbackify
util.callbackify(original) 将 async 异步函数(或者一个返回值为 Promise 的函数)转换成遵循异常优先的回调风格的函数,例如将 (err, value) > ... 回调作为最后一个参数。 在回调函数中,第一个参数为拒绝的原因(如…...
解决使用CLIP模型时TypeError: Cannot handle this data type: (1, 1, 224, 224), |u1
想提供Huggingface的transformer库实现多模态模型CLIP的推断,结果报错 (myenv) rootd27d1ff1836c:/home/model_test# python3 CLIP.py ftfy or spacy is not installed using BERT BasicTokenizer instead of ftfy. Traceback (most recent call last): File “/hom…...
Mysql第二章 多表查询的操作
这里写自定义目录标题 一 外连接与内连接的概念sql99语法实现 默认是内连接sql99语法实现左外连接,把没有部门的员工也查出来sql99语法实现右外连接,把没有人的部门查出来sql99语法实现满外连接,mysql不支持这样写mysql中如果要实现满外连接的…...
ESP32-CAM:TinyML 图像分类——水果与蔬菜
目录 故事 硬件参数: 在 Arduino IDE 上安装 ESP32-Cam 使用 BLINK 测试电路板 测试无线网络 运行您的 Web 服务器 水果与蔬菜-图像分类 下载数据集 使用 Edge Impulse Studio 训练模型...
如何防止订单重复支付
想必大家对在线支付都不陌生,今天和大家聊聊如何防止订单重复支付。 看看订单支付流程 我们来看看,电商订单支付的简要流程: 订单钱包支付流程 从下单/计算开始: 下单/结算:这一步虽然不是直接的支付起点,但…...
不是那么快乐的五一
大家好,我是记得诚。 五一假期结束了,明天开始上班了。 这个假期没休息好,也没出去玩。 放假前一天,接到通知让加班。 第一天就去公司加班了,属实很难受,我心想如果别人有了出远门的安排,还…...
Maven命令和配置详解
Maven命令和配置详解 1. pom基本结构2. build基本结构3. Maven命令详解3.1 打包命令3.2 常用命令3.3 批量修改版本-父子pom4. Maven配置详解4.1 settings.xml4.2 项目内的maven工程结构Maven POM构建生命周期工程实践1. pom基本结构 <?xml versi...
P3029 [USACO11NOV]Cow Lineup S 双指针 单调队列
“五一”小长假来了趟上海,在倒数第二天终于有时间做了一会儿题目,A了之后过来写一篇题解 【问题描述】 农民约翰雇一个专业摄影师给他的部分牛拍照。由于约翰的牛有好多品种,他喜欢他的照片包含每个品种的至少一头牛。 约翰的牛都站在一条沿…...
数据结构与算法之链表: Leetcode 83. 删除排序链表中的重复元素 (Typescript版)
删除排序链表中的重复元素 https://leetcode.cn/problems/remove-duplicates-from-sorted-list/ 描述 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 示例 1 输入:head [1,1,2] 输出&…...
ubuntu16.04升级到20.04后报错 By not providing “FindEigen.cmake“
编译问题: CMake Error at modules/perception/lidar/CMakeLists.txt:14 (find_package): By not providing "FindEigen.cmake" in CMAKE_MODULE_PATH this project has asked CMake to find a package configuration file provided by "Eigen&…...
设计模式——模板方法模式
是什么? 在我们的实际开发中尝尝会遇到这种问题:在设计一个系统时知道了算法所需要的关键步骤,而且确定了这些步骤的执行顺序,但是某些步骤的具体实现还不知道,或者说某些步骤的实现与具体的环境相关,例如每…...
15 | Qt的自定义信号
1 前提 Qt 5.14.2 2 具体操作 2.1 自定义信号 2.1.1 UI界面设置 2.1.1.1 widget.ui 2.1.1.2 setdialog.ui 2.1.2 headers 2.1.2.1 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui {class Widget; } QT_END_NAMESP…...
线性表,顺序表,链表
线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列 线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条直线 …...
洛谷 P2782 友好城市 线性DP 最长上升子序列 二分查找 lower_bound
🍑 算法题解专栏 🍑 洛谷:友好城市 题目描述 有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。每对…...
easyexcel读取excel合并单元格数据
普通的excel列表,easyexcel读取是没有什么问题的。但是,如果有合并单元格,那么它读取的时候,能获取数据,但是数据是不完整的。如下所示的单元格数据: 我们通过简单的异步读取,最后查看数据内容&…...
2023哪款蓝牙耳机性价比高?200左右高性价比蓝牙耳机推荐
现如今的蓝牙耳机越来越多,人们在选择时不免纠结,不知道选什么蓝牙耳机比较好?针对这个问题,我来给大家推荐几款性价比高的蓝牙耳机,一起来看看吧。 一、南卡小音舱Lite2蓝牙耳机 参考价:299 蓝牙版本&am…...
Java代码弱点与修复之——Masked Field(掩码字段)
弱点描述 MF: Masked Field (FB.MF_CLASS_MASKS_FIELD) 是 FindBugs 代码分析工具的一个警告信息, 属于中风险的代码弱点。 Masked Field,翻译过来是掩码字段, 字段可以理解为属性, 那么掩码是什么意思呢? 掩码是什么? 掩码是一串二进制代码对目标字段进行位与运算,屏…...
C语言编程入门之刷题篇(C语言130题)(8)
(题目标题可以直接跳转此题链接) BC72 平均身高 描述 从键盘输入5个人的身高(米),求他们的平均身高(米)。 输入描述: 一行,连续输入5个身高(范围0.00~2.00…...
QML动画类型总结
目录 一 常用动画 二 特殊场景动画 一 常用动画 有几种类型的动画,每一种都在特定情况下都有最佳的效果,下面列出了一些常用的动画: 1、PropertyAnimation(属性动画)- 使用属性值改变播放的动画; 2、Num…...
编译一个魔兽世界开源服务端Windows需要安装什么环境
编译一个魔兽世界开源服务端Windows需要安装什么环境 大家好我是艾西,去年十月份左右wy和bx发布了在停服的公告。当时不少小伙伴都在担心如果停服了怎么办,魔兽这游戏伴随着我们渡过了太多的时光。但已经发生的事情我们只能顺其自然的等待GF的消息就好了…...
HTML5字体集合的实践经验
随着互联网的发展,网站已成为人们获取信息和交流的重要平台。而一个好的网站,不仅需要有美观的界面,还需要有良好的用户体验。其中,字体是影响用户体验的一个重要因素。下面就让我们来看看HTML字体集合的相关内容。 HTML字体集合是…...
Mybatis 框架 ( 一 ) 基本步骤
1.概念 1.1.什么是Mybatis框架 (1)Mybatis是一个半ORM(Object Relation Mapping 对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、…...
【华为OD机试真题】We Are A Team(C++javapython)100%通过率 超详细代码注释 代码优化
We Are A Team 题目描述: 总共有n个人在机房,每个人有一个标号(1<=标号<=n) ,他们分成了多个团队,需要你根据收到的m条消息判定指定的两个人是否在 一个团队中,具体的: 1、消息构成为abc,整数a、b分别代表两个人的标号,整数C代表指令 2、c = = 0 代表a和b在一…...
株洲高端网站建设工作室/天眼查询个人信息
jQuery代码段可获取当前网页的完整标题并将其存储在变量中,以与其他脚本一起使用。 这是您在浏览器标题上看到的标题。 Current page title: mytitle .安迪建议的另一种方式。 var current_href $(location).attr(href); var current_title $(document).attr(titl…...
哈尔滨网站域名部门/网上永久视频会员是真的吗
您是否发现自己经常说“我稍后再做”?“先刷下抖音”?如果是这样,我能感受到你患有严重的拖延症,今天我们来聊聊七种最实用的方法来缓解你的拖延症。 我们为自己可以完成的事情而完不了而后悔,确实是一种压力很大的生…...
海外建站推广/app推广软文范文
上篇博客中,已经实现了一个Struts例子,现在就来分析下这个例子。详细的解读Struts1。 Struts1的几个核心组件是值得我们注意的: (1)ActionServlet(核心控制器)。 (2)ActionForm(接收页面中传…...
go语言做的网站/北京出大大事了
如果想从头学起Cypress,可以看下面的系列文章哦 https://www.cnblogs.com/poloyy/category/1768839.html .focus() 命令 聚焦 DOM 元素 语法格式 .focus() .focus(options) 正确写法 cy.get(input).first().focus() 重点 必须是 DOM 元素才能调用 .focus() 方法&am…...
wordpress创建表格/百度知道入口
前言 数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷。 也因如此,它作为博主大二上学期最重要的必…...
建设产品网站/怎样在百度上发帖子
前缀是标记到CSS属性开头的特定于供应商的名称。 例如,您需要以下代码将元素旋转10: -moz-transform: rotate(10deg); /* Firefox 3.5 */ -o-transform: rotate(10deg); /* Opera 10.5 */ -webkit-transform: rotate(10deg); /* Chrome and Safari3.1 …...