C++11新增了很多东西,只有三个可以称为神器:
1 智能指针
2 移动语义和
3 可调用对象的包装器和绑定器
下面详解可调用对象、包装器std::function、绑定器std::bind
以及它们最有价值的应用场景:
可变函数和参数、回调函数、取代虚函数
可调用对象:
在C++中,可以像函数一样调用的有:普通函数、类的静态成员函数、仿函数、lambda函数、类的非静态成员函数、可被转换为函数的类的对象,统称可调用对象或函数对象。
可调用对象有类型,可以用指针存储它们的地址,可以被引用(类的成员函数除外)
一、普通函数
普通函数类型可以声明函数、定义函数指针和函数引用,但是不能定义函数的实体。
#include <iostream>
using namespace std;
using Fun = void(int, const string &); //普通函数类型的别名
Fun show; //声明普通函数,这一行效果等同于下面这一行
// void show(int, const string &);//声明普通函数
void test()
{
show(1, "a1"); //直接调用普通函数
// 1 C风格的
void (*pf)(int, const string &) = show; //声明函数指针,指向普通函数
pf(2, "a2");
void (&rf)(int, const string &) = show; //声明函数引用,引用普通函数
rf(3, "a3");
// 2 C++风格的
//声明函数指针,指向普通函数
Fun *pf2 = show;
pf2(4, "a4");
//声明函数引用,引用普通函数
Fun &rf2 = show;
rf2(5, "a5");
}
int main()
{
test();
return 0;
}
//定义普通函数
void show(int a, const string &b)
{
cout << a << " " << b << endl;
}
//以下代码是错误的,不能用函数类型定义函数的实体.
// Fun show(int a, const string &b)
// {
// cout << a << " " << b << endl;
// }
/*1 a1
2 a2
3 a3
4 a4
5 a5 */
函数指针我们经常使用,很少使用函数引用,引用的本质是指针,所以,函数指针和函数引用是同一个东西
上面的demo重点表达函数类型和函数对象这两个概念。
在C++中,函数是一种数据类型,函数的实体被看成对象,而不只是一段代码。
函数是对象,可以调用的对象,或者叫函数对象,这种思路和传统的不一样
二、类的静态成员函数
类的静态成员函数和普通函数本质上是一样的,把普通函数放在类中而已
#include <iostream>
using namespace std;
using Fun = void(int, const string &); //别名
struct AA
{
static void show(int a, const string &b)
{
cout << a << " " << b << endl;
}
};
void test()
{
AA::show(1, "a1"); //直接调用静态成员函数
// 1 C风格的
void (*pf)(int, const string &) = AA::show; //声明函数指针
pf(2, "a2");
void (&rf)(int, const string &) = AA::show; //声明函数引用
rf(3, "a3");
// 2 C++风格的
//声明函数指针
Fun *pf2 = AA::show;
pf2(4, "a4");
//声明函数引用
Fun &rf2 = AA::show;
rf2(5, "a5");
}
int main()
{
test();
return 0;
}
三、仿函数
仿函数的本质是类,调用的代码像函数。
仿函数的类型就是类的类型。
普通类创建的对象为什么不是可调用的对象呢?
因为它不能像函数那样直接调用,就扣字面意思,调用它的成员函数是另外一回事。
#include <iostream>
using namespace std;
struct AA
{
void operator()(int a, const string &b)
{
cout << a << " " << b << endl;
}
};
void test()
{
AA aa;
aa(1, "1"); //用对象调用仿函数
AA()
(2, "2"); //用匿名对象调用仿函数,这里换行是VSCODE格式化就这样,不报错
AA &raa = aa; //引用函数
raa(3, "3"); //用对象的引用调用仿函数
}
int main()
{
test();
return 0;
}
仿函数是类,当然可以创建引用
四、lambda函数
lambda函数的本质是仿函数,仿函数的本质是类
#include <iostream>
using namespace std;
void test()
{
auto func = [](int a, const string &b)
{
cout << a << " " << b << endl;
};
func(1, "a"); //用lambda对象调用仿函数
//引用lambda
auto &func2 = func;
func2(2, "b"); //引用lambda对象的引用调用仿函数
}
int main()
{
test();
return 0;
}
五、类的非静态成员函数 (这个与众不同,所以需要包装器和绑定器)
类的非静态成员函数有地址,但是,只能通过类的对象 才能调用它,所以C++对它做了特别处理。
类的非静态成员函数只有指针类型,没有引用类型,不能引用。
C++有6种可调用对象,只有类的非静态成员函数与众不同,正因为它的不同,所以才需要可调用对象的包装器和绑定器。但是它确实最重要的可调用对象,不能没有它。
#include <iostream>
using namespace std;
//普通函数
void show(int a, const string &b)
{
cout << a << " " << b << endl;
}
struct AA
{
void show(int a, const string &b)
{
cout << a << " " << b << endl;
}
};
void test()
{
AA aa;
aa.show(1, "a1");
// 1 传统C风格
void (*pft1)(int, const string &) = show; //普通函数指针,普通函数可以省略&,也可以不省略
void (*pft2)(int, const string &) = &show; //普通函数指针,普通函数可以省略&,也可以不省略
void (AA::*pf)(int, const string &) = &AA::show; //定义类的成员函数的指针,&AA::show,取地址的符号&不能省略
(aa.*pf)(2, "a2"); //用类的成员函数的指针调用成员函数
// 2 C++11风格
//类成员函数的指针类型 起别名
using pFun = void (AA::*)(int, const string &);
pFun pf1 = &AA::show; //让类成员函数的指针指向类的成员函数的地址
(aa.*pf1)(11, "aa1"); //用类成员函数的指针调用类的成员函数
}
int main()
{
test();
return 0;
}
这种调用的方式与其它可调用的对象不一样,其它可调用对象用变量名(对象名)就可以直接调用,而类成员函数这种可调用对象,需要对象名和函数指针变量名组合在一起才能调用。
这种区别给模板带来了麻烦,代码不能统一,模板没法写。
怎么解决这个问题,后面包装器绑定器再说。
六、可被转换为函数指针的类对象 (意义不大)
类可以重载类型转换运算符
语法:
operator 数据类型()
如果数据类型时函数指针或者函数引用类型,那么该类实例也将成为可调用对象。
它的本质是类,调用的代码像函数。
为什么说这种方式没什么意义呢?
因为这个转换函数只能返回普通全局函数和类的静态成员函数,不能返回类的非静态成员函数,普通全局函数和类的静态成员函数本身就是可调用对象,直接调用就行了,用不着用这个,没任何好处。