文章目录
现代c++急急急——functional、bind、lambda
bind前世今生
bind1st:operator()的第一个参数成为一个既定的值
bind2nd:operator()的第二个参数成为一个既定的值
从一个排序的例子开始
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;
template<class T>
void MyPrint(T& container)
{
for (auto x : container)
{
cout << x << ' ';
}
cout << endl;
}
int main()
{
vector<int> vec;
srand(time(NULL));
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
MyPrint(vec);
sort(vec.begin(), vec.end());
//sort(vec.begin(), vec.end(), greater<int>());
MyPrint(vec);
return 0;
}
以上代码生成一个vector并从小到大排序
sort第三个参数支持自定义的排序方法,这里我们传入的是greater,实现从大到小排序,greater实现两个元素之间的比较
那么现在需要把70插入容器中,要求容器顺序不变,该怎么做?
在STL中有一个find_if
可以在遍历某个容器的时候,按某个条件查找,比如说本次要求,只需要挨个拿容器中的某个元素和70进行比较,返回bool即可
但是我们注意到,find_if
拿一个元素和一个既定的元素做比较,而greater或者less的都是拿两个元素做比较,有没有办法实现greater其中的一个元素固定
下来呢?
这就是绑定器的作用,比如说让greater的第一个元素固定为70
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;
template<class T>
void MyPrint(T& container)
{
for (auto x : container)
{
cout << x << ' ';
}
cout << endl;
}
int main()
{
vector<int> vec;
srand(time(NULL));
int num = 0;
cin >> num;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
MyPrint(vec);
sort(vec.begin(), vec.end(), greater<int>());
auto res = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), num));
if (res != vec.end())
{
vec.insert(res, num);
}
MyPrint(vec);
return 0;
}
同理,bind2nd也是一样的效果
可能的实现
find_if
// 取自cppreference
template<class InputIterator, class UnaryPredicate>
InputIterator find_if (InputIterator first, InputIterator last, UnaryPredicate pred)
{
while (first!=last) {
if (pred(*first)) return first;
++first;
}
return last;
}
bind1st
template<class Compare, class T>
class _MyBind1st
{
public:
_MyBind1st(Compare cmp, T val) : _cmp(cmp), _val(val) {};
bool operator()(const T& secondElement)
{
return _cmp(_val, secondElement);
}
private:
Compare _cmp;
T _val;
};
template<class Compare, class T>
_MyBind1st<Compare, T> myBind1st(Compare cmp, const T& val)
{
return _MyBind1st<Compare, T>(cmp, val);
}
最终效果
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;
template<class T>
void MyPrint(T& container)
{
for (auto x : container)
{
cout << x << ' ';
}
cout << endl;
}
template<class Compare, class T>
class _MyBind1st
{
public:
_MyBind1st(Compare cmp, T val) : _cmp(cmp), _val(val) {};
bool operator()(const T& secondElement)
{
return _cmp(_val, secondElement);
}
private:
Compare _cmp;
T _val;
};
template<class Compare, class T>
_MyBind1st<Compare, T> myBind1st(Compare cmp, const T& val)
{
return _MyBind1st<Compare, T>(cmp, val);
}
int main()
{
vector<int> vec;
srand(time(NULL));
int num = 0;
cin >> num;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
MyPrint(vec);
sort(vec.begin(), vec.end(), greater<int>());
auto res = find_if(vec.begin(), vec.end(), myBind1st(greater<int>(), num));
if (res != vec.end())
{
vec.insert(res, num);
}
MyPrint(vec);
return 0;
}
运行结果正确,由此可知,绑定器是函数对象的一种用法,使得某个函数的某个参数成为既定的参数(既定的意思并不是说右值)
今生
#include <iostream>
#include <functional>
#include <string>
using namespace std;
void Say(string str)
{
cout << str << endl;
}
int main()
{
auto res = bind(Say, "hello");
res();
return 0;
}
与1st和2nd不同的是,bind可以使得任意一个参数绑定为指定的参数,而不是固定的位置(如1st只能第一个参数变成指定的参数)
当然,bind也有1st和2nd的功能,也就是20个占位符,用于既定的位置
的参数指定成给定的参数
(很绕,我知道,这句话看一遍就好,不要深陷其中,看下面的例子)
一个实际的例子:
#include <iostream>
#include <functional>
#include <string>
using namespace std;
void Say(string str)
{
cout << str << endl;
}
int main()
{
// 指明Say函数的第一个参数(指定的位置)由用户指定
auto res = bind(Say, placeholders::_1);
res("hello");
return 0;
}
以上效果便是第一个参数被指定为由用户给出的参数
函数对象
从hello开始
#include <iostream>
#include <functional>
#include <string>
using namespace std;
void SayStr(string str)
{
cout << str << endl;
}
void Say()
{
cout << "hello" << endl;
}
int main()
{
function<void()> funcObj = Say;
function<void(string)> funcObj2 = SayStr;
funcObj();
funcObj2("hello");
return 0;
}
可以看到,函数对象有点类似于函数指针,但是背后原理其实是模板类,重载了()
运算符,只是效果类似于函数指针,可以“指向”函数(注意有一个引号)
那么有趣的就是,函数这个概念,除了普通的函数,bind和lambda表达式都可以理解为返回了一个函数
除此之外,其实还有类内函数(比较重要的OOP手法)
#include <iostream>
#include <functional>
#include <string>
using namespace std;
class Say
{
public:
void SayHello(string str) { cout << str << endl; };
};
int main()
{
function<void(Say*, string)> funcObj = &Say::SayHello;
Say tmpObj;
funcObj(&tmpObj, "hello world");
return 0;
}
通过以上例子,可以看出要使得functional可以“指向”类内函数,需要满足以下的条件
- 类内函数为Public
- 实例化function对象的函数签名需要有一个类的指针(也就是this指针)
- 需要一个具体的类的对象
那么有趣的是,bind的参数也是一个函数,因此也可以通过传入的参数“指向”某个类的成员函数,这么做的好处是什么?
有点类似友元函数 + 回调函数的概念了,可以在不同的地方回调这个函数,极大的增加了代码的复用
可能的实现
#include <iostream>
#include <string>
#include <functional>
using namespace std;
void Say(string str)
{
cout << str << endl;
}
template<class Fty>
class myFunction {};
template<class R, class Arg>
class myFunction<R(Arg)>
{
public:
using pFunc = R(*)(Arg);
myFunction(pFunc func) : func_(func) {};
R operator() (Arg arg)
{
return func_(arg);
};
private:
pFunc func_;
};
int main()
{
myFunction<void(string)> func(Say);
func("hello world");
return 0;
}
这里可以看出其实就是使用了函数指针来实现的,唯一的差别就是,这个实现只是一个参数,官方实现应该是可变参
// c++ insights的结果更容易看出实现
#include <iostream>
#include <string>
#include <functional>
using namespace std;
void Say(std::basic_string<char> str)
{
std::operator<<(std::cout, str).operator<<(std::endl);
}
template<class Fty>
class myFunction
{
};
/* First instantiated from: insights.cpp:31 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class myFunction<void (std::basic_string<char>)>
{
public:
using pFunc = void (*)(std::basic_string<char>);
inline myFunction(pFunc func)
: func_{func}
{
}
inline void operator()(std::basic_string<char> arg)
{
return this->func_(std::basic_string<char>(arg));
}
private:
pFunc func_;
public:
};
#endif
template<class R, class Arg>
class myFunction<R (Arg)>
{
public:
using pFunc = R (*)(Arg);
inline myFunction(pFunc func)
: func_{func}
{
}
inline R operator()(Arg arg)
{
return this->func_(arg);
}
private:
pFunc func_;
};
int main()
{
myFunction<void (std::basic_string<char>)> func = myFunction<void (std::basic_string<char>)>(Say);
func.operator()(std::basic_string<char>("hello world", std::allocator<char>()));
return 0;
}
lambda
语法格式:
[]<class T>()->{};
[]:捕获列表
<class T>
:c++20新增的,也就是lambda表达式模板
():函数参数列表
->:函数返回类型,c++11的语法,后置返回类型,也有叫尾置返回
{}:函数体
注意,有一个分号
再注意,lambda表达式模板是c++20的语法,此前的c++标准是没有的
lambda表达式的实现原理其实就是模板类,重载了()
运算符
例子
从hello开始
#include <iostream>
#include <functional>
#include <string>
using namespace std;
void Say(string str)
{
cout << str << endl;
}
int main()
{
auto res = []()->void { cout << "hello" << endl; };
res();
return 0;
}
// c++ inights的结果
#include <iostream>
#include <functional>
#include <string>
using namespace std;
void Say(std::basic_string<char> str)
{
std::operator<<(std::cout, str).operator<<(std::endl);
}
int main()
{
class __lambda_14_13
{
public:
inline /*constexpr */ void operator()() const
{
std::operator<<(std::cout, "hello").operator<<(std::endl);
}
using retType_14_13 = auto (*)() -> void;
inline constexpr operator retType_14_13 () const noexcept
{
return __invoke;
};
private:
static inline /*constexpr */ void __invoke()
{
__lambda_14_13{}.operator()();
}
};
__lambda_14_13 res = __lambda_14_13{};
res.operator()();
return 0;
}
可以看到,lambda本质就是一个模板类 + 重载()
运算符
带函数参数的lambda
#include <iostream>
#include <functional>
#include <string>
using namespace std;
void Say(string str)
{
cout << str << endl;
}
int main()
{
auto res = [](int a, int b)->int { return a + b; };
cout << res(1, 2) << endl;
return 0;
}
捕获外界变量的lambda
#include <iostream>
#include <functional>
#include <string>
using namespace std;
void Say(string str)
{
cout << str << endl;
}
int val;
int main()
{
int a = 0, b = 0;
cin >> a >> b;
// 值捕获
auto res = [a, b]()->int { return a + b; };
cout << res() << endl;
// 引用捕获
auto ans = [&a, &b]()->int { a++, b++; return a + b; };
cout << ans() << endl;
// 值捕获,但是捕获当前作用域所有变量:局部和全局
auto cnt = [=]()->int { return val + b; };
cout << cnt() << endl;
// 引用捕获,但是捕获当前作用域所有变量:局部和全局
auto tmp = [&]()->int { return val + b; };
cout << tmp() << endl;
return 0;
}
关于捕获列表,还有其他花活,比如说捕获this指针,值捕获,但是某些变量引用捕获以及一个比较重要的:值捕获,但是可以修改捕获的变量(mutable),有兴趣可以自行了解
至于尾置返回类型,是可以省略的,如果省略了,就默认返回void