前言

之前在学muduo网络库时,看到陈硕以基于对象编程的方式,大量使用boost库中的bindfunction机制,如今,这些概念都已引入至C++11,包含在头文件<functional>中。

本篇文章主要梳理C++绑定器相关的内容以及C++11中引入的function机制,其中绑定器主要有三种:bind1stbind2ndbind(C++11)。学完本篇内容,将对C++绑定器及function机制等的底层实现有深刻理解,那么我们开始说吧。

函数对象

首先说说函数对象,之所以说函数对象,是因为绑定器、function都涉及到该部分概念。函数对象实际上是类调用operator()()小括号运算符重载,实现像在“调用函数”一样的效果,因此还有个别名叫“仿函数”。函数对象示例代码如下:

class Print {
public:
void operator()(string &s) { cout << s << endl; }
}; int main() {
string s = "hello world!";
Print print; //定义了一个函数对象print
print(s);
return 0;
}

上面代码print(s);语句,看似像函数调用,其实是类对象print调用其小括号运算符重载print.operator(string &s)print就是一个函数对象,至此对函数对象就有了基本的认识。

剖析绑定器bind1st、bind2nd

了解了函数对象,接下来我们说说绑定器,为什么需要绑定器?在使用STL时经常会遇到STL算法中需要传递某元函数对象,比如在写sort时,第三个参数决定了我们的排序规则,用来接收一个“比较器”函数对象,该函数对象是一个二元的匿名函数对象,形如greator<int>()或者less<int>()。二元函数对象的意思是,这个函数对象的小括号运算符重载函数接收两个参数,那么几元就表示接收几个参数。下面是库中自带的greaterless模板类的源码实现,可以看到是对小括号运算符重载的实现,sort第三个参数接收该模板类的二元匿名函数对象。

  template<typename _Tp>
struct greater : public binary_function<_Tp, _Tp, bool>
{
_GLIBCXX14_CONSTEXPR
bool
operator()(const _Tp& __x, const _Tp& __y) const
{ return __x > __y; }
}; template<typename _Tp>
struct less : public binary_function<_Tp, _Tp, bool>
{
_GLIBCXX14_CONSTEXPR
bool
operator()(const _Tp& __x, const _Tp& __y) const
{ return __x < __y; }
};

再回到刚才的问题,那为什么需绑定器?由于STL接口的限制,有时我们拿到的函数对象和特定STL算法中要接收的函数对象在参数上并不匹配,意思就是需要传递一个一元函数对象,你有一个二元函数对象,那可以通过绑定器提前绑定二元函数对象的其中一个参数,使得最终返回的是一个一元函数对象,那么从二元函数对象到一元函数对象的转换过程,就需要绑定器去实现。

如STL中的泛型算法find_if,可用来查找可变长数组vector中符合某个条件的值(这个条件比如是要大于50,要小于30,要等于25等等)。其第三个参数需要传递一个一元函数对象,假如现在要找到第一个小于70的数,可将绑定器与二元函数对象结合,转换为一元函数对象后传递给find_if

我们知道系统自带的greater<int>()less<int>()模板类对象是二元匿名函数对象,所以需要通过绑定器将其转换为一元函数对象,可以通过bind1stbind2nd去绑定,顾名思义,前者对二元函数对象的第一个参数进行绑定,后者对二元函数对象的第二个参数进行绑定,两个绑定器均返回一元函数对象,用法如下:

sort(vec.begin(), vec.end(), greater<int>()); //从大到小对vector进行排序
find_if(vec.begin(), vec.end(), bind1st(greater<int>(), 70));
find_if(vec.begin(), vec.end(), bind2nd(less<int>(), 70));

两个绑定器分别提前绑定了一个参数,使得二元函数对象+绑定器转换为一元函数对象:

operator()(const T &val)
greater a > b ====> bind1st(greater<int>(), 70) ====> 70 > b
less a < b ====> bind2nd(less<int>(), 70) ====> a < 70

下面给出bind1st绑定过程图,二元函数对象绑定了第一个数为70,变为一元函数对象,传递给find_if泛型算法,此时find_if所实现的功能就是:找出有序降序数组中第一个小于70的数,所以find_if返回指向65元素的迭代器:

file:///Users/guochen/Notes/docs/media/16656563650484/16657214749366.jpg

