函数对象
把函数作为对象是程序设计,。STL 是通过重载类中的operator函数实现函数对象功能的,不但可以对容器中的数据进行各种各样的操作,而且能够维护自己的状态。与标准C库函数相比,函数对象更为通用。
6.1 简介
6.1.1 为何引入函数对象
首先看一段示例,功能是采用 STL 固有 for_each 算法求保存在向量中的整数之和
int sum = 0; // 定义一个整数变量sum并初始化为0
void f(int n) { // 定义了一个函数f,接受一个整数参数n
sum += n; // 将参数n的值加到sum变量上
}
int main() { // 主函数入口
vector<int> v; // 定义一个整数向量v,用于存储整数值
for (int i = l; i <= 100; i++) { // 从l开始,循环迭代至100
v.push_back(i); // 将i的值添加到向量v的末尾
}
for_each(v.begin(), v.end(), f); // 对向量v中的所有元素依次调用函数f
printf("sum=%d\n", sum); // 打印输出sum的值
return 0; // 返回0作为程序的退出状态码
}
f(int)是全局普通函数,sum 是求和全局变量,结果是显示1~100 的总和。实际是随着面向对象思想大多数的功能都封装在类中,例如上述的求和过程封装在如下类中
class CSum{
private:
int sum;
public:
Csum()(sum=0;]
void f(int n){
sum+=n;}
int GetSum() (return sum;) };
6.1.2 函数对象分类
函数对象是重载了 operator()的类的一个实例,perator()是函数调用运算符。标准C++ 库根据 operator()参数个数为 0个,1个,2个加以划分。主要有以下5 种类型
发生器:一种没有参数且返回一个任意类型值的函数对象,例如随机数发生器。
一元函数:一种只有一个任意类型的参数,且返回一个可能不同类型值的函数
二元函数:一种有两个任意类型的参数,且返回一个任意类型值的函数对象。
一元判定函数:返回 bool 型值的一元函数。
二元判定函数: 返回 bool 型值的二元函数。
PS:STL 中函数对象最多仅适用于两个参数
同样是求整型向量各元素之和,利用函数对象后代码如下所示,注意
1)必须重载 operator 函数。这是实现函数对象功能最重要的环节,不能随便写,因此6.1.1节中CSum类中的 f(int)函数修改为 operator()(int),而函数体的内容不变。
2)函数对象调用方式。直接采用构造函数形式调用,如本例中 for_each(vbegin(),v.end(),CSum())中的第三个参数 CSum()。也就是说,对本例而言,STL 知道 CSum()对应着CSum类中重载的 operator 函数,具体有几个参数呢?由于 for_each 函数一次只能选代出一个整型数,因此STL 知道每选代一次整型数都要执行一次 CSum 中的 operator()(int)函数。
3)CSum sObi用来接收 for_each 选代函数对象的最终结果值。
class CSum {
private:
int sum;
public:
CSum() {
sum = 0;
}
//使得对象可以像函数一样被调用。该函数将传入的整数n加到sum上
void operator()(int n) {
sum += n;
}
//返回当前求和的结果sum
int GetSum() {
return sum;
}
};
int main() {
vector<int> v;//创建一个名为v的整数向量
for (int i = 1; i <= 100; i++) {
v.push_back(i);//将当前循环变量i的值添加到向量v中
}
//使用STL算法for_each对向量v中的元素应用CSum类的对象,并将结果赋值给CSum类型的对象sObj
//for_each算法会遍历向量中的每个元素,并对每个元素调用CSum类的operator()函数,将元素的值加到sum上
CSum sObj = for_each(v.begin(), v.end(), CSum());//打印求和的结果
printf("sum=%d\n", sObj.GetSum());
return 0;
}
6.2 一元函数
STL 中一元函数善卷景一个模板类,其原型如下:
//这是一个模板声明,声明了两个模板参数 _A 和 _R,用于表示一元函数的参数类型和返回值类型
template<class_A,class_R>
struct unary_function{//定义了一个结构体,表示一元函数的类型
typedef_A argument_type;//_A 是作为传入的模板参数,它将被用作参数类型的别名
typedef _R result_type;//定义了另一个类型别名 result_type,用于表示一元函数的返回值类型
};
利用一元函数求向量各元素之和
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>//包含了向量容器的头文件,用于使用向量
using namespace std;
template<class _inPara, class _outPara>
//定义一个模板类 CSum,用于表示求和的函数对象,模板参数 _inPara 和 _outPara 表示输入和输出的参数类型
class CSum : public unary_function<_inPara, _outPara>
//定义类 CSum 继承自 unary_function 类,表示一元函数的类型
{
public:
_outPara sum;//求和的结果变量
CSum() { sum = 0; }
//重载了函数调用运算符 operator()
void operator()(_inPara n) {
sum += n;
}
_outPara GetSum() {//获取求和的结果
return sum;
}
};
int main()
{
vector<int> v;//定义一个整数向量 v
for (int i = 1; i <= 100; i++) {
v.push_back(i);//将当前循环变量 i 的值添加到向量 v中
}
//使用 STL 算法 for_each 对向量 v 中的元素应用 CSum 类的对象,并将结果赋值给 CSum 类型的对象 sobj
//for_each 算法会遍历向量中的每个元素,并对每个元素调用 CSum 类的operator() 函数将元素的值加到求和结果上
CSum<int, int> sobj = for_each(v.begin(), v.end(), CSum<int, int>());
cout << "sum(int) = " << sobj.GetSum() << endl;
vector<float> v2;//定义一个浮点数向·量 v2
float f = 1.3f;//定义浮点数变量 f 并赋值为1.3
for (int i = 1; i <= 99; i++) {
v2.push_back(f);//将当前浮点数变量 f 的值添加到向量 v2 中
f += 1.0f;//将浮点数变量 f 的值增加1
}
CSum<float, float> sobj2 = for_each(v2.begin(), v2.end(), CSum<float, float>());//同理
cout << "sum(float) = " << sobj2.GetSum() << endl;//打印求和的结果
return 0;
}
理解以下几点:
1)应用 STL 模板一元函数必须从 unary_function 基类派生。例如本例中的CSum 类。
2)加深对一元函数模板类模板参数的理解,本例中的 inPara 表示operator 函数的参数类型,所以写作 void operator()(_inPara n);_outPara 表示返回值的类型因此返回值变量sum应定义成_outPara类型。由于这两个参数在本例中都是动态传进去的,
因此在类定义的前面要加上 template<classinPara,class _outPara>
Sum<int,int> sObj=for_each(v,begin(),v.end(),CSum<int,int>())表明是对整型向量元素求和。CSum<int,int>表明CSum 是一个模板类,两个动态参数都是整型数。CSum<float,float>sObj2=foreach(v2.begin(),v2.end()CSum<float,float>())表明是对浮点向量元素求和,两个动态参数都是浮点型。
可以看出对整型向量求和、浮点向量求和都是由 CSum 函数对象类完成的
当然也可用 CSum 求其他数据类型的和
template<class inPara,class outPara>//用来表示输入和输出的数据类型
//这个类继承自 unary_function<class_inPara,class_outPara>,这是一个函数对象的基类模板,用于指定函数对象只接受一个参数
class CSum: public unary_function<class_inPara,class_outPara>{
public:
_outPara sum;//一个变量,用于存储求和的结果。
CSum(_outPara init) { sum = init; }//构造函数,接受一个初始值 init,将其赋值给 sum。
void operator()(_inPara n)//函数调用运算符重载。它接受一个参数 n,并将其与 sum 求和
{
sum += n;
}
_outPara GetSum(){//返回当前求和结果的方法。
return sum;
}
};
6.3 二元函数
STL中二元函数基类是一个模板类,原型如下。
template<class Argl,class Arg2, class Result>//分别表示第一个参数类型、第二个参数类型和结果类型
//定义了一个结构体模板 binary_function,用于表示二元函数对象
struct binary_function {
//使用 typedef 关键字为 Argl 创建一个别名 first_argument_type。
//这个别名表示函数对象的第一个参数类型
typedef Argl first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;}
结构体模板 binary_function 的目的是提供一个通用的模板结构,用于表示接受两个参数并产生结果的二元函数对象。通过使用 typedef 创建别名,可以方便地获取函数对象的参数类型和结果类型。它有三个模板参数,Argl、Arg2 是输人参数,Result 是返回类型,且这三个参数的是任意的,因此它的动态特性非常强。
例如若按学生成绩升序排列,利用二元函数后代码如下所示。
需要注意的是,代码中使用了模板参数 _inParal 和 _inPara2,这使得类 binary_sort 可以适应不同的数据类型,以实现通用的比较功能。
该函数的序执行流程:主程序中首先把 4个学生对象依次放人向量 vector ,当执行排序函数 sort 时,调用二元函数类 binary_sort 中重的 operator 函数当执行:in1<in2 时,由于 in1,in2 的参数类型都是 Srudent&因此调用 Studet 类中重载operator<函数完成两个 Student 引用对象真正的比较功能,随后把返回值依次返函数根据此返回值决定这两个 Student 对象是否交换。
#include <iostream>
#include< functional>
#include<algorithm>
#include<iostream>
#include<vector>
#include<string>
#include<iterator>
using namespace std;
class Student{
public:
string name;
int grade;
public:
Student (string name, int grade){
this->name=name;
this->grade=grade;
}
//重要,决定less重载了 < 运算符,用于比较学生对象的成绩大小
bool operator< (const Student& s) const{
return grade<s.grade;
}
};
//重载了输出流运算符 <<,用于将学生对象的信息输出到流中
ostream& operator<< (ostream& os, const Student& s){
os<<s.name<<"\t"<<s.grade<<"\n";
return os;
}
//定义了一个名为 binary_sort 的类模板,用于比较两个参数的大小
template<typename _inParal, typename _inPara2>
//这个类模板继承自 binary_function<_inParal, _inPara2, bool>,这是一个函数对象的基类模板,
//用于指定函数对象接受两个参数,并返回一个布尔值
class binary _sort:public binary_function<_inParal, _inPara2, bool>{
public:
//函数调用运算符重载。它接受两个参数 in1 和 in2,并比较它们的大小。
//返回结果是一个布尔值,表示第一个参数是否小于第二个参数
bool operator()(_inParal in1, _inPara2 in2){
return in1<in2;}
};
/*一般不需要有student operator<所以定义
template<> //模板类的成员特化,其<>中是空的!
bool less<Student>:operator()(const Student& v1, const Student& v2)const{
cout << "here" << endl;
return (vl<v2);
} */
less<student>myit;
void main(){
Student sl("zhangsan",60);
Student s2("lisi",80);
Student s3("wangwu",70);
Student s4("zhaoliu",90);
bool ret=s1<s2;//运算符定义在类内要求做操作符必须在类内s1.operator<(s2)
bool ret=15<s2;//错误类内没有定义过15。 15.operator<(s2),改正方法是将上面的放在类外
vector<Student>v;
v.push_back(s1);
v.push_back(s2);
v.push_back(s3);
v.push_back(s4);
/* sort(v.begin(),v.end(),binary_sort<const Student&,const Student&>());//升序排列
copy(v.begin(),v.end(),ostream_iterator<Student>(cout,""));*/
sort(v.begin(),v.end());//升序排
copy(v.begin(),v.end(),ostream_iterator<Student>(cout,"\n"));//降序排
/* sort(v.beginO),v.end(), Less<Student>);//对应前面的template<> */
return 0;
}
6.4部分习题(自定义仿函数和系统自带仿函数)
1. 编写一个自定义仿函数 `LessThan`,用于比较两个整数是否相等,并将其作为参数传递给 `std::count_if` 算法函数,统计一个向量中小于 42 的元素个数。
#include <iostream>
#include <algorithm>
#include <vector>
class LessThan {
public:
bool operator()(int x, int y) const {
return x < y;
}
};
int main() {
std::vector<int> numbers = {13, 42, 23, 17, 5, 29, 37, 19};
int count = std::count_if(numbers.begin(), numbers.end(), LessThan{42});
std::cout << "There are " << count << " numbers less than 42.\n";
return 0;
}
输出结果为:There are 5 numbers less than 42.
2. 使用 `std::sort()` 函数对一个字符串向量进行排序,要求按照字符串长度从短到长排序。
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
class SortByLength {
public:
bool operator()(const std::string& str1, const std::string& str2) const {
return str1.length() < str2.length();
}
};
int main() {
std::vector<std::string> strings = {"hello", "world", "this", "is", "a", "test"};
std::sort(strings.begin(), strings.end(), SortByLength());
// 输出排序后的结果
for (const auto& str : strings) {
std::cout << str << " ";
}
return 0;
}
输出结果为:a is test hello world this3.
编写一个自定义仿函数 `GreaterThan`,用于比较两个浮点数是否相等,并将其作为参数传递给 `std::unique` 算法函数,去除一个浮点数向量中的重复元素。
#include <iostream>
#include <algorithm>
#include <vector>
class GreaterThan {
public:
bool operator()(double x, double y) const {
return x > y;
}
};
int main() {
std::vector<double> numbers = {3.14, 2.71, 1.41, 2.71, 3.14, 1.73};
std::sort(numbers.begin(), numbers.end(), GreaterThan{});
auto end = std::unique(numbers.begin(), numbers.end());
numbers.erase(end, numbers.end());
// 输出去重后的结果
for (const auto& num : numbers) {
std::cout << num << " ";
}
return 0;
}
输出结果为:3.14 2.71 1.41 1.734. 使用 `std::count_if` 算法函数和 `std::greater<int>` 标准仿函数,统计一个整数向量中大于 3 的元素个数。
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
int count = std::count_if(numbers.begin(), numbers.end(), std::greater<int>{}(3));
std::cout << "There are " << count << " numbers greater than 3.\n";
return 0;
}
输出结果为:There are 2 numbers greater than 3.
5. 使用 `std::transform` 算法函数和 `std::negate<int>` 标准仿函数,将一个整数向量中的每个元素取相反数,并存储到另一个向量中。
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::vector<int> result(numbers.size());
std::transform(numbers.begin(), numbers.end(), result.begin(), std::negate<int>{});
// 输出结果
for (const auto& num : result) {
std::cout << num << " ";
}
return 0;
}
输出结果为:-1 -2 -3 -4 -56. 使用 `std::remove_if` 算法函数和 `std::less<int>` 标准仿函数,去除一个整数向量中小于 3 的元素。
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
auto end = std::remove_if(numbers.begin(), numbers.end(), std::less<int>{}(3));
numbers.erase(end, numbers.end());
// 输出结果
for (const auto& num : numbers) {
std::cout << num << " ";
}
return 0;
}
输出结果为:3 4 5
6.5 习题
1、下面关于函数对象的论述那个有问题?
a)函数对象是通过重载操作符()实现的
b)函数对象可以用来保存运行状态
c)函数对象须由基础的模板类unary_function或binary_function派生//一元、二元函数需要。发生器不需要派生。一元、二元判定函数用派生。.
d) STL中提供了大量的系统函数对象
2、返回布尔值的函数对象称为_谓词,默认的是_小于比较符号“<”操作符。
3、什么是函数对象?
STL是通过重载类中的operator函数实现函数对象功能的,不但可以对容器中的数据进行各种各样的操作,而且能够维护自己的状态。因此与标准C库函数相比,函数对象更为通用。
函数对象是重载了operate()的类的一个实例,operate()是函数调用运算符
根据operator()参数个数加以划分,主要有五种类型
发生器没有参数,返回任意一个类型值的函数对象。不能返回bool值
一元函数只有一个任意类型的参数
二元函数有两个任意类型的参数
一元判定函数:返回bool型值的一元函数,
二元判定函数:返回bool型值的二元函数,
一元函数必须从unary_function基类派生。
一元函数在模板上是两个参数,在operate上是一个参数。
4、函数对象带来的好处?(两个老师都标的重点)
函数对象是重载了operator()的类的一个实例,operator()是函数调用运算符,在函数中调用所需类对象的函数,使程序结构显得非常简洁。
5.使用函数对象和函数指针的异同:
(1)函数对象可以携带附加数据,函数指针不行
(2)函数对象可以用来封装 类成员函数指针
(3)函数对象和函数指针都可以应用于模板