美文网首页C++ 杂记
025 C++ 函数调用运算符

025 C++ 函数调用运算符

作者: 赵者也 | 来源:发表于2020-06-29 09:57 被阅读0次

    如果类重载了函数调用运算符,则可以像使用函数一样使用该类的对象,因为这样的类同时也能存储状态,所以与普通函数相比它们更加灵活。

    struct absInt{
        int operator()(int val) const{
            return val < 0 ? -val : val;
        }
    };
    

    上面的类只定义了一种操作:函数调用运算符,它负责接受一个 int 类型的形参,然后返回该实参的绝对值。

    int i = -42;
    absInt absObj;
    int ui = absObj(i); // i 被传递给 absObj.operator()
    

    即使 absObj 是一个对象而非函数,也能调用该对象,调用对象实际上是在运行重载的调用运算符。

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

    如果类定义了调用运算符,则该类的对象称作 函数对象 ,因为可以调用这种对象,所以这些对象的行为像函数一样。

    含有状态的函数对象类

    函数对象除了 operator() 之外也可以包含其他成员。

    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;
    };
    

    使用:

    PrintString printer;
    printer(s);     // cout 中打印 s,后面跟一个空格
    PrintString errors(cerr, '\n');
    errors(s);      // cerr 中打印 s,后面跟一个换行符
    

    函数对象通常是作为泛型算法的实参:

    for_each(vs.begin(), vs.end(), PrintString(cerr, "\n"));
    

    lambda 是函数对象

    编写了一个 lambda 后,编译器将该表达式翻译成一个未命名类的未命名对象。

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

    其行为类似下面这个类的一个未命名对象:

    class ShorterString{
    public:
        bool operator()(const string &a,const string &b)
        {return a.size() < b.size();}
    };
    

    使用上面的类重写 stable_sort :

    stable_sort(words.begin(), words.end(), ShorterString());
    

    表示 lambda 及相应捕获行为的类

    auto wc = find_if(words.begin(),words.end(),
                      [sz](const string &a) {
        return a.size() > = sz;
    });
    

    该 lambda 表达式产生的类将形如:

    class SizeComp
    {
        SizeComp(size_t n):sz(n) { }
        bool operator()(const string &s) const {return s.size() >= sz;}
    private:
        size_t sz;
    };
    
    auto wc = find_if(words.begin(), words.end(), SizeComp(sz));
    

    标准库定义的函数对象

    标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行命名操作的调用运算符。

    plus<int> intAdd;   // 可执行 int 加法的函数对
    negate<int> intNegate;  // 可对 int 值取反的函数对象
    int sum = intAdd(10, 20);   // sum = 30
    sum = intAdd(10, intNegate(10));     // sum = 0
    

    定义在 functional 头文件中的函数对象:

    function 的操作 说明
    function<T> f; f 是一个用来存储可调用对象的空 function,这些可调用对象的调用形式应该与函数类型 T 相同(即 T 是 retType(args))
    function<T> f(nullptr); 显式地构造一个空 function
    function<T> f(obj); 在 f 中存储可调用对象 obj 的副本
    f 将 f 作为条件:当 f 含有一个可调用对象时为真;否则为假
    f(args) 调用 f 中的对象,参数是 args
    定义为 function<T> 的成员的类型 说明
    result_type 该 function 类型的可调用对象返回的类型
    argument_type
    first_argument_type
    second_argument_type
    当 T 有一个或两个实参时定义的类型。如果 T 只有一个实参,则 argument_type 是该类型的同义词;如果 T 有两个实参,则 first_argument_type 和 second_argument_type 分别代表两个实参的类型

    在算法中使用标准库函数对象

    // 传入一个临时函数对象用于执行两个 string 对象的比较运算
    sort(svec.begin(),svec.end(),greater<string>());
    

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

    vector<string *> nameTable; // 指针的 vector
    // 错误:nameTable 中的指针彼此之间没有任何关系,所以 < 将产生未定义的行为
    sort(nameTable.begin(),nameTable.end(),[](string *a, string *b){return a < b;}) ;
    
    //正确,标准库规定指针的 less 定义是良好的
    sort(nameTable.begin(), nameTable.end(), less<string*>());
    

    可调用对象与 function

    C++ 中的可调用对象包括:函数、函数指针、lambda 表达式、bind 创建的对象以及重载了函数调用运算符的类。

    可调用对象也有类型,labmda 有自己唯一的未命名类型,函数及函数指针的类型由其返回值类型和实参类型决定。两个不同类型的可调用对象却可能共享同一种调用形式,调用形式指明了返回类型以及传递给调用的实参类型,一种调用形式对应一个函数类型:

    int (int,int)   // 是一个函数类型,它接受两个 int,返回一个 int
    

    不同的类型可能具有相同类型的调用方式

    int add(int i,int j){return i + j;}
    auto mod = [](int i,int j){return i % j;};
    struct divide{
        int operator()(int denminator,int divisor){
            return denminator / divisor;
        }
    };
    

    尽管这些可调用对象对其参数执行了不同的算术运算,尽管它们的类型各不相同,但是共享一种调用形式:

    int (int,int)
    

    构建实现不同运算的函数表:

    divide div;
    map<string,int(*)(int,int)> binops;
    binops.insert({"+",add});
    
    binops.insert({"/", div});  //错误,div 不是函数指针
    

    标准库 function 类型

    function 定义在 functional 头文件中。 function 是一个模板,创建具体的 function 类型时需要提供额外的信息。

    function<int(int,int)>
    
    divide div;
    std::function<int(int,int)> f1 = add;       // 函数指针
    std::function<int(int,int)> f2 = div;       // 函数对象类的对象
    std::function<int(int,int)> f3 = [](int i,int j){return i * j;};    // lambda
    std::function<int(int,int)> f4 = mod; // lambda
    
    //调用
    std::cout<<f1(7,3)<< std::endl;
    std::cout<<f2(7,3)<< std::endl;
    std::cout<<f3(7,3)<< std::endl;
    std::cout<<f4(7,3)<< std::endl;
    

    使用 function 重新定义上面的函数表:

    divide div;
    std::map<std::string, std::function<int(int,int)>> binops =
    {
        {"+", add},     //函数指针
        {"-", std::minus<int>()}, // 标准库函数对象
        {"/", div},     // 自定义函数对象
        {"*", [](int i,int j){return i * j;}},          // 未命名 lambda 表达式
        {"%", mod}, // 命名 lambda 表达式
    };
    

    调用:

    std::cout << binops["+"](19,5) << std::endl;
    std::cout << binops["-"](19,5) << std::endl;
    std::cout << binops["/"](19,5) << std::endl;
    std::cout << binops["*"](19,5) << std::endl;
    std::cout << binops["%"](19,5) << std::endl;
    

    重载的函数与 function

    不能直接将重载函数的名字存入 function 类型的对象中。

    struct Sales_data;
    int add(int i,int j){return i + j;}
    Sales_data add(const Sales_data& lhd, const Sales_data& rhd);
    
    int main() {
        std::map<std::string, std::function<int(int,int)>> binops;
        binops.insert({"+", add}); // 错误,不能区分是哪个 add
        return 0;
    }
    

    解决上面问题的有效途径是存储函数指针而非函数名字:

    int (*fp) (int, int) = add;
    binops.insert({"+", fp});   // 正确,fp 指向正确的 add 版本
    

    同样,也可以使用 lambda 来指定希望使用的 add 版本:

    binops.insert({"+", [](int a, int b){ return add(a,b);} });
    

    注意:本文非原创,内容摘录自《函数调用运算符》,感谢原作者的付出和无私分享。

    相关文章

      网友评论

        本文标题:025 C++ 函数调用运算符

        本文链接:https://www.haomeiwen.com/subject/axtofktx.html