以上就是绑定器的概念。因此需要绑定器的原因就很明显了,绑定器可以返回一个转换后的某元函数对象,用于匹配泛型算法。

根据上面的理解,接下来实现一下bind1st,代码实现如下:

/*可以看到 自己实现的绑定器本质上也是个函数对象 调用operator()进行绑定*/
template<typename Compare, typename T>
class _mybind1st {
public:
_mybind1st(Compare comp, T first) : _comp(comp), _val(first) {}
bool operator()(const T &second) {
return _comp(_val, second);
}
private:
Compare _comp;
T _val;
}; /*实现bind1st 函数模板*/
//直接使用函数模板,好处是可以进行类型推演
template<typename Compare, typename T>
_mybind1st<Compare, T> mybind1st(Compare comp, const T &val) { //绑定器返回值_mybind1st为一元函数对象
return _mybind1st<Compare, T>(comp, val);
}

上述代码中mybind1st绑定器第一个参数Compare comp是要绑定的二元函数对象,第二个参数val是在原有函数对象上绑定的值,最后绑定器调用_mybind1st模板函数对象的小括号运算符重载并返回该一元匿名函数对象,可以看到_mybind1st小括号运算符重载中已将绑定器mybind1st第二个参数val传递给了原本的二元函数对象Compare comp,因此原本绑定器接收的二元函数对象只需要处理第二个参数。所以绑定器返回的函数对象_mybind1st其实是在原本的函数对象上套了一层参数的新的函数对象,阅读上面的代码实现,就可更深刻的理解bind1st的底层原理。

与此同时,不难写出bind2nd的实现,顾名思义该绑定器是对第二个参数进行绑定,不过多赘述,贴出实现代码:

template<typename Compare, typename T>
class _mybind2nd {
public:
_mybind2nd(Compare comp, T second) : _comp(comp), _val(second) {}
bool operator()(const T &first) {
return _comp(first, _val);
}
private:
Compare _comp;
T _val;
}; template<typename Compare, typename T>
_mybind2nd<Compare, T> mybind2nd(Compare comp, const T &val) {
return _mybind2nd<Compare, T>(comp, val);
}

根据上文,我们清楚了解到泛型算法find_if第三个参数接收一元函数对象,且该泛型算法功能是寻找第一个符合某条件的元素,我们对其补充实现,代码贴出:

/**
* 自己实现了find_if后发现其实绑定器返回的就是绑定后的函数对象
* 使用绑定器的目的:就是将原本某元的函数对象转化为另一个元的函数对象
* 说白了,绑定器还是对函数对象的一个应用
**/
template<typename Iterator, typename Compare>
Iterator my_find_if(Iterator first, Iterator last, Compare comp) {
for(; first != last; ++first) {
if(comp(*first)) { //调用comp的小括号运算符重载 一元函数对象 comp.operator()(*first)
return first;
}
}
return last;
}

此时要寻找vector中第一个小于70的数,就可以这样写:

auto it = my_find_if(vec.begin(), vec.end(), mybind1st(greater<int>(), 70));
cout << *it << endl; //打印vec中第一个小于70的数值

以上,围绕bind1stbind2nd以及函数对象等,展开讨论了绑定器bind1stbind2nd的实现原理,但是同时我们也发现其缺点,就是只能对二元函数对象进行绑定转换,让其转换为一元函数对象,那如果遇到很多元的函数对象,我们还得一个一个自己去实现吗?所以将boost库的boost::bind引入到了C++11标准库中,接下来我们介绍C++11的绑定器std::bind,它是对上述两种绑定器的泛化。支持任意函数对象(其实标准库中最多支持29元函数对象,不过这也足够使用了)。

补充:上面都是以函数对象为例,作为绑定器第一个参数传递,其实第一个参数可以是函数对象、成员函数、也可以是普通函数。

总结:绑定器本身是函数模板,绑定器第一个参数可能是普通函数、成员函数或函数对象等,返回的一定是函数对象。还有就是这两个绑定器在C++17中已移除,因此仅用于学习和理解绑定器,也方便我们对C++11引入的bind的学习。至于当前这两个绑定器如何实现对类成员函数的绑定等等我们也没必要去寻找答案了(我一开始也在努力寻找如何使用这两个绑定器去绑定类成员函数,但是发现bind可以很轻松地做到,当然如果大家知道怎么使用bind1stbind2nd绑定类成员函数,也可以评论告知我,感谢~)。

