函数对象的引入
C 中已经存在函数指针的概念,函数指针指向的是内存中的一段代码而非一段可以读写的数据,函数指针最常用的形式就是回调函数,回调函数的本质就是在A模块中定义,在B模块中被调用,如下图:
callback.jpg
因为在A模块定义,因此可以使用A模块变量的作用域,在B模块中被调用,所以调用的实参是从B模块传入的,但是我们有时候有这样的情形,我们依然需要在模块A定义函数,同时函数A的运行需要依赖B模块产生的数据,然后将模块A定义的函数和模块B产生的数据一并传递给C模块来调用,如下图所示:
special_case.jpg
我们依然需要在模块A定义函数,同时函数A的运行需要依赖B模块产生的数据,然后将模块A定义的函数和模块B产生的数据一并传递给C模块来调用,就像这样:
typedef void (*func) (int);
struct closure{
funcpointer f;
int arg;
};
此时可以定义一个结构体,将函数指针和数据打包起来
void run(struct closure func) {
func->f(func->arg);
}
closure既包含了一段代码也包含了这段代码使用的数据,这里的数据也被称为context,即上下文,或者environment,其实就是函数运行依赖的数据;
这就是引入function 类型的原因,单纯的函数指针没有捕捉上下文的能力,这里的上下文就是指代码依赖的数据,我们不得不手动创建一个结构体来保存代码依赖的上下文,上面的定义的结构体本质上可以理解为一个类;
单纯的函数指针并没有捕捉上下文的能力,这里的上下文就是指代码依赖的数据,你不得不自己动手构造出一个结构体用来存储代码依赖的上下文,在C++中你没有办法单纯的利用函数指针指向对象的成员函数,就是因为函数指针没有办法捕捉this(指向对象的指针)这个上下文
function 类型就是为了解决这个问题引入的
利用std::function你不但可以保存一段代码,同时也可以保存必要的上下文,然后在合适的地方基于上下文调用这段代码;
同时std::function也更加通用,你可以用其存储任何可以被调用的对象(callable object),也就是可以存储仿函数对象
function 类型的定义
function 类型支持下面四种类型的赋值
- 普通函数
- 类成员函数
- 仿函数
- 匿名函数(lamda 表达式)
- 普通函数的 函数指针vs函数对象
uint32_t testNormalFunction(uint32_t a) {
cout << "testNormalFunction a: " << a << endl;
return a;
}
{
//normal function pointer
uint32_t(*funcptr)(uint32_t) = testNormalFunction;
(*funcptr)(10); // testNormalFunction a:10
funcptr(10); // testNormalFunction a:10 这种形式也是可以的
}
- 类成员函数
注意类成员函数使用函数对象时,需要传入对象的实例,可以是指针或者引用
注意函数的原型定义完全是在function 的模板参数中定义的
class demoFooClass{
public:
demoFooClass() = default;
~demoFooClass() = default;
uint32_t testPrintFunction(uint32_t a) {
return a + magicNumber;
}
uint32_t magicNumber = 10;
};
//传入对象的指针
{
demoFooClass *p = new demoFooClass();
function<uint32_t(demoFooClass*, uint32_t)> mfptr = &demoFooClass::testPrintFunction;
uint32_t val = mfptr(p, p->magicNumber);
printf("mfptr using pointer value is val: %d\n", val);
}
//传入对象的引用
{
demoFooClass p;
function<uint32_t(demoFooClass&, uint32_t)> mfptr = &demoFooClass::testPrintFunction;
uint32_t val = mfptr(p, p.magicNumber);
printf("mfptr using reference value is val: %d\n", val);
}
- 仿函数
仿函数作为函数类型时,实际上就是绑定重载()的函数,本质上和绑定类成员函数的原理是一样的
class demoMyTestClass {
public:
demoMyTestClass() = default;
~demoMyTestClass() = default;
uint32_t magicNumber = 1;
uint32_t operator()(uint32_t a) {
return a + magicNumber;
}
};
{
demoMyTestClass p;
function<uint32_t(demoMyTestClass&, uint32_t)> mftestptr = &demoMyTestClass::operator();
uint32_t val = mftestptr(p, p.magicNumber);
printf("mftestptr val: %d\n", val);
}
- lamda 表达式
lamda 表达式本质上是一种匿名函数,所属的类型为function 类型
{
uint32_t temp = 100;
function<uint32_t(uint32_t)> p = [=](uint32_t a)->uint32_t {
return a + temp;
};
uint32_t val = p(10);
printf("p val: %d\n", val); // 110
}
std::bind/std::placeholder
std::bind 的作用是绑定函数参数,它解决的问题是:我们有时候不一定能够一次性获取调用某个函数的全部参数,通过 std::bind,我们可以将部分调用参数体腔绑定到函数身上成为一个新的对象,此时的对象函数的参数个数发生了变化,等到最参数全部齐全后,再完成函数的调用
std::placeholder 作为占位符,表示的是还没有确定的参数,注意std::placeholder 顺序可以改变实际调用参数的顺序
- 通过 placeholder 改变实际参数的顺序
class demoFooClass{
public:
demoFooClass() = default;
~demoFooClass() = default;
uint32_t testPrintFunction(uint32_t a, uint32_t b, uint32_t c) {
cout << " testPrintFunction a:" << a << " b:" << b << " c:" << c << endl;
uint32_t sum = a + b + c;
return sum;
}
uint32_t magicNumber = 10;
};
{
demoFooClass p;
function<uint32_t(demoFooClass&, uint32_t, uint32_t, uint32_t)> mftestptr = &demoFooClass::testPrintFunction;
// normal use
auto mBindptr = bind(mftestptr, p, placeholders::_1, placeholders::_2, placeholders::_3);
mBindptr(1,2,3); // a:1 b:2 C:3
// change order
auto nBindptr = bind(mftestptr, p, placeholders::_2, placeholders::_1, placeholders::_3);
nBindptr(1, 2, 3); // a:2 b:1 C:3
}
- 通过 placeholder 改变实际参数的个数
{
demoFooClass p;
function<uint32_t(demoFooClass&, uint32_t, uint32_t, uint32_t)> mftestptr = &demoFooClass::testPrintFunction;
auto mBindptr = bind(mftestptr, p, placeholders::_1, placeholders::_2, 100);
mBindptr(1, 2);
auto nBindptr = bind(mftestptr, p, placeholders::_1, 200, 100);
nBindptr(1);
}
std::function 是一种通用性的,多态性质的函数封装,它的实例可以对任何可以调用的目标进行存储、复制和调用操作,它也是C++中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的),换句话说,就是函数的容器,我们有了函数对象之后能够更加方便的将函数指针,函数本体作为对象处理
lamda 表达式
lamda 表达式就是一个函数(匿名函数),也就是一个没有函数名的函数;因为我们使用它是一次性的,允许直接在原位构造的方式使用它,所以不需要名字
lamda 表达式也叫闭包,闭就是封闭的意思,表示其他地方不会调用它,包就是函数的意思
lamda 本质上是一个函数对象,其内部自动创建了一个重载()操作符的类
lamda 表达式基本形式如下:
lamda形式.jpg
变量捕获
- [] 不捕获任何变量,此时lamda表达式不能访问任何外部变量
- [&] 函数体内可以使用 lambda 所在范围内所有可见的局部变量(包括 lambda 所在类的 this),并且是引用传递方式
(相当于是编译器自动为我们按引用传递了所有局部变量) - [=] 函数体内可以使用 lambda 所在范围内所有可见的局部变量(包括 lambda 所在类的 this),并且是值传递方式(相
当于编译器自动为我们按值传递了所有局部变量) - [=, &a] 以引用的方式捕获a,其余变量按照值的方式进行传递
- [&, a] 以值的方式捕获a,其余变量按照引用的方式进行传递
- [a] 以值的方式捕获a,不捕获其他变量
- [this] 捕获所在类的 this 指针(qt 中使用很多,如此lamda 表达式可以通过this 访问界面空间的数据)
函数参数定义
标识重载的 () 操作符的参数,没有参数时这部分可以省略,参数可以通过按值(如: (a, b))和按引用 (如: (&a, &b)) 两种方式进行传递
mutable 或 exception 声明
这部分可以省略,按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是值本身)exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)
返回值类型
标识函数返回值的类型,当返回值为 void或者函数体中只有一处 return 的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略
std::string mstr = "MagicStr";
uint32_t mVal = 100;
{
uint32_t temp = 10;
auto fptr = [=](uint32_t x, uint32_t y) {
printf("magic str: %s temp value: %d \n", mstr.c_str(), temp);
mstr = "hello";
mVal = 0;
//temp = 0; //compile error [=](uint32_t x, uint32_t y) mutable {
return x + y;
};
// get fptr value: 30 mstr:hello mVal:100 temp:10
printf("get fptr value: %d mstr:%s mVal:%d temp:%d \n", fptr(10, 20), mstr.c_str(), mVal, temp);
}
{
uint32_t temp = 100;
auto fptr = [&](uint32_t x, uint32_t y) {
printf("magic str: %s temp value: %d \n", mstr.c_str(), temp);
mstr = "world";
mVal = 0;
temp = 0;
return x + y;
};
// get fptr value: 30 mstr:world mVal:0 temp:100
printf("get fptr value: %d mstr:%s mVal:%d temp:%d \n", fptr(10, 20), mstr.c_str(), mVal, temp);
}
- 如果是以值传递的方式,那么直接修改局部变量的值会编译出错,如果想修改局部变量的拷贝,需要加上mutable 关键字
- 使用引用传递是可以修改类成员变量的值的
在捕获参数的情况下,lamda 的使用方法如下:
[] (int x, int y) { return x + y; } // 隐式返回类型
[] (int& x) { ++x; } // 没有 return 语句 -> Lambda 函数的返回类型是 'void'
[] () { ++global_x; } // 没有参数,仅访问某个全局变量
[] { ++global_x; } // 与上一个相同,省略了 (操作符重载函数参数)
使用lamda 表达式的优势之一就是可以与容器和算法库相结合
- 使用lamda 表达式用一行代码对容器数据求和
//使用lamda 表达式求和
{
uint32_t sum = 0;
vector<uint32_t> somelist{10, 20 ,30 ,40 ,50};
for_each(somelist.begin(), somelist.end(), [&sum](uint32_t x) {
sum += x;
});
printf("Get vector somelist sum: %d \n", sum); // 150
}
- 循环打印容器内容
{
vector<uint32_t> somelist{ 10, 20 ,30 ,40 ,50 };
for_each(somelist.begin(), somelist.end(), [](uint32_t x) {
printf(" %d", x);
});
printf("\n");
}
网友评论