第六章 函数对象,一二元函数

函数对象

把函数作为对象是程序设计,。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)函数对象和函数指针都可以应用于模板

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值