C++11 bind通用绑定器(函数适配器)

我们可将bind函数看作是一个通用的函数适配器,它接受一个可调用函数对象,生成一个新的可调用函数对象来“适应”原对象的参数列表。bind相比于bind1st和bind2nd,实现了“动态生成新的函数”的功能。简言之,可通过bind函数修改原函数并生成一个可以被调用的对象,类似于函数的重载,但是我们又不需要去重新写一个函数,用bind函数就可以实现。相信在上面讲bind1st和bind2nd时,大家对这些关于绑定器(函数适配器)的概念已经有所认知,我们直接看看如何用的吧。

绑定一个普通函数和函数指针

#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;
int fun(int a, int b, int c, int d, int e) {
return a + b - c + d - e;
}
int main() {
int x = 1, y = 2, z = 3;
auto g = bind(fun, x, y, _2, z, _1); //第一个参数&可省略 但最好写成&fun
cout << g(11, 22) << endl; // fun(1, 2, 22, 3, 11) => 1+2-22+3-11
// cout << bind(fun, x, y, _2, z, _1)(11, 22) << endl; //等价
}

g是有两个参数的二元函数对象,其两个参数分别用占位符placeholders::_2placeholders::_1表示,_2代表二元函数对象的第二个参数22_1代表二元函数对象的第一个参数11。这个新的可调用对象将它自己的参数作为第三个和第五个传递给fun,fun函数的第一个、第二个第四个参数分别被绑定到给定的值xyz上。

绑定一个类的静态成员函数与绑定全局函数没有任何区别,这里不做说明,可参考文章:[ bind绑定器使用方法 ],该文章中bind详细用法中描述了对静态成员方法的使用。

绑定一个类成员函数

绑定器绑定一个成员函数时,我们知道非静态成员函数第一个参数隐藏了一个this指针对象,所以绑定时绑定器第二个参数传递匿名类对象本身。bind和之前的bind1stbind2nd一样,最终返回的一定是函数对象,下面的代码将一个五元函数绑定后,返回了一个三元函数对象,效果等同于调用f.operator()(10, 6, 7)

#include <iostream>
#include <functional>
using namespace std;
using namespace placeholders;
class Test {
public:
int func(int a, int b, int c, int d, int e) { return a + b - c + d - e; }
}; int main() {
auto f = bind(&Test::func, Test(), _1, 12, _3, 5, _2);
cout << f(10, 6, 7) << endl; //输出:10+12-7+5-6 = 14
cout << f.operator()(10, 6, 7) << endl;
}

作为类成员函数,需要注意的一点是,如果是非静态的成员函数,它会存在一个默认的this指针,静态的成员函数则不存在this指针,所以在将其作为bind函数的参数时,需要注意使用this指针作为其中一个参数,当使用静态成员函数作为参数时,其用法和全局函数类似,当参数为类内非静态成员函数时,第一个参数必须使用&符号。

注:成为成员函数时,第一个参数之所以必须使用&符号,这部分原因可参考:[ C++中普通函数指针与类成员函数指针的异同 ],文章中有说明具体原因。

以上就是C++11 bind的使用方法,衍生于bind1stbind2nd,支持更多的参数绑定,关于bind函数更多的使用方法,也可参考C++Boost的说明文档:[ Boost.Bind ]。关于bind函数绑定的过程,可参考:[ bind原理图释 ],该文章中的图片方便我们对绑定过程的理解。

C++11 function机制

C++11的function机制是C语言中函数指针的衍生,用来实现回调功能,我们上面的绑定器通常都是以语句执行为单位,当出了某个语句的执行后,绑定器返回的这个函数对象也就随之消失,因此需要有回调功能的function去长期保留绑定器返回的函数对象,以便在需要的时候随时通过function机制调用即可。那有人会问,既然有函数指针,为什么还要再整出来一个function机制?这不是多此一举吗?答案肯定是:很有必要,因为function能做到的,函数指针未必能做到,接下来容我花点篇幅去说明为什么C++中有函数指针还需要std::function

为什么C++中有函数指针还需要std::function?

C/C++中可以使用指针指向一段代码,这个指针就叫函数指针,假设有这样一段代码:

#include <stdio.h>

