仿函数
所谓的仿函数(functor),是通过重载()运算符模拟函数形为的类。
因此,这里需要明确两点:
- 仿函数不是函数,它是个类;
- 仿函数重载了()运算符,使得它的对你可以像函数那样子调用(代码的形式好像是在调用函数)。
仿函数比较大小
#include <iostream>
using namespace std;
template<typename T> struct comp{
bool operator()(T in1, T in2) const{
return (in1>in2);
}
};
int main() {
comp<int> m_comp_objext;
cout << m_comp_objext(6, 3) << endl; //使用对象调用
cout << comp<int>()(1, 2) << endl; //使用仿函数实现
return 0;
}
仿函数详细说明
在下面的使用场景(统计一个容器中的符合规定的元素),将说明之前提到的函数指针为什么不能在STL中替换掉仿函数
- 统计容器中小于x的变量个数
#include <iostream>
#include <vector>
#include <algorithm> // 算法包
using namespace std;
template<typename T> struct my_count{
my_count(T a){
threshold = a;
}
T threshold;
bool operator()(T num){
return (num < threshold);
}
};
int main() {
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
std::vector<int> v_a(a, a + 10);
cout << "count: " << std::count_if(v_a.begin(), v_a.end(), my_count<int>(3));
return 0;
}
仿函数当做排序准则
#include <iostream>
#include <string>
#include <set>
#include <algorithm>
using namespace std;
class Person {
public:
Person(string a, string b) :strFirstname(a), strLastname(b) {}
public:
string firstname() const {
return strFirstname;
}
string lastname() const {
return strLastname;
}
private:
const string strFirstname;
const string strLastname;
};
//仿函数实现自定义排序
class PersonSortCriterion {
public:
//仿函数
//排序规则为:按照lastname升序排列,lastname相同时按firstname升序排列
bool operator()(const Person &p1, const Person &p2) {
return (p1.lastname() > p2.lastname() ||
((p2.lastname() <= p1.lastname()) &&
p1.firstname() > p2.firstname()));
}
};
int main() {
//类型重定义,并指定排序规则
typedef set<Person, PersonSortCriterion> PersonSet;
PersonSet col1;
//创建元素,并添加到容器
Person p1("Jay", "Chou");
Person p2("Robin", "Chou");
Person p3("Robin", "Lee");
Person p4("Bob", "Smith");
//向容器中插入元素
col1.insert(p1);
col1.insert(p2);
col1.insert(p3);
col1.insert(p4);
PersonSet::iterator pos;
//输出PersonSet中的所有元素
for (pos = col1.begin(); pos != col1.end(); ++pos) {
cout << pos->firstname() << " " << pos->lastname() << endl;
}
cout << endl;
return 0;
}
有多种状态的仿函数
仿函数都是传值,而不是传址的。因此算法并不会改变随参数而来的仿函数的状态
#include <iostream>
#include <list>
#include<algorithm>
using namespace std;
class IntSequence {
private:
int value; //记录内部状态的成员变量
public:
IntSequence(int initialValue) : value(initialValue) {}
//仿函数
int operator()() {
return value++;
}
};
int main() {
list<int> col1;
//产生长度为9的序列,依次插值到col1容器的尾部
generate_n(back_inserter(col1),9,IntSequence(1));
//1 2 3 4 5 6 7 8 9
for (auto t : col1) {
cout << t << " ";
}
cout << endl;
//替换col1容器中第2个到倒数第2个,从42开始
generate(++col1.begin(),--col1.end(),IntSequence(42));
//1 42 43 44 45 46 47 48 9
for (auto t : col1) {
cout << t << " ";
}
cout << endl;
return 0;
}
输出:
1 2 3 4 5 6 7 8 9
1 42 43 44 45 46 47 48 9
- 仿函数都是传值,而不是传址的。因此算法并不会改变随参数而来的仿函数的状态
IntSequence seq(1); //从1开始的序列
//从1开始向容器col1中插入9个元素
generate_n(back_inserter(col1), 9, seq);
//仍然从1开始向容器col1中插入9个元素
generate_n(back_inserter(col1), 9, seq);
generate函数
#include <iostream>
#include <algorithm>
#include <array>
#include <functional>
using namespace std;
int main(){
array<int,4> t1;
//产生4个100内的随机数
generate(t1.begin(),t1.end(),[](){return rand()%100;});
for_each(t1.begin(),t1.end(),[](int i){cout<<i<<"\t";});
cout<<endl;
//产生5个1000内的随机数
array<int,5> t2;
generate_n(t2.begin(),5,[](){return rand()%1000;});
for_each(t2.begin(),t2.end(),[](int i){cout<<i<<"\t";});
return 0;
}
当然,也有方法来解决上述使仿函数内部状态改变的问题。
方法有两种:
1、以引用的方式传递仿函数;
2、运用for_each()算法的返回值。
因为for_each()算法它返回其仿函数。也就是说,我们可以通过返回值可以取得仿函数的状态。
(1)以引用的方式传递仿函数
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
class IntSequence{
private:
int value;
public:
IntSequence(int initValue) : value(initValue) {}
int operator()() {
return value++;
}
};
int main() {
list<int> col1;
IntSequence seq(1);
//采用引用类型
generate_n < back_insert_iterator < list < int > > ,
int, IntSequence &>(back_inserter(col1),
4,
seq);
//1 2 3 4;
for (auto t : col1) {
cout << t << " ";
}
cout << endl;
//相当于重新构建一个对象从42开始插入4个元素
generate_n(back_inserter(col1),4,IntSequence(42));
//1 2 3 4; 42 43 44 45
for (auto t : col1) {
cout << t << " ";
}
cout << endl;
//前面使用的是引用类型,所以seq的内部状态已经被改变了
//插值从上次完成后的5开始
//注意:这次调用仍然使用的是传值类型
generate_n(back_inserter(col1),4,seq);
//1 2 3 4 42 43 44 45 5 6 7 8
for (auto t : col1) {
cout << t << " ";
}
cout << endl;
//上一次调用使用的是传值类型,所以这次还是从5开始插值
generate_n(back_inserter(col1),4,seq);
//1 2 3 4 42 43 44 45; 5 6 7 8 5 6 7 8
for (auto t : col1) {
cout << t << " ";
}
cout << endl;
return 0;
}
- 仿函数与一元谓词的区别
#include <iostream>
#include <set> // STL包
#include <algorithm> // 算法包
using namespace std;
// 仿函数(扩展性强) C++内置源码使用仿函数频率高,扩展性强
class showActionObj {
public:
int count = 0;
void _count() { cout << "本次输出次数是:" << this->count << endl; }
void operator() (int __first) {
cout << "仿函数" << __first << endl;
count++;
}
};
// 回调函数 (功能够简单)
void showAction(int __first) {
cout << "一元谓词" << __first << endl;
}
int main() {
// 理解:类型传递
// set<int, showActionObj> setVar; 这样写的语法是OK的,不能加括号
set<int> setVar;
setVar.insert(10);
setVar.insert(20);
setVar.insert(30);
setVar.insert(40);
setVar.insert(50);
setVar.insert(60);
// 第一种方式,无法统计打印次数
for_each(setVar.begin(), setVar.end(), showAction);
// 第二种方式,能统计打印次数
showActionObj s; // 理解:值传递
// 传入进去的s是新的副本,我们外面的s是旧地址
s = for_each(setVar.begin(), setVar.end(), s);
s._count();
return 0;
}
(2)运用for_each()算法的返回值
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class MeanValue {
private:
long num;
long sum;
public:
MeanValue() : num(0), sum(0) {}
void operator()(int elem) {
num++;
sum += elem;
}
double value() {
return static_cast<double>(sum) / static_cast<double>(num);
}
};
class MeanSum {
private:
long sum;
public:
MeanSum() : sum(0) {}
void operator()(int elem) {
sum += elem;
}
double value() {
return sum;
}
};
int main() {
vector<int> col1;
for (int i = 1; i <= 8; ++i) {
col1.push_back(i);
}
for (auto t : col1) {
cout << t << " ";
}
cout << endl;
MeanValue mv = for_each(col1.begin(), col1.end(), MeanValue());
MeanSum sum = for_each(col1.begin(), col1.end(), MeanSum());
cout << "Mean Value: " << mv.value() << endl;
cout << "Mean Sum: " << sum.value() << endl;
return 0;
}
(3)判断式与仿函数
- 判断式就是返回布尔型的函数或者仿函数。对于STL而言,并非所有返回布尔值的函数都是合法的判断式。这可能会导致很多出人意料的行为,比如下例:
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
class Nth {
private:
int nth;
int count;
public:
Nth(int n) : nth(n), count(0) {}
bool operator()(int) {
return ++count == nth;
}
};
int main() {
list<int> col1;
for (int i = 1; i <= 9; ++i) {
col1.push_back(i);
}
list<int>::iterator pos;
pos = remove_if(col1.begin(), col1.end(), Nth(3));
col1.erase(pos, col1.end());
for (auto t : col1) {
cout << t << " ";
}
cout << endl;
return 0;
}
谓词仿函数
返回值为布尔类型bool的仿函数称为谓词。如果operator()接受一个参数,叫做一元谓词,如果operator()接受两个参数,叫做二元谓词。
一元谓词
当使用algorithm中的算法模板处理自定义数据,或者不想按默认规则处理时,也需要自行写出完成相应逻辑判断的谓词,并把谓词传递给函数模板。
内建函数对象:C++的functional头文件里已经帮我们重载了很多(),我们可以直接拿来使用,下面给出常见的内建函数对象:
- 1.算术仿函数:值得注意的是,前5个仿函数只支持两个相同数据类型的操作数做运算,所以只传递一个操作数的数据类型,另外注意,取反仿函数是一元谓词。
template<class T> T plus<T>//加法
template<class T> T minus<T>//减法
template<class T> T multiplies<T>//乘法
template<class T> T divides<T>//除法
template<class T> T modulus<T>//取模
template<class T> T negate<T>//取反,即取负数
- 2.关系仿函数:同上,它们只支持两个相同数据类型的操作数做比较,只传递一个操作数的数据类型。
template<class T> bool equal_to<T>//判断相于=
template<class T> bool not_equal_to<T>//判断不等于!=
template<class T> bool greater<T>//判断大于>
template<class T> bool greater_equal<T>//判断大于等于>=
template<class T> bool less<T>//判断小于<
template<class T> bool less_equal<T>//判断小于等于<=
- 3.逻辑仿函数:同样地逻辑与、逻辑或只支持两个相同数据类型的操作数做运算,只需传递一个操作数的数据类型,注意逻辑非是一元谓词。
template<class T> bool logical_and<T>//逻辑与
template<class T> bool logical_or<T>//逻辑或
template<class T> bool logical_not<T>//逻辑非
二元谓词
- 例子一
#include <iostream>
#include <set>
using namespace std;
class Cmp {
public:
bool operator()(int a, int b) {
return a > b;
}
};
int main() {
set<int, Cmp> s;
s.insert(10);
s.insert(20);
s.insert(30);
s.insert(40);
for ( auto it = s.begin(); it != s.end(); it++) {
cout << *it << "\t";
}
cout << endl;
return 0;
}
- 例子二
#include <iostream>
#include <set>
using namespace std;
// C++源码:typename _Compare = std::less less内置的仿函数,根据内置仿函数去写 自定义
// bool operator()(const _Tp& __x, const _Tp& __y) const 二元谓词
class CompareObjectClass {
public:
// const 指针 const 常量指针常量 = 只读
bool operator() (const string & __x, const string & __y) const {
return __x > __y;
}
};
int main() {
set<string, CompareObjectClass> setVar;
setVar.insert(setVar.begin(), "AAAAAAA");
setVar.insert(setVar.begin(), "BBBBBBB");
setVar.insert(setVar.begin(), "CCCCCCC");
setVar.insert(setVar.begin(), "DDDDDDD");
setVar.insert(setVar.begin(), "EEEEEEE");
setVar.insert(setVar.begin(), "FFFFFFF");
// 迭代器 循环
for (auto it = setVar.begin(); it != setVar.end(); it++) {
cout << "item:" << *it << "\t";
}
return 0;
}