C++进阶(7)——包装器 - 教程

目录

包装器

function

基本介绍

function的统一性

function包装器的应用

总结一下:

bind

基本介绍

bing包装器的使用

bind包装器调整传入参数的顺序

总结一下


包装器

function

基本介绍

function是一个类模板,也是一个包装器。function的实例对象可以包装存储其他的可以调用对象,包括了函数指针、仿函数、lambda和bind表达式等,存储的可调用对象被称之为function的目标。

类模板的原型如下:

template 
class function; // undefined;
template 
class function;

参数说明:

Ret:被包装存储的可调用对象的返回值类型。

Args...:被包装存储的调用对象的形参类型。

我们这里还是来举出一些栗子:

#include 
#include 
using namespace std;
int f(int a, int b) {
    return a + b;
}
struct Functor {
    public:
        int operator() (int a, int b) {
            return a + b;
        }
};
class Plus {
    public:
        Plus(int n = 10) : _n(n) {}
        static int plusi(int a, int b) {
            return a + b;
        }
        double plusd(double a, double b) {
            return (a + b) * _n;
        }
    private:
        int _n;
};
int main() {
    // 包装可调用对象
    function f1 = f;
    function f2 = Functor();
    function f3 = [](int a, int b) {
        return a + b;
    };
    cout << f1(1, 1) << endl;
    cout << f2(1, 1) << endl;
    cout << f3(1, 1) << endl;
    // 包装静态成员函数
    // 这里需要指定类域并且在前面加上&才能获取地址
    function f4 = &Plus::plusi;
    // function f4 = Plus::plusi; // 这里也可以没有&
    cout << f4(1, 1) << endl;
    // 包装普通成员函数
    // 普通成员函数还有一个隐含的this指针,所以绑定的时候传入对象或是对象指针都是可以的
    function f5 = &Plus::plusd;
    Plus pd;
    cout << f5(&pd, 1.1, 1.1) << endl;
    function f6 = &Plus::plusd;
    cout << f6(pd, 1.1, 1.1) << endl;
    function f7 = &Plus::plusd;
    cout << f7(move(pd), 1.1, 1.1) << endl;
    cout << f7(Plus(), 1.1, 1.1) << endl;
    return 0;
}

测试效果:

敲黑板:

我们这里取静态成员函数的地址可以不用取地址运算符“&”,但是我们取非静态成员函数的地址就必须要使用取地址运算符了。

我们这里包装非静态成员函数的时候,要注意还有一个隐含的this指针参数,这里我们要指明第一个参数的类型,可以值对象指针,也可以是对象。

function的统一性

我们这里可以将我们的function包装器和我们的函数模板来进行一个对比,首先我们要有一个函数模板,这个函数模板的要求如下:

1、传入这个函数的第一个参数可以是任意的可以调用的对象,就是我们上面所提到的函数指针、仿函数、lambda表达式等。

2、这个函数模板中需要定义一个静态变量,然后每次打印这个静态变量的地址,看看是不是调用的同一个函数。

函数模板:

template
T func(F f, T t) {
    static int temp = 1;
    cout << "&temp: " << &temp << endl;
    return f(t);
}

测试代码:

int f(int x) {
    return x * 2;
}
struct Functor {
    int operator()(int x) {
        return x * 3;
    }
};
int main() {
    // 函数指针
    cout << func(f, 2) << endl;
    cout << "****************" << endl;
    // 仿函数
    cout << func(Functor(), 2) << endl;
    cout << "****************" << endl;
    // lambda表达式
    cout << func([](int x){return x * 4;}, 2) << endl;
    return 0;
}

测试效果:

我们这里可以看到我们的三种方式调用的都不是统一个函数,也是就说我们的func函数实际上是被实例化出来的三份,但是我们直到这里根本没有必要实例化出来三份,因为我们这里的三个函数的返回值和参数类型都是相同的,这里我们就可以使用包装器对着三个可调用对象进行包装了。

示例代码:

int main() {
    // 函数指针
    function f1 = f;
    cout << func(f1, 2) << endl;
    cout << "****************" << endl;
    // 函数对象
    function f2 = Functor();
    cout << func(f2, 2) << endl;
    cout << "****************" << endl;
    // lambda表达式
    function f3 = [](int x) {return x * 4;};
    cout << func(f3, 2) << endl;
    cout << "****************" << endl;
    return 0;
}

测试效果:

我们这里可以看到我们调用的是同一个函数。

function包装器的应用

我们这里给出的示例是一道力扣上面的题——逆波兰表达式求值

这个题目主要考察的是对于栈的运用,我们这里直接给出实现思路:

1、定义栈,然后遍历字符床

2、遍历的过程中遇到了数字字符直接入栈,遍历到了运算符,就从栈中取出两个数字进行运算,然后将我们计算的结果返回栈中。

3、循环上面的步骤直到我们遍历结束,栈顶就是结果。

代码如下:

class Solution {
public:
    int evalRPN(vector& tokens) {
        stack st;
        for (auto& str : tokens) {
            if (str == "+" || str == "-" || str == "*" || str == "/") {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                switch (str[0]) {
                    case '+':
                        st.push(left + right);
                        break;
                    case '-':
                        st.push(left - right);
                        break;
                    case '*':
                        st.push(left * right);
                        break;
                    case '/':
                        st.push(left / right);
                        break;
                }
            } else {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

我们这里的实现比较的臃肿,使用的是switch_case语句来进行的判断和计算,我们这一节学习了包装器,这样的情况是可以用我们的包装器来简化代码的,我们可以将运算符和我们的计算函数之间建立映射关系,然后直接需要的时候调用即可。

示例代码:

class Solution {
public:
    int evalRPN(vector& tokens) {
        stack st;
        // function作为map的映射可调用对象的类型
        map> opFuncMap = {
            {"+", [](int x, int y) { return x + y; }},
            {"-", [](int x, int y) { return x - y; }},
            {"*", [](int x, int y) { return x * y; }},
            {"/", [](int x, int y) { return x / y; }}
        };
        for (auto& str : tokens) {
            if (opFuncMap.count(str)) { // 操作符
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                int ret = opFuncMap[str](left, right);
                st.push(ret);
            } else {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

我们这里使用哈希表映射了对应的函数,着不仅仅可以使代码更加简洁,还可以使代码具有更高的灵活性。

总结一下:

1、function包装器可以统一管理我们的可调用对象。

2、functoin包装器提高了代码的灵活性。

bind

基本介绍

bind是一个函数模板,同时也是一个可调用对象的包装器,可以把它看作是一个函数的适配器,对接受的可调用对象处理后返回一个可调用对象,主要是用来调整参数个数和参数顺序的。

函数模板如下:

simple(1)
template
/* unspecified */ bind (Fn&& fn, Args&&... args);
with return type(2)
template

参数说明:

fn:可调用对象名。

args...:要绑定的参数列表。

调用bind的一般形式

auto newCallable = bind(callable, arg_list);

参数说明:

newCallable:生成的可调用对象。

callable:传入的需要包装的可调用对象。

arg_list:逗号分隔的参数列表,对应给定的callable的参数。

这里需要说明的是:我们的arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数实际上是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示的是生成的可调用对象中参数的位置:_1表示的就是我们newCallable的第一个参数,_2就是第二个参数,以此类推即可,这些占位符放在了placeholders的一个命名空间中。

bing包装器的使用

普通绑定

代码示例:

#include 
#include 
using namespace std;
int Sub(int a, int b) {
    return (a - b) * 10;
}
int main() {
    auto f1 = bind(Sub, placeholders::_1, placeholders::_2);
    cout << f1(10, 5) << endl;
    return 0;
}

测试效果:

这里实现的也是第一个参数传入给placeholders::_1,第二个参数传入给placeholders::_2,并生成了新的可调用对象,所以我们这里的绑定和我们的直接调用可调用对象没什么区别。

绑定固定参数

示例代码:

#include 
#include 
using namespace std;
int Sub(int a, int b) {
    return (a - b) * 10;
}
int main() {
    auto f2 = bind(Sub, placeholders::_1, 2);
    cout << f2(10) << endl;
    return 0;
}

测试效果:

我们这里讲我们的第二个参数固定为了10,所以此时我们传入的时候就只需要传入一个参数即可。

绑定成员函数

这个是我们实现一些项目的时候比较常用的,我们这里的第二个参数需要传入对象的示例(或是对象的指针,这里和上面的function相似)。

示例代码:

#include 
#include 
using namespace std;
class Foo {
public:
    void print(int val) const {
        std::cout << "Foo value: " << val << std::endl;
    }
};
int main() {
    Foo obj;
    function func_binder = bind(&Foo::print, &obj, placeholders::_1);
    // function func_binder = bind(&Foo::print, obj, placeholders::_1); // 也可以传入对象
    func_binder(42);
    return 0;
}

测试效果:

绑定lambda表达式

这里其实和上面的原理一样,就不再赘述了。

示例代码:

#include 
#include 
int main() {
    using namespace std::placeholders;
    auto lambda_add = [](int a, int b) {
        return a + b;
    };
    auto lambda_reorder = std::bind(lambda_add, _2, _1);
    std::cout << "Reordered sum: " << lambda_reorder(10, 20) << std::endl;
    return 0;
}

测试效果:

bind包装器调整传入参数的顺序

我们这里还是给出一个栗子来理解这个过程,就比如下面的代码:

示例代码:

#include 
#include 
using namespace std;
class Add {
    public:
        int add(int x, int y) {
            return x * 10 + y;
        }
};
int main() {
    function f = bind(&Add::add, Add(), placeholders::_1, placeholders::_2);
    cout << f(1, 2) << endl;
    return 0;
}

测试效果:

这个时候我们就可以来对这里的的传参顺序进行一个调整了。

示例代码:

#include 
#include 
using namespace std;
class Add {
    public:
        int add(int x, int y) {
            return x * 10 + y;
        }
};
int main() {
    function f = bind(&Add::add, Add(), placeholders::_2, placeholders::_1);
    cout << f(1, 2) << endl;
    return 0;
}

测试效果:

我们这里可以看到我们的参数确实是变换了顺序,但是这里要说明的是这个功能其实是不常用的,了解即可。

总结一下

1、可以将函数的一些参数固定下来,那样我们就不用传递那些参数了。

2、可以调整我们传入参数的顺序。

posted @ 2025-11-04 13:13  ycfenxi  阅读(1)  评论(0)    收藏  举报