int func(int a) { return a + 1; }

int main() {
int (*f)(int) = func;
printf("%p\n", f);
return 0;
}

我们定义了一个函数func,然后使用指针变量f指向该函数,然后打印出变量f指向的地址,代码很简单,然后我们编译一下,看下编译后生成的指令,我们重点关注func函数:

int func(int a) {
4005b6: 55 push %rbp
4005b7: 48 89 e5 mov %rsp,%rbp
4005ba: 89 7d fc mov %edi,-0x4(%rbp)
return a + 1;
4005bd: 8b 45 fc mov -0x4(%rbp),%eax
4005c0: 83 c0 01 add $0x1,%eax
}
4005c3: 5d pop %rbp
4005c4: c3 retq

可以看到,编译好后的函数func位于地址0x4005b6这个地址,让我们记住这个地址。然后运行一下编译后生成的程序,想一想这段代码会输出什么呢?显然应该是func函数的在内存中的地址!

[root@localhost 07]# ./a.out
0x4005b6

没有猜错吧,实际上函数指针本质也是一个指针,只不过这个指针指向的不是内存中的一段数据而是内存中的一段代码,就像这样:

看到了吧,我们常说的指针一般都是指向内存中的一段数据,而函数指针指向了内存中的一段代码,在这个示例中指向了内存地址0x4005b6,在这个地址中保存了函数func的机器指令。

现在你应该明白函数指针了,细心的同学可能会有一个疑问,为什么编译器在生成可执行文件时就知道函数func存放在内存地址0x4005b6上呢?这不应该是程序被加载到内存后开始运行时才能确定的吗?

函数指针的作用是可以把一段代码当做一个变量传来传去,主要的用途之一就是回调函数。关于回调函数其实是在A模块定义,在B模块被调用,就像这样:

然而有时我们会有这样的场景,我们依然需要在模块A定义函数,同时函数A的运行需要依赖B模块产生的数据,然后将模块A定义的函数和模块B产生的数据一并传递给C模块来调用,就像这样:

此时,单纯的函数指针已经不够用了,因为函数指针只是单纯的指向了内存中的一段代码,我们不但需要将内存中的一段代码同时也需要将内存中的一块数据传递给模块C,此时你可以定义一个结构体,将代码和数据打包起来,就像这样:

typedef void (*func)(int);

struct functor {
func f;
int arg;
};

我们将这个结构体命名为functor,注意看,这个结构中有两部分:

  • 一个指向代码的指针变量
  • 一个保存数据的变量

这样,我们在A模块为指针变量赋值,在B模块为保存数据的变量赋值,然后将此结构体传递给模块C,模块C中可以这样使用:

void run(struct functor func) {
func->f(func->arg);
}

即,functor既包含了一段代码也包含了这段代码使用的数据,这里的数据也被称为context,即上下文,或者environment,即环境,不管怎么称呼,其实就是函数运行依赖的数据:

而这也正是C++中std::function的目的所在。

单纯的函数指针并没有捕捉上下文的能力,这里的上下文就是指代码依赖的数据,你不得不自己动手构造出一个结构体用来存储代码依赖的上下文。在C++中你没有办法单纯的利用函数指针指向对象的成员函数,就是因为函数指针没有办法捕捉this(指向对象的指针)这个上下文。

️注:std::function的作用本质上和我们刚才定义的结构体区别不大。

利用std::function你不但可以保存一段代码,同时也可以保存必要的上下文,然后在合适的地方基于上下文调用这段代码。


根据上文的介绍,我们也知道std::function相比函数指针的优势所在,要去理解std::function,只需要理解上面提到的结构体即可。接下来我们来谈谈std::function的用法以及一步一步实现一个简单的std::function,剖析其原理。

function的基本用法

接下来直接展示function的直观用法,我们可以把function想象为一个模板类,调用该模板类的operator()()小括号运算符重载,执行封装的函数指针,关于std::function具体实现细节,后续再继续说明,函数指针可用于回调功能,函数对象也可用于回调功能,lambda表达式也可用于回调功能,甚至bind绑定适配后的成员函数也可用于回调功能,那么在不确定的情况下,通过function机制这样的泛型机制统一表示,就会很方便。

普通函数:

