目录
(1)仿函数(函数对象)
0.含义
在C++中,仿函数(Functor,也称为函数对象)是一种通过重载operator()实现的对象,其行为类似于函数。但与普通函数相比,有更多的优点,比如仿函数可以将复杂逻辑封装为可调用的对象,不仅可以实现相关函数功能,还可以统计函数运行过程中的各种属性等,当然还有更多高级用法。
1.函数对象、函数模板与普通函数
- 重载了()的类,创建的对象为函数对象
- 标准库中的很多算法都可以使用函数对象或者函数来作为自定的回调行为
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
using namespace std;
// 重载了()的类,创建的对象为函数对象
template <typename T>
class ShowElement
{
public:
ShowElement()
{
n = 0;
}
void operator()(T &t)
{
n++; // 用n记录()的调用次数
cout << t << endl;
}
void printN()
{
cout << "n=" << n << endl;
}
protected:
private:
int n;
};
// 模板函数
template <typename T>
void FuncShowElement(T &t)
{
cout << t << endl;
}
// 普通函数
void FuncShowElement2(int &t)
{
cout << t << endl;
}
int main()
{
// 1.创建函数对象,调用()运算符(像函数一样)
int a = 10;
ShowElement<int> se;
se(a);
// 2.函数模板
FuncShowElement<int>(a);
// 3.普通函数
FuncShowElement2(a);
return 0;
}
2.函数对象与stl算法
函数对象是类对象,能够突破函数的概念,能保持调用状态信息。一般结合stl算法使用。
函数对象 + for_each算法
- for_each算法中,函数对象做函数参数
- for_each算法中,函数对象充当返回值
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
// 函数对象类
template <typename T>
class ShowElement
{
public:
ShowElement()
{
n = 0;
}
void operator()(T &t)
{
n++; // 用n记录()的调用次数
cout << t << endl;
}
void printN()
{
cout << "n=" << n << endl;
}
protected:
private:
int n;
};
// 模板函数
template <typename T>
void FuncShowElement(T &t)
{
cout << t << endl;
}
// 普通函数
void FuncShowElement2(int &t)
{
cout << t << endl;
}
int main()
{
vector<int> v1 = {1, 3, 6};
// for_each算法:遍历值,然后调用对应的函数
// 1.ShowElement<int>()匿名函数对象 匿名仿函数 做for_each的参数
cout << endl;
for_each(v1.begin(), v1.end(), ShowElement<int>());
// 2.普通函数做for_each回调函数的入口地址
cout << endl;
for_each(v1.begin(), v1.end(), FuncShowElement2);
// 3.函数对象做for_each的返回值,查看被调用的次数
ShowElement<int> show1;
ShowElement<int> show2;
cout << endl;
show2 = for_each(v1.begin(), v1.end(), show1);
show1.printN(); // 0 // for_each算法的函数对象的传递 是元素值传递,不是引用
show2.printN(); // 3
return 0;
}
函数对象 + transform算法
transform算法:将两个容器的元素进行运算,然后放入第三个容器中
用法1:transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), 函数对象);
说明:从v1.begin()到v1.end(),与v2.begin()开始,按相同位置与,进行SumAdd运算,运算结果从v3.begin()开始依次放入v3
#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
// 二元谓词
template <typename T>
class SumAdd
{
public:
T operator()(T t1, T t2)
{
return t1 + t2;
}
};
void test()
{
//v1 v2 ==> v3
vector<int> v1, v2;
vector<int> v3;
v1.push_back(1);
v1.push_back(3);
v1.push_back(5);
v2.push_back(2);
v2.push_back(4);
v2.push_back(6);
v3.resize(10);
// v1: 1 3 5
// v2: 2 4 6
// 从v1.begin()到v1.end(),与v2.begin()开始,按相同位置与,进行SumAdd运算
// 运算结果从v3.begin()开始依次放入v3
transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), SumAdd<int>());
for (vector<int>::iterator it=v3.begin(); it!=v3.end(); it++ )
{
cout << *it << " ";
}
cout << endl;
// 3 7 11 0 0 0 0 0 0 0
}
int main()
{
test();
return 0;
}
用法2:std::transform(str1.begin(), str1.end(), str2.begin(), 函数对象);
说明:从str1.begin()到str1.end(),进行运算,运算结果从str2.begin()开始依次放入str2
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
unsigned char m_tolower(char x)
{
return std::tolower(x);
}
int main()
{
std::string str = "hello world!";
std::string str2;
str2.resize(str.size());
// 将string转大写
std::transform(str.begin(), str.end(), str2.begin(),
[](unsigned char c){return std::toupper(c);});
std::cout << str2 << std::endl; // HELLO WORLD!
// 将string转小写
std::transform(str.begin(), str.end(), str2.begin(), m_tolower);
std::cout << str2 << std::endl; // hello world!
return 0;
}
3.谓词
①谓词含义与分类
C++中,返回值是bool类型的函数或者仿函数或者匿名函数(lambda表达式)称为谓词。注意返回值一定是bool类型。
比如:bool func(T&a);或者bool func(T&a,T&b);
find_if(),count_if()等算法函数需要传入bool型的判断条件参数,因此会经常用到谓词。
对于返回bool类型的仿函数来说:
-
- 如果operator()接受一个参数,那么叫做一元谓词
- 如果operator()接受两个参数,那么叫做二元谓词
②谓词与算法的结合
一元谓词 + find_if算法
用法:find_if(v.begin(), v.end(), 谓词);
注意这里 find_if返回的是迭代器,而不像前面for_each返回的是函数对象
示例1:在vector找到第一个大于5的值
#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
// 返回值类型是bool数据类型的仿函数,这个仿函数被称为谓词
// 这里只有一个参数,所以就是一元谓词
class GreaterFive{
public:
bool operator()(int val)
{
return val > 5;
}
};
void test01()
{
vector<int> v;
for(int i = 0; i < 10; i++){
v.push_back(i);
}
// 查找容器中 有没有大于5的元素
// GreaterFive() 匿名函数对象
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
// 返回符合条件的第一个值迭代器
if(it != v.end()) {
cout << "found:" << *it << endl;
} else {
cout << "not found" << endl;
}
}
int main()
{
test01();
return 0;
}
示例2:找到能被某整数整除的元素
#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
// 函数对象:判断能否整除
template<typename T>
class IsDiv
{
public:
IsDiv(const T &divisor) {
this->divisor = divisor;
}
bool operator()(T &t) {
return (t%divisor == 0);
}
protected:
private:
T divisor;
};
void test()
{
vector<int> v1;
for (int i=10; i<33; i++) {
v1.push_back(i);
}
int a = 4;
// 1.用实例对象
// IsDiv<int> myDiv(a);
// find_if(v1.begin(), v1.end(), myDiv );
// 2.用匿名对象
vector<int>::iterator it;
it = find_if(v1.begin(), v1.end(), IsDiv<int>(a));
if (it != v1.end()) {
cout << "found:" << *it << endl; // 12
} else {
cout << "not found" << endl;
}
}
int main()
{
test();
return 0;
}
二元谓词 + sort算法
默认排序:sort(v.begin(),v.end());
自定义排序:sort(v.begin(),v.end(), 谓词);
注意sort算法会改变容器的排序
#include <vector>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
// 此为二元谓词
class MyCompare
{
public:
bool operator()(int a, int b)
{
// 对于sort算法来说,返回true表示不交换a和b,即按ab排序
// 返回false表示ba这样排序
return a > b; // 从大到小
}
};
void test01()
{
vector<int> v;
v.push_back(20);
v.push_back(40);
v.push_back(30);
v.push_back(50);
v.push_back(10);
// 1. sort是默认的从小到大
sort(v.begin(),v.end());
for(vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << endl;
}
// 打印结果表明sort函数改变了v内部值的顺序
// 2.使用函数对象 改变算法策略 变排序规则为从大到小
cout << endl;
sort(v.begin(),v.end(), MyCompare());
for(vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << endl;
}
}
int main()
{
test01();
return 0;
}
二元谓词 + transform算法
#include <iostream>
#include <string>
#include <set>
#include <algorithm>
#include <functional>
using namespace std;
unsigned char m_tolower(char x)
{
// std空间中的tolower只需传入一个参数
return std::tolower(x);
}
struct CompareCase // 注意这里一定是struct
{
bool operator()(const string &str1, const string &str2)
{
string str1_;
str1_.resize(str1.size());
transform(str1.begin(), str1.end(), str1_.begin(), m_tolower);
string str2_;
str2_.resize(str2.size());
transform(str2.begin(), str2.end(), str2_.begin(), m_tolower);
return (str1_ < str2_);
}
};
void test()
{
set<string> set1 = {"aaa", "bbb", "ccc"};
// 1.区分大小的查找(不能找到)
auto it = set1.find("aAa");
if (it != set1.end()) {
cout << *it << endl;
} else {
cout << "not found" << endl;
}
// 2.用谓词,不区分大小的查找(能找到)
set<string, CompareCase> set2 = {"bbb", "aaa", "ccc"};
set<string, CompareCase>::iterator it2 = set2.find("aAa");
if (it2 != set2.end()) {
cout << *it2 << endl;
} else {
cout << "not found" << endl;
}
}
int main()
{
// 二元谓词在set中的应用
test();
return 0;
}
4.预定义函数对象
①介绍与用法示例
预定义函数对象基本概念:标准模板库STL提前定义了很多预定义函数对象,#include <functional> 必须包含。
比如类模板plus<> 的实现了不同类型的数据进行加法运算
示例
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
template <typename T>
void print_vector(vector<T> m_vec)
{
auto it = m_vec.begin();
for (it; it != m_vec.end(); it++)
{
std::cout << "(" << *it << ")\t";
}
std::cout << std::endl;
}
int main()
{
// 示例1:整数加法
plus<int> intAdd;
int x = 10;
int y = 20;
int z = intAdd(x, y); //等价于 x + y
cout << z << endl;
// 示例2:字符串相加
plus<string> stringAdd;
string myc = stringAdd("aaa", "bbb");
cout << myc << endl;
// 示例3:vector排序
vector<string> v1 = {"bbb", "aaa", "ccc", "zzzz"};
print_vector(v1);
// 缺省情况下,sort()用底层元素类型的小于操作符以升序排列容器的元素。
sort(v1.begin(), v1.end());
print_vector(v1);
// 为了降序,可以传递预定义的类模板greater,它调用底层元素类型的大于操作符
// 匿名函数对象 greater<string>()
sort(v1.begin(), v1.end(), greater<string>()); //从大到小
print_vector(v1);
return 0;
}
②预函数对象常用类型
算术函数对象
预定义的函数对象支持加、减、乘、除、求余和取反。调用的操作符是与type相关联的实例
加法:plus<Types>
plus<string> stringAdd;
sres = stringAdd(sva1,sva2);
减法:minus<Types>
乘法:multiplies<Types>
除法divides<Tpye>
求余:modulus<Tpye>
取反:negate<Type>
negate<int> intNegate;
int m = intNegate(5);
cout << m << endl; // -5
关系函数对象
等于equal_to<Tpye>
equal_to<string> stringEqual;
sres = stringEqual(sval1,sval2);
不等于not_equal_to<Type>
大于 greater<Type>
大于等于greater_equal<Type>
小于 less<Type>
小于等于less_equal<Type>
注意,使用不同的stl算法时,可能需要涉及函数适配器,比如下面例子中的count_if算法
使用例子
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main()
{
vector<string> v1;
v1.push_back("bbb");
v1.push_back("aaa");
v1.push_back("ccc");
v1.push_back("zzzz");
v1.push_back("ccc");
string s1 = "ccc";
// 需求:在v1中统计s1的出现次数
/*
int num = count_if(v1.begin(), v1.end(), equal_to<string>(), s1);
会提示报错:no instance of function template "std::count_if" matches the argument listC/C++(304)
原因:count_if只传递3个参数
equal_to<string>() 有两个参数 left参数来自容器,right参数来自s1
因此可以用函数适配器将equal_to<string>()和s1绑定起来,bind2nd(equal_to<string>(), s1)
另外要绑定第一个参数的话,用bind1nd
*/
int num = count_if(v1.begin(), v1.end(), bind2nd(equal_to<string>(), s1));
cout << num << endl; // 2
return 0;
}
逻辑函数对象
逻辑与 logical_and<Type>
logical_and<int> indAnd;
ires = intAnd(ival1, ival2);
逻辑或logical_or<Type>
逻辑非logical_not<Type>
logical_not<int> IntNot;
Ires = IntNot(ival1);
(2)函数适配器
1.函数适配器是什么
STL中已经定义了大量的函数对象,但是有时候需要对函数返回值进行进一步的简单计算,或者因为有多余的参数,不能直接代入算法。
函数适配器实现了这一功能,将一种函数对象转化为另一种符合要求的函数对象。
2.函数适配器分类
函数适配器可以分为4大类:绑定适配器(bind adaptor)、组合适配器(composite adaptor)、指针函数适配器(pointer function adaptor)和成员函数适配器(member function adaptor)。
STL标准库中的函数适配器有很多,比如binder1st、binder2nd、unary_negate等。
3.函数适配器辅助函数
直接构造STL中的函数适配器通常会导致冗长的类型声明。为简化函数适配器的构造,STL还提供了函数适配器辅助函数,直接用适配器辅助函数即可,借助于泛型自动推断技术,无需显示的类型声明便可实现函数适配器的构造。以下4个常用。
适配器辅助函数 | 功能说明 | 示例 |
bind1st | 辅助构造binder1st适配器实例,绑定固定值到二元函数的第1个参数位置 | bind1st(op, value) |
bind2nd | 辅助构造binder2nd适配器实例,绑定固定值到二元函数的第2个参数位置 | bind2nd(op, value) |
not1 | 辅助构造unary_negate适配器实例,生成一元函数的逻辑反函数 | not1(op) |
not2 | 辅助构造binary_negate适配器实例,生成二2元函数的逻辑反函数 | not2(op) |
4.函数适配器结合stl算法使用例子
例子:常用函数适配器 + count、count_if算法
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
class isGreat
{
public:
isGreat(int i)
{
m_num = i;
}
bool operator()(int &num)
{
if (num > m_num)
{
return true;
}
return false;
}
private:
int m_num;
};
void test()
{
// 1.初始化容器
vector<int> v1;
for (int i=0; i<10; i++)
{
v1.push_back(i+1);
}
v1.push_back(3);
for (vector<int>::iterator it=v1.begin(); it!=v1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
// 2.count算法
int num1 = count(v1.begin(), v1.end(), 3);
cout << "num1=" << num1 << endl; // 2
// 3.通过谓词求大于2的个数
int num2 = count_if(v1.begin(), v1.end(), isGreat(2));
cout << "num2=" << num2 << endl; // 9
// 4.通过STL中自带的预定义好的函数对象来求大于2的个数
/*
template<class _Ty>
struct greater
: public binary_function<_Ty, _Ty, bool>
{ // functor for operator>
bool operator()(const _Ty& _Left, const _Ty& _Right) const
{ // apply operator> to operands
return (_Left > _Right);
}
};
*/
// greater<int>() 有两个参数 左参数来自容器的元素 ,右参数固定成2
// 通过函数适配器bind2nd变成一个参数
int num3 = count_if(v1.begin(), v1.end(), bind2nd(greater<int>(), 2));
cout << "num3=" << num3 << endl; // 9
// 求奇数的个数(对2求余的结果是否为真)
int num4 = count_if(v1.begin(), v1.end(), bind2nd(modulus<int>(), 2));
cout << "num4=" << num4 << endl; // 6
// 求偶数的个数,对是否为奇数取反器(negator)
int num5 = count_if(v1.begin(), v1.end(), not1( bind2nd(modulus<int>(), 2) ));
cout << "num5=" << num5 << endl; // 5
}
int main()
{
test();
return 0;
}
(3)STL的容器算法迭代器的设计理念
- STL的容器通过类模板技术,实现数据类型和容器模型的分离
- STL的迭代器技术实现了遍历容器的统一方法;也为STL的算法提供了统一性奠定了基础
- STL的算法,通过函数对象实现了自定义数据类型的算法运算,所以说:STL的算法也提供了统一性。核心思想:其实函数对象本质就是回调函数,回调函数的思想:就是任务的编写者和任务的调用者有效解耦合。函数指针做函数参数。
具体例子:transform算法的输入,通过迭代器first和last指向的元算作为输入;通过result作为输出;通过函数对象来做自定义数据类型的运算。
end