现代c++急急急——functional、bind、lambda

现代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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值