void hello() {cout << "hello world!" << endl;}
void hello_str(string str) {cout << str << endl;}
int main() {
function<void()> func1 = &hello;
// function<void()> func1(&hello); // 两种调用方法均可
func1(); //调用func1.operator()() ==> void hello()
function<void(string)> func2 = &hello_str;
func2("hello world"); //调用func2.operator()(string) ==> void hello_str(string)
return 0;
}

模板函数:

template<typename T>
T sum(T a, T b) { return a + b; }
int main() {
function<int(int, int)> func1 = sum<int>;
//调用func1.operator()(int, int) ==> int sum(int, int);
cout << func1(3, 5) << endl; //输出8
return 0;
}

lambda表达式:

int main() {
function<int(int, int)> func1 = [](int a, int b)->int { return a + b; };
cout << func1(3, 5) << endl; //打印8 调用func1.operator()(int, int) ==> 调用lambda表达式返回求和结果
return 0;
}

函数对象:

class PrintAdd1 {
public:
void operator()(int left, int right) {
cout << "sum : " << left + right << endl;
}
};
int main() {
function<void(int, int)> func1 = PrintAdd1(); //调用默认无参构造函数创建匿名类对象给func1
func1(3, 5); //func1.operator()(int, int) ==> 调用void PrintAdd1.operator(int, int)
return 0;
}

模板函数对象:

template<typename T>
class PrintAdd2 {
public:
void operator()(T left, T right) {
cout << "sum : " << left + right << endl;
}
};
int main() {
function<void(int, int)> func1 = PrintAdd2<int>(); //调用默认无参构造函数创建匿名模板类对象给func1
func1(3, 5); //func1.operator()(int, int) ==> 调用void PrintAdd2.operator()(int, int)
return 0;
}

类静态成员函数:

class StaticClass1 {
public:
static void hello_static(string s) {
cout << s << endl;
}
};
int main() {
function<void(string)> func1 = &StaticClass1::hello_static;
func1("hello world"); //func1.operator()(string) ==> 调用void hello_static(string)
return 0;
}

模板类静态成员函数:

template<typename T>
class StaticClass2 {
public:
static void hello_static(T out) {
cout << out << endl;
}
};
int main() {
function<void(string)> func1 = &StaticClass2<string>::hello_static;
func1("static.. hello world"); //func1.operator()(string) ==> 调用void StaticClass2<string>::hello_static(string)
return 0;
}

普通类成员函数:

class Test {
public:
void hello(string str) {
cout << str << endl;
}
};
int main() {
// function<void(Test *, string)> func = &Test::hello;
// func(&Test(), "call Test::hello"); //这种第一个参数传递匿名对象的方法在GCC8.4下不可行 在vs2017下可行 不建议使用匿名对象
Test test; //定义对象
function<void(Test *, string)> func1 = &Test::hello;
func1(&test, "call Test::hello"); //func1.operator(Test *, string) ==> 调用void Test::hello(string)
return 0;
}

模板类成员函数:

template<typename T>
class Test2 {
public:
void hello(T str) {
cout << str << endl;
}
};
int main() {
Test2<string> test2;
function<void(Test2<string> *, string)> func1 = &Test2<string>::hello;
func1(&test2, "call template Test::hello"); //func1.operator(Test2 *, string) ==> 调用void Test2<string>::hello(string)
return 0;
}

function底层原理剖析

function用法有基本了解后,为了剖析function底层原理,我们还需知道模板的「特例化」以及「可变参数模板』,这里不再说明,可参考我以下两篇博文,已经对模板特化和可变参数模板进行了解释:

function是C++11特有的一种比函数指针更灵活的机制,现在如果我们要接收一个hello函数,形如:

void hello(string str) { cout << str << endl; }

该如何实现呢?function利用了函数对象的手段,结合函数指针去调用小括号运算符重载实现,因此理所应当的实现是这样的,内部有一个函数指针_pfunc,并且该函数指针在operator()小括号运算符重载重被调用:

//前向声明模板类
template<typename T>
class myfunction {}; //单个类型参数模板完全特例化
template<typename R, typename A1>
class myfunction<R(A1)> {
public:
using PFUNC = R (*)(A1);
public:
myfunction(PFUNC pfunc) : _pfunc(pfunc) {}
R operator()(A1 arg) {
return _pfunc(arg);
}
private:
PFUNC _pfunc;
};

