Loading

14-8 C++函数调用运算符

14.8.0 引入

函数对象

我们可以重载类类型的函数调用运算符,进而像使用函数一样使用这个类

那么这个类既可以存储状态,又可以当函数使用,十分灵活

//定义一个类,让它起到一个求绝对值函数的作用
struct absInt{
    int operator()(int val) const {
        return val<0? -val : val;
    }
};

//使用
int main(){
    int i = -42;
    absInt absObj;
    int ui = absObj(i);
    cout<<ui<<endl;
    return 0;
}

函数调用运算符必须是成员函数。一个类可以定义多个不同版本的调用运算符[这样就像函数的重载],相互之间应该在参数数量或类型上有所区别。

如果类定义了调用运算符,则该类的对象称作函数对象( function object)。因为可以调用这种对象,所以我们说这些对象的“行为像函数一样”。

含有状态的函数对象

举个例子,我们将定义一个打印 string 实参内容的类。默认情况下,我们的类会将内容写入到cout中,每个string之间以空格隔开。同时也允许类的用户提供其他可写入其他分隔符。我们将该类定义如下

class PrintString{
public:
    PrintString(ostream &o = cout, char c = ' ') :
    	os(o), sep(c){};
    void operator()(const string &s) const{
        os<<s<<sep;
    }
private:
    ostream &os;
    char sep;
};

//上述的类等价于下面的函数
void PrintString_Function(const string &s, 
                 ostream &os = cout, char sep = ' '){
    os<<s<<sep;
}

int main(){
    string s("Hello World!");
    PrintString ps;
    ps(s);
    return 0;
}

函数对象常做泛型算法实参

和lambda一样,函数对象常做泛型算法的实参

如for_each

for_each(vs.begin(), vs.end(), PrintString(cout,'\n'));

//等价于
for_each(vs.begin(), vs.end(), 
         [](const string &s){cout<<s<<endl;});

14.8.1 lambda是函数对象

表示没有捕获值的lambda的类

在第十章泛型算法的“10-3定制操作lambda”中我们介绍了lambda的使用,但其实lambda本质上就是重载了()的未命名类的未命名对象,如10-3的例子

Stable_sort(words.begin(), words.end(),
           [](const string &a, const string &b)
              {return a.size() < b.size();});

//lambda的行为类似于下面的类的一个未命名对象
class ShorterString{
    bool operator()(const string &a, const string &b)
    { return a.size()<b.size();}
};

//Stable_sort还可以写为
Stable_sort(words.begin(), words.end(),                     		ShorterString());

表示lambda及其捕获行为的类

众所周知,lambda有两种捕获行为:引用捕获和值捕获

  1. 引用捕获时:程序负责确保执行时引用的对象确实存在,所以可以直接使用该对象的引用,而不必在lambda产生的类中存储为数据成员
  2. 值捕获时:lambda必须将每个值捕获的对象保存为数据成员
//获得第一个指向满足条件元素的迭代器,该元素满足size()>=sz
auto wc = find_if(words.begin(), words.end(),
                 [sz](const string &s)
                  {return s.size() >= sz;});

//lambda等价于下面的类的未命名对象
class SizeComp{
public :
    SizeComp(int n) : sz(n){}
    //该调用类型的形参,函数体和返回类型都和lambda一致
    bool operator()(const string s){
        return s.size() >= sz;
    }
private:
    int sz;
};

//等价调用
auto wc = find_if(words.begin(), words.end(),
                 	SizeComp(sz));

14.8.2 标准库定义的函数对象

标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类

例如:

  1. plus类定义了一个函数调用运算符用于对一对运算对象执行+的操作;
  2. modulus类定义了一个调用运算符执行二元的%操作;
  3. equal_to类执行==

这些类都被定义成模板的形式

例如:

  1. plus<string>令string加法运算符作用于string对象;
  2. plus<int>的运算对象是int;
  3. plus<sales_data>对 sales_data对象执行加法运算

image-20220302124412864

在算法中使用高标准库函数

image-20220302124528009

标准库规定其函数对象对于指针同样适用。

我们之前曾经介绍过比较两个无关指针将产生未定义的行为(参见3.5.3节,第107页),然而我们可能会希望通过比较指针的内存地址来sort指针的vector。

直接这么做将产生未定义的行为,因此我们可以使用一个标准库函数对象来实现该目的:

image-20220302124503917

14.8.3 可调用对象与function

调用形式

C语言中可调用对象

  1. 函数和函数指针
  2. lambda
  3. 重载了函数调用运算符的类

不同的调用对象可能有相同的调用形式,如

//普通函数
int add(int i, int j) { return i+j;}
//lambda,产生一个未命名的函数对象类
auto mod = [](int i, int j){ return i%j;};
//函数对象类
struct divide{
    int operator()(int denominator, int divisor){
        return denominator/divisor;
    }
};

上述三种对象都享有相同的调用形式

int ( int, int)

用function统一调用形式

function定义在头文件functional

image-20220302125646918

function<int(int, int)> f1 = add; //函数指针
function<int(int, int)> f2 = divide(); //函数对象类的对象
function<int(int, int)> f3 = [](int i, int j)//lambda
                               {return i*j;};

cout<<f1(4,2)<<endl; //打印6
cout<<f2(4,2)<<endl; //打印2
cout<<f3(4,2)<<endl; //打印8

用function建立函数表

函数表(function table)中不知包含函数,而是包含各种可调用对象(函数,lambda,函数对象类的对象)

//列举了可调用对象与二元运算符对应关系的表格
//所有可调用对象都必须接受两个int、返回一个int
//其中的元素可以是函数指针、函数对象或者lambda
map<string ,function<int(int, int)> binops = {
    {"+", add},				//函数指针
    {"-", minus<int>()},	//标准库函数对象
    {"/", divide()},		//用户定义的函数对象
    {"*",[](int i, int j){return i*j;}}, //未命名lambda
    {"%", mod}				//命名了的lambda对象
};

//使用:
binops["+"](10,5); //调用add(10,5)
binops["-"](10,5); //使用minus<int>对象的调用运算符
binops["/"](10,5); //使用divide对象的调用运算符
binops["*"](10,5); //调用lambda函数对象
binops["%"](10,5); //调用lambda函数对象
posted @ 2022-03-02 14:08  咪啪魔女  阅读(156)  评论(0编辑  收藏  举报