系列文章目录
文章目录
前言
介绍了C++11前半部分的特性之后,紧接着介绍后面的内容。
一、可变模板参数
1.1 什么是可变参数模板
第一次接触可变参数是在c语言使用printf
时的可变参数。
C++11
的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03
,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。
我们学习基本的使用方法。
下面就是一个基本可变参数的函数模板:
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
上面的参数args
前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args
中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]
这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。
在没有可变模板参数时,我们通常像下面一样写类型不同,类型的个数固定的模板:
template<class T>
void ShowList(T&& x)
{
//...
}
而可变模板参数的类型个数可变,类型也可变。
int main()
{
ShowList();
ShowList(1);
ShowList(1,"111");
ShowList(1,"111",2.2);
return 0;
}
可以通过sizeof
来查看可变模板参数的参数个数:
template<typename ...Args>
void ShowList(Args...args)
{
cout << sizeof...(args) << endl; //注意这里的...在括号外,比较特殊
}
int main()
{
ShowList();
ShowList(1);
ShowList(1, "111");
ShowList(1, "111", 2.2);
return 0;
}
运行结果:
1.2 如何打印可变模板参数的内容
像下面这样进行打印是不行的:
template<typename ...Args>
void ShowList(Args...args)
{
cout << sizeof...(args) << endl;
for (size_t i = 0; i < sizeof...(args); i++)
{
cout << args[i] << " ";
}
cout << endl;
}
c语言
的可变参数是类似数组的方式存储的,可以理解为在运行时解析多个参数。
而c++
这一部分是模板,模板是在编译时进行推导。所以这里是在编译阶段进行解析多个参数所以上面的代码是运行时逻辑,逻辑是走不通的。
那如何进行解析呢?
递归函数方式展开参数包
//递归终止函数
void Print()
{
cout << endl;
}
template<class T, class ...Args>
void Print(T x, Args... args)
{
cout << x << " ";
Print(args...); //参数包往下传时... 在后面
}
template<class ...Args>
void ShowList(Args...args)
{
Print(args...);
}
int main()
{
ShowList();
ShowList(1);
ShowList(1, "111");
ShowList(1, "111", 2.2);
return 0;
}
运行结果:
编译时逻辑就像下图一样,拿三个参数举例:
模板实例化出了多个不同的参数个数的函数:
这样我们就可以打印n个参数的内容。就像是模板的模板一样。支持多个参数。
逗号表达式展开参数包
这种展开参数包的方式,不需要通过递归终止函数,是直接在expand
函数体中展开的, printarg
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
expand
函数中的逗号表达式:(printarg(args), 0)
,也是按照这个执行顺序,先执行printarg(args)
,再得到逗号表达式的结果0
。同时还用到了C++11
的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}
将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc... )
,最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]
。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数,也就是说在构造int数组
的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包.
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
template<class ...Args>
void ShowList(Args...args)
{
int arr[] = { (PrintArg(args),0)... };
cout << endl;
}
int main()
{
//ShowList(); //注意没有参数为0的函数
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
数组arr中实际的值都是0.可以使用返回值的形式进行构造数组arr:
template <class T>
int PrintArg(T t)
{
cout << t << " ";
return 0;
}
template<class ...Args>
void ShowList(Args...args)
{
int arr[] = { PrintArg(args)... };
cout << endl;
}
注意模板ShowList实例化出的就是:
单纯打印就可以这样:
template<class ...Args>
void ShowList(Args...args)
{
int arr[] = { (cout << args << " ",0)...};
cout << endl;
}
逻辑上通过。
STL容器中的empalce相关接口函数:
http://www.cplusplus.com/reference/vector/vector/emplace_back/
http://www.cplusplus.com/reference/list/list/emplace_back/
template <class... Args>
void emplace_back (Args&&... args);
首先我们看到的emplace
系列的接口,支持模板的可变参数,并且万能引用。那么相对insert
和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;
}
下面是我们自己实现的string
接口:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
#include <assert.h>
namespace bit {
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{return _str;}
iterator end()
{return _str + _size;}
const_iterator begin() const
{return _str;}
const_iterator end() const
{return _str + _size;}
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
cout << "string(char* str)" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// s1.swap(s2)
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 深拷贝" << endl;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
if (this != &s)
{
_str[0] = '\0';
_size = 0;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
return *this;
}
// 移动构造
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;
}
//n个val构造
string(size_t n, char ch = '\0')
:_str(nullptr)
,_capacity(0)
,_size(0)
{
cout << "string(size_t n, char ch = '\\0')" << endl;
reserve(n);
for (size_t i = 0; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
~string()
{
delete[] _str;
_str = nullptr;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
if (_str)
{
strcpy(tmp, _str);
delete[] _str;
}
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
//string operator+=(char ch)
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0; // 不包含最后做标识的\0
};
}
#include <list>
int main()
{
// 下面我们试一下带有拷贝构造和移动构造的bit::string,再试试呢
// 我们会发现其实差别也不到,emplace_back是直接构造了,push_back
// 是先构造,再移动构造,其实也还好。
std::list< std::pair<int, bit::string> > mylist;
mylist.emplace_back(10, "sort");
mylist.emplace_back(make_pair(20, "sort"));
mylist.push_back(make_pair(30, "sort"));
mylist.push_back({ 40, "sort"});
return 0;
}
int main()
{
list<bit::string> lt;
bit::string s1("1111111");
lt.emplace_back(s1);
lt.emplace_back(move(s1));
// 直接构造string参数包往下传,直接用string参数包构造string
lt.emplace_back("1111111");
lt.emplace_back(10, 'x');
cout << endl << endl;
list <pair<bit::string, int>> lt1;
// 构造pair + 拷贝/移动构造pair到list节点中的data上
pair<bit::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
lt1.emplace_back(move(kv));
cout << endl << endl;
// 直接构造pair的参数包往下传,直接用pair参数包构造pair
lt1.emplace_back("苹果", 1);
return 0;
}
1.3 emplace_back的实现
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())
:_next(nullptr)
, _prev(nullptr)
, _data(data)
{}
ListNode(T&& data)
:_next(nullptr)
, _prev(nullptr)
, _data(move(data))
{}
template <class... Args> //可变模板参数
ListNode(Args&&... args)
: _next(nullptr)
, _prev(nullptr)
, _data(std::forward<Args>(args)...) //右值会退化
{}
};
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
ListIterator(Node* node)
:_node(node)
{}
// ++it;
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
iterator begin()
{return iterator(_head->_next);}
const_iterator begin() const
{return const_iterator(_head->_next);}
iterator end()
{return iterator(_head);}
const_iterator end() const
{return const_iterator(_head);}
void empty_init()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
list()
{empty_init();}
list(initializer_list<T> il)
{
empty_init();
for (const auto& e : il)
push_back(e);
}
// lt2(lt1)
list(const list<T>& lt)
{
empty_init();
for (const auto& e : lt)
push_back(e);
}
// lt1 = lt3
list<T>& operator=(list<T> lt)
{
swap(_head, lt._head);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
auto it = begin();
while (it != end())
it = erase(it);
}
void push_back(const T& x)
{
insert(end(), x);
}
void push_back(T&& x)
{
insert(end(), move(x));
}
void pop_back()
{
erase(--end());
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_front()
{
erase(begin());
}
// 没有iterator失效
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* newnode = new Node(x);
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
iterator insert(iterator pos, T&& x)
{
Node* cur = pos._node;
Node* newnode = new Node(move(x));
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
template <class... Args> //可变模板参数
void emplace_back(Args&&... args)
{
insert(end(), std::forward<Args>(args)...); //右值会退化
}
// 原理:编译器根据可变参数模板生成对应参数的函数
/*void emplace_back(string& s)
{
insert(end(), std::forward<string>(s));
}
void emplace_back(string&& s)
{
insert(end(), std::forward<string>(s));
}
void emplace_back(const char* s)
{
insert(end(), std::forward<const char*>(s));
}
void emplace_back(size_t n, char ch)
{
insert(end(), std::forward<size_t>(n), std::forward<char>(ch));
}*/
template <class... Args>
iterator insert(iterator pos, Args&&... args)
{
Node* cur = pos._node;
Node* newnode = new Node(std::forward<Args>(args)...); //右值会退化
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
// erase 后 pos失效了,pos指向节点被释放了
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
return iterator(next);
}
private:
Node* _head;
};
测试代码:
int main()
{
bit::list<bit::string> lt;
bit::string s1("1111111");
lt.emplace_back(s1);
lt.emplace_back(move(s1));
// 直接构造string参数包往下传,直接用string参数包构造string
lt.emplace_back("1111111");
lt.emplace_back(10, 'x');
cout << endl << endl;
bit::list <pair<bit::string, int>> lt1;
// 构造pair + 拷贝/移动构造pair到list节点中的data上
pair<bit::string, int> kv("苹果", 1);
lt1.emplace_back(kv);
lt1.emplace_back(move(kv));
cout << endl << endl;
// 直接构造pair的参数包往下传,直接用pair参数包构造pair
lt1.emplace_back("苹果", 1);
return 0;
}
结果和库中的list一样:
1.4 可变模板参数为何高效
减少代码重复:
通过使用可变模板参数,可以将具有相似逻辑但参数数量不同的函数或类模板进行统一处理,避免了为每个参数数量的组合单独编写重复的代码。例如,如果需要对不同数量的整数进行求和操作,使用可变模板参数可以只编写一个函数模板来处理,而不需要为每个可能的参数数量分别编写函数。
template<typename... Args>
int sum(Args... args) {
int total = 0;
(total += args,...);
return total;
}
类型推导:
编译器能够自动推导可变模板参数的类型,这使得代码更加简洁和灵活。在使用时不需要显式指定每个参数的类型,减少了类型声明的繁琐。
性能优化:
在一些情况下,可变模板参数可以在编译时进行更多的优化。例如,编译器可以更好地展开循环、内联函数等,从而提高程序的运行效率。
灵活的接口设计:
它为函数和类提供了更加灵活的接口,允许用户以不同的方式传递参数,增强了代码的通用性和可扩展性。
总的来说,可变模板参数在提高代码的复用性、简洁性和性能方面发挥了重要作用,使得 C++ 编程更加高效和强大。
二、lambda表达式
2.1 C++98中的一个例子
在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。
#include <algorithm>
#include <functional>
int main()
{
int array[] = { 4,1,8,5,3,7,0,9,2,6 };
// 默认按照小于比较,排出来结果是升序 less<int>()
std::sort(array, array + sizeof(array) / sizeof(array[0]));
// 如果需要降序,需要改变元素的比较规则
std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
return 0;
}
如果待排序元素为自定义类型,需要用户定义排序时的比较规则:
#include <algorithm>
#include <functional>
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());
return 0;
}
随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm
算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。
2.2 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; });
}
2.3 lambda表达式语法
lambda
表达式格式:[capture-list] (parameters) mutable -> return-type { statement}
- lambda表达式各部分说明:
[capture-list]
: 捕捉列表,该列表总是出现在lambda
函数的开始位置,编译器根据[]
来判断接下来的代码是否为lambda
函数,捕捉列表能够捕捉上下文中的变量供lambda
函数使用。(parameters)
:参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()
一起省略mutable
:默认情况下,lambda
函数总是一个const
函数,mutable
可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。->returntype
:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导,最好是写上返回值。{statement}
:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
注意:
- 在
lambda
函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为
空。因此C++11中最简单的lambda函数为:[]{}
; 该lambda函数不能做任何事情。
捕捉列表和函数体不能省略不写。
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[] {};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
[] {return 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [](int c) {};
fun1(10);
// 各部分都很完善的lambda函数
auto fun2 = [](int c)->int {return c + 2; };
fun2(10);
return 0;
}
通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto
将其赋值给一个变量。
捕捉列表说明
写一个交换函数:
int main()
{
int a = 1;
int b = 2;
cout << a << ":" << b << endl;
//交换任意整形
auto swap1 = [](int& x, int& y) {
int tmp = x;
x = y;
y = tmp;
};
cout << a << ":" << b << endl;
return 0;
}
运行结果发现结果没有变是怎么回事:
int a = 1;
int b = 2;
//交换a,b
auto swap2 = []() {
int tmp = a;
a = b;
b = tmp;
};
发现表达式里不能使用a,b
。得使用[]
捕捉a,b
:
//交换a,b
auto swap2 = [a, b]() {
int tmp = a;
a = b;
b = tmp;
};
但是又不能修改左值,因为a,b只是拷贝。这时可以用到mutable
:
//使用mutable可以改变函数内的a,b
auto swap2 = [a, b]() mutable {
int tmp = a;
a = b;
b = tmp;
};
swap2(); //未发生改变
cout << a << ":" << b << endl;
但是,a,b还是拷贝,结果还是不会变:
实际上是因为捕捉列表的捕捉方式不对。lambda只能用当前局部域和捕捉的成员。
**默认是传值捕捉,并且使用的是const
修饰的。mutable
虽然去掉了const
但是还是拷贝,不影响外部。
捕捉列表描述了上下文中那些数据可以被lambda
使用,以及使用的方式传值还是传引用。
[var]
:表示值传递方式捕捉变量var (默认)[=]
:表示值传递方式捕获所有父作用域中的变量(包括this)[&var]
:表示引用传递捕捉变量var[&]
:表示引用传递捕捉所有父作用域中的变量(包括this)[this]
:表示值传递方式捕捉当前的this指针(在成员函数里会默认捕捉)
注意这里的&
不是取地址,而是引用。
int a = 1;
int b = 2;
cout << a << ":" << b << endl;
//交换a,b
auto swap2 = [&a,&b]() {
int tmp = a;
a = b;
b = tmp;
};
swap2();
cout << a << ":" << b << endl;
auto swap3 = [&]() {
int tmp = a;
a = b;
b = tmp;
};
swap3();
cout << a << ":" << b << endl;
结果:
有如下几种用法:
int a = 1, b = 2, c = 3, d = 4;
//所有的值,传值捕捉(abcd)
auto func1 = [=] {
return a + b + c + d;
};
//所有的值,传引用捕捉(abcd)
auto func2 = [&] {
a++;
b++;
c++;
d++;
return a + b + c + d;
};
//混合捕捉
auto func3 = [&a, b] {
//a可以修改,影响外部a,
//b不可修改,不影响外面
};
//除b之外的都传值捕捉
auto func4 = [=, &b] {
//b可修改,影响外部
//其他不可修改
};
//。。。
lambda
可以用全局域的变量。不需要捕捉。
注意:
a. 父作用域指包含lambda函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]
:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量[&,a, this]
:值传递方式捕捉变量a和this,引用方式捕捉其他变量
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
d. 在块作用域以外的lambda函数捕捉列表必须为空。
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
f. lambda
表达式之间不能相互赋值,即使看起来类型相同
void (*PF)();
int main()
{
auto f1 = []{cout << "hello world" << endl; };
auto f2 = []{cout << "hello world" << endl; };
// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
//f1 = f2; // 编译失败--->提示找不到operator=()
// 允许使用一个lambda表达式拷贝构造一个新的副本
auto f3(f2);
f3();
// 可以将lambda表达式赋值给相同类型的函数指针
PF = f2;
PF();
return 0;
}
2.4 底层原理(函数对象与lambda表达式)
函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()
运算符的类对象。
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);
// lamber
auto r2 = [=](double monty, int year)->double {return monty * rate * year; };
r2(10000, 2);
return 0;
}
从使用方式上来看,函数对象与lambda表达式完全一样。
函数对象将rate
作为其成员变量,在定义对象时给出初始值即可,lambda
表达式通过捕获列表可以直接将该变量捕获到。
实际在底层编译器对于lambda
表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda
表达式,编译器会自动生成一个类,在该类中重载了operator()
。
虽说是匿名函数,但是编译器自己生成了一个uuid进行构造对象。
所以 lambda
表达式之间不能相互赋值,即使看起来类型相同,是因为uuid
不同。
三、包装器
3.1 function包装器
function
包装器也叫作适配器。C++中的function
本质是一个类模板,也是一个包装器。
它包含在<functional>
头文件中,它可以包装任何类型的可调用对象(1、函数指针,2、仿函数、3、lambda)
那么我们来看看,我们为什么需要function呢?
#include <functional>
int f(int a,int b)
{
return a + b;
}
struct Functor
{
int operator()(int a, int b)
{
return a + b;
}
};
int main()
{
function<int(int, int)> f1 = f;
function<int(int, int)> f2 = Functor();
function<int(int, int)> f3 = [](int a, int b) {return a + b; };
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
return 0;
}
包装器的其他场景:
逆波兰表达式求值
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
- 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
- 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
- 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
class Solution
{
public:
int evalRPN(vector<string>& tokens)
{
stack<int> st;
for (auto& str : tokens)
{
//遇到符号计算
if (str == "+" || str == "-" || str == "*" || str == "/")
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
switch (str[0]) //将结果压入栈
{
case '+':
st.push(left + right);
break;
case '-':
st.push(left - right);
break;
case '*':
st.push(left * right);
break;
case '/':
st.push(left / right);
break;
}
}
else//遇到数字入栈
{
// 1、atoi itoa
// 2、sprintf scanf
// 3、stoi to_string C++11
st.push(stoi(str));
}
}
return st.top();
}
};
// 使用包装器以后的玩法
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
{
// 1、atoi itoa
// 2、sprintf scanf
// 3、stoi to_string C++11
st.push(stoi(str));
}
}
return st.top();
}
};
注意function
在包装类成员函数时,隐含的this指针要注意:
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
// 类的成员函数
// 包装静态成员函数
std::function<int(int, int)> func4 = &Plus::plusi;
cout << func4(1, 2) << endl;
// 包装普通成员函数
std::function<double(Plus*, double, double)> func5 = &Plus::plusd;
Plus pd;
cout << func5(&pd, 1.1, 2.2) << endl;
std::function<double(Plus, double, double)> func6 = &Plus::plusd;
cout << func6(pd, 1.1, 2.2) << endl;
cout << func6(Plus(), 1.1, 2.2) << endl;
return 0;
}
有了包装器,如何解决模板的效率低下,实例化多份的问题呢?
#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;
}
3.2 bind
std::bind
函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。
// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
调用bind的一般形式:auto newCallable = bind(callable,arg_list)
;
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的 callable
的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list
中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable
的参数,它们占据了传递给newCallable
的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1
为newCallable
的第一个参数,_2
为第二个参数,以此类推。
_N代表第N个实参。
// 使用举例
#include <functional>
int Plus(int a, int b)
{
return a + b;
}
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
using placeholders::_1;
using placeholders::_2;
int main()
{
//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
std::function<int(int, int)> func1 = std::bind(Plus, _1,_2);
//auto func1 = std::bind(Plus, _1, _2);
//func2的类型为 function<void(int, int, int)> 与func1类型一样
//表示绑定函数 plus 的第一,二为: 1, 2
auto func2 = std::bind(Plus, 1, 2);
cout << func1(1, 2) << endl;
cout << func2() << endl;
Sub s;
//将第一个参数固定
auto func3 = std::bind(Plus,1,_1)
cout << func3(2) << endl; // 1 + 2
// 绑定成员函数
std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, _1, _2);
// 参数调换顺序
std::function<int(int, int)> func5 = std::bind(&Sub::sub, s, _2, _1);
cout << func4(1, 2) << endl;
cout << func5(1, 2) << endl;
return 0;
}
如果你有所收获可以留下你的点赞和关注,谢谢你的观看!!!