function对象接收hello函数时,R作为返回值会被推导为voidA1作为单一参数类型被推导为string类型:

myfunction<void(string)> f1 = &hello;
f1("hello world"); //打印"hello world"

那现在如果我们要接收两个参数的sum求和函数呢?

int sum(int a, int b) { return a + b; }

其实理解了function的原理后,这个时候要接收一个双参数sum求和函数,可以再去特化一个适合sum的类,其中R推导为intA1sum的第一个参数类型intA2sum的第二个类型int

#include <iostream>
using namespace std; int sum(int a, int b) { return a + b; } template<typename T>
class myfunction {}; //两个类型参数模板完全特例化
template<typename R, typename A1, typename A2>
class myfunction<R(A1, A2)> {
public:
using PFUNC = R (*)(A1, A2);
public:
myfunction(PFUNC pfunc) : _pfunc(pfunc) {}
R operator()(A1 arg1, A2 arg2) {
return _pfunc(arg1, arg2);
}
private:
PFUNC _pfunc;
}; int main() {
myfunction<int(int, int)> f2 = &sum;
// myfunction<int(int, int)> f2(&sum);
cout << f2(3, 4) << endl; // 输出5
return 0;
}

以上就是function去接收单参数和双参数时的实现,但是这有个很致命的缺点,如果要接收三个参数、四个参数、十个参数、一百个参数、一千个参数呢?(当然这不太可能,就是单纯去考虑这种情况),那是不是还得对不同参数都进行一次实现?那模板的意义何在?如何消除代码冗余就是问题所在,我们需要用到可变模板参数,我的这篇文章已经说明什么是可变参数模板,如何去使用它:[ 泛化之美 —— C++11 可变参数模板的妙用 ]

所以通过可变模板参数,我们去实现一个可变参数的function,该function可以接收任意数量参数的函数、函数对象、lambda表达式、bind适配后的成员函数和普通函数等等:

template<typename T>
class myfunction {}; /*那如果类型参数多了,我们还要一个一个定义吗??其实可以采用变长参数模板实现*/
// 可变参数模板完全特例化
template<typename R, typename... A>
class myfunction<R(A...)> {
public:
using PFUNC = R (*)(A...);
public:
myfunction(PFUNC pfunc) : _pfunc(pfunc) {}
R operator()(A... arg) {
return _pfunc(arg...);
}
private:
PFUNC _pfunc;
};

根据以上实现,我们对function的底层原理有了深刻的认识,能够自己实现一个接收任意参数的function模板类。虽然以上实现和标准库还差得很远,但是起到了学习、理解的作用吧。

总结

本文通过C++11之前的bind1stbind2nd引入,介绍了C++11的bind的使用,理清了它的作用,何谓“绑定”?然后介绍了function的使用方法,并对其进行了逻辑上的实现,以上这些知识点都挺难的,涉及函数对象、模板特化、可变参数模板。但是也算是一次系统性总结吧,感觉意义蛮大的。C++11的路还很远,以后有机会再继续深耕吧。

顺便记录一下,我还在为自己的秋招迷茫中,如果有好消息,我在回来还愿!

C++11绑定器bind及function机制的更多相关文章

  1. C++之函数适配器--绑定器bind原理图解

    转自:http://www.cnblogs.com/xusd-null/p/3698969.html#3081606 本文解释了bind 是如何工作的.为了清晰,图中的语法作了一些简化(例如,省略函数 ...

  2. C++11 学习笔记 std::function和bind绑定器

    C++11 学习笔记 std::function和bind绑定器 一.std::function C++中的可调用对象虽然具有比较统一操作形式(除了类成员指针之外,都是后面加括号进行调用),但定义方法 ...

  3. boost库在工作(15)绑定器与函数对象之三

    前面已经可以优美地解决两个参数的函数给算法for_each调用了,但是又会遇到这样的一种情况,当需要三个参数或者三个以上的参数给算法for_each调用呢?从STL里的绑定器bind1st,显然是不行 ...

  4. c++11——std::function和bind绑定器

    c++11中增加了std::function和std::bind,可更加方便的使用标准库,同时也可方便的进行延时求值. 可调用对象 c++中的可调用对象存在以下几类: (1)函数指针 (2)具有ope ...

  5. 第11课 std::bind和std::function(2)_std::bind绑定器

    1. 温故知新:std::bind1st和std::bind2nd (1)bind1st.bind2nd首先它们都是函数模板,用于将参数绑定到可调用对象(如函数.仿函数等)的第1个或第2个参数上. ( ...

  6. C++11的闭包(lambda、function、bind)

    c++11开始支持闭包,闭包:与函数A调用函数B相比较,闭包中函数A调用函数B,可以不通过函数A给函数B传递函数参数,而使函数B可以访问函数A的上下文环境才可见(函数A可直接访问到)的变量:比如: 函 ...

  7. jquery阻止冒泡事件:$('span').bind("click",function(event){event.stopPropagation();})(有用源)

    冒泡事件就是点击子节点,会向上触发父节点,祖先节点的点击事件. <body> <div id="content"> 外层div元素 <span> ...

  8. jQuery事件绑定方法bind、 live、delegate和on的区别

    我们试图绑定一些事件到DOM元素上的时候,我相信上面这4个方法是最常用的.而它们之间到底有什么不同呢?在什么场合下用什么方法是最有效的呢? 1.准备知识 当我们在开始的时候,有些知识是必须具备的: 1 ...

  9. jQuery中绑定事件bind() on() live() one()的异同

    jQuery中绑定事件的四种方法,他们可以同时绑定一个或多个事件 bind()-------------------------版本号小于3.0(在Jquery3.0中已经移除,相应unbind()也 ...

随机推荐

  1. Python 工匠:一个关于模块的小故事

    前言 模块(Module)是我们用来组织 Python 代码的基本单位.很多功能强大的复杂站点,都由成百上千个独立模块共同组成. 虽然模块有着不可替代的用处,但它有时也会给我们带来麻烦.比如,当你接手 ...

  2. CCF NOI Online 2021 提高组 T2 积木小赛 (子序列自动机+后缀自动机,O(n^2))

    题面 Alice 和 Bob 最近热衷于玩一个游戏--积木小赛. Alice 和 Bob 初始时各有 n 块积木从左至右排成一排,每块积木都被标上了一个英文小写字母. Alice 可以从自己的积木中丢 ...

  3. 【面试题】如何去掉vue的url地址中的#号?及其原理?

    如何去掉vue的url地址中的#号?及其原理? 点击打开视频讲解更加详细 如何去掉vue的url地址中的#号? import Vue from 'vue'; import VueRouter from ...

  4. 免杀手法-tcp套字节传递shellcode学习

    免杀手法-tcp套字节传递shellcode学习

  5. 【java】学习路径25-ArrayList类,Vector类,LinkedList类的使用和区别,Iterator迭代器的使用

    ArrayList的使用 ArrayList类:可变化长度的数组. 与一般的数组不同的是,其长度不固定,可以添加任意类型的数据. 也可以添加不同类型的数据,但是一般不这么做. ArrayList类位于 ...

  6. HC32L110(三) HC32L110的GCC工具链和VSCode开发环境

    目录 HC32L110(一) HC32L110芯片介绍和Win10下的烧录 HC32L110(二) HC32L110在Ubuntu下的烧录 HC32L110(三) HC32L110的GCC工具链和VS ...

  7. 简单创建一个SpringCloud2021.0.3项目(四)

    目录 1. 项目说明 1. 版本 2. 用到组件 3. 功能 2. 上三篇教程 3. 日志处理 1. 创建日志公共模块 2. Eureka引入日志模块 4. 到此的功能代码 5. 注册中心换成naco ...

  8. 简单创建一个SpringCloud2021.0.3项目(一)

    目录 1. 项目说明 1. 版本 2. 用到组件 3. 功能 2. 新建父模块和注册中心 1. 新建父模块 2. 新建注册中心Eureka 3. 新建配置中心Config 4. 新建两个业务服务 1. ...

  9. KingbaseES 参数 - ora_statement_level_rollback

    参数 ora_statement_level_rollback 控制KingbaseES 是否实现类似oracle 语句级的回滚.当该参数打开时,如果事务操作失败,仅会回滚最后一条操作,避免了全部操作 ...

  10. C#winform中使用Cef的ChromiumWebBrowser内嵌谷歌内核,调用前端js方法

    1.在winform中调用js方法: --调js中的方法无 入参形式 webBrowser1.ExecuteJavascript("logout()"); --调js中的方法给js ...