我们承担ROS,FastDDS等通信中间件,C++,cmake等技术的项目开发和专业指导和培训,有10年+相关工作经验,质量有保证,如有需要请私信联系。
函数指针
定义一个函数指针:
- 使用类型别名:
using func_ptr = type(*) (param_type1, param_type2, ...)
- typedef定义一个函数指针类型:
typedef <return_type> (*func_ptr)(param_type1, param_type2, ...)
int add(int a, int b) { return a+b; }
typedef int (Func)(int, int); //声明一个函数类型
typedef int (*pFunc)(int, int); //声明一个函数指针类型
pFunc func0 = add;
Func *func1 = add;
函数指针作为参数传递给函数时,&是可选的。
函数对象
默认情况下函数对象是值传递而不是引用传递:
- 好处是可以传递常量表达式或暂态表达式(在参数中之间传个值,如1)
- 缺点就是无法改变function object的状态,有三个办法改变:
1. 在外部持有状态,并让function object指向它
2. 以by reference方式传递function object
3. 利用for_each()算法的返回值
概念:函数对象(仿函数)是定义了一个operator()的类
- 三大优点:
- 函数对象比一般函数更灵巧,因为它可以拥有状态
- 有类型,可以作为template的参数,或容器的参数
1. 可以作为排序准则 - 执行速度上,函数对象比函数指针更快
- 特点:
- 可以根据需要重载operator()()函数
- 可以通过类名调用,也可通过实例名称调用
- 缺点:——待补充
- 判断式:返回bool的函数或函数对象
预定义好的函数对象#include<functional>
:
函数适配器
指能够将不同的函数对象结合起来的函数对象,头文件 #include<functional>
- bind函数,, 一般形式为
auto newCallable = std::bind(callable, arg_list)
- newCallable是一个可调用对象,类型为std::function
- callbale:如果是类成员要加取地址符,如
std::bind(&T::t, arg_list)
;它执行的还是callbale,只不过换个参数传递方式 - arg_list: 逗号分割的参数列表,按顺序作为callbale的参数;可包含形如_n的名字,n为整数,占位符,表示可调用对象中参数的位置,_1为newCallable的第一个参数
- _n都定义在一个名为placeholders的命名空间中,而这个命名空间本身定义在命名空间std中,形如:
using std::placeholders::_1
- _n都定义在一个名为placeholders的命名空间中,而这个命名空间本身定义在命名空间std中,形如:
lambda表达式
常见形式
[capture list](parameter list) mutable constexpr noexcept_specifier attributes -> return type { function body }
(C++20P586)
- capture list: 必需。lambda所在函数中定义的局部变量的列表,只有在捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。但可以使用当前函数体之外的变量,通常为空;捕获列表有如下形式:
- [var]:表示值传递捕获边变量var
- [=]:以值传递方式捕获所有父作用域的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域的变量(包括指针)
- [this]:表示值传递捕捉当前的this指针
- [=, &a, &b]:以引用方法捕捉a和b,值传递方式捕捉其他所有变量
- [=, this]:按值捕获所有内容,并显式捕获this指针。在C++20之前,[=]会隐身捕获this指针,C++20之后不会隐式捕获this,如果需要需显式捕获
- 关于捕获块的注意事项:
- 如果默认指定了值捕获或者引用捕获,则不允许额外通过值或引用捕获特定的变量,例如
[=,x]
和[&, &x]
都是无效的 - 对象的数据成员不能被捕获
- 当通过拷贝this指针
[this]
,或拷贝当前对象[*this]
来捕获this时,lambda表达式可以访问被捕获对象的所有公有、保护、私有数据成员和方法。 - 全局变量总是通过引用捕获,即便要求值捕获!如下代码默认值捕获所有内容,单global全局变量仍然是通过引用捕获的。
- 如果默认指定了值捕获或者引用捕获,则不允许额外通过值或引用捕获特定的变量,例如
int global {42};
int main() {
int n1 = 1, n2 = 2, n3 = 3, n4 = 4, n5 = 5;
std::cout << global << std::endl; // global == 42;
auto f01 {[=] {global=2;}};
// auto f01 {[=] {global=2; n1 = 3;}}; 这种方式编译不过
f01();
std::cout << global << std::endl; // global == 2;
}
- (parameter list):参数列表,不能有默认参数。只有当不需要任何参数,不指定mutable,constexpr,noexcept说明符、属性、返回类型或可选条款时,才可以省略
- mutable:可选。lambda函数总是一个默认的const函数,所以在lambda表达式中,按值捕获的非const变量也不能修改,mutable可以取消其常量属性
- constexpr:将lambda表达式标记为constexpr,它就可以在编译期计算。即使省略,如果满足constexpr函数的约束条件,lambda表达式也会隐式成为constexpr
- noexcept说明符:类似普通函数的noexcept
- attributes 属性:指定lambda表达式的属性
- ->return type:
- 返回类型可以省略,这种情况下编译器根据与函数返回类型推导相同的规则来推导lambda表达式的返回类型。
- 与普通函数不同的是,lambda必须使用尾置返回来指定返回类型,如果lambda函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void——这个是什么意思?
- 返回类型推导会移除任何引用和const限定。可以将返回类型与delctype(auto)结合使用使推导出来的类型与真实类型相匹配。
泛型lambda表达式
可以为lambda表达式的参数使用自动类型推导,而不是显式地为它们指定具体类型。若要为参数指定自动类型推导,只需要将类型定为auto,类型推导规则与模板参数推导规则相同。
auto func1 { [] (const auto& val1, const auto& val2) {
return val1 == val2; }
// 编译器为泛型lambda表达式生成的仿函数行为如下:
class xx {
public:
template<typename T1, typename T2> auto operator()(const T1& val1, const T2& val2) const {
return val1 == val2; }
};
模板化lambda表达式
C++20开始支持
[capture list] <typename T> (parameter list) mutable constexpr noexcept_specifier attributes -> return type { function body }
使用lambda表达式的优势:
- 相比较泛型lambda表达式,指定模板类型代码更加易懂。比如,为了获得val的类型:
auto lamb { [] (const auto& val) {
using V = decay_t<decltype(val)>;
using T = typename V::value_type;
} };
- 第二个好处是,如果想对泛型lambda表达式添加某种限制,如以下代码:
[] (const auto& val1, const auto& val2) { /* ... */ }
如果想对两个参数加以限制,希望具有相同的类型,可以将其转换为一个模板化lambda表达式,并添加requires条款对模板类型设置约束
[] <typename T> (const T& val1, const T& val2) requires integral<T> { /* ... */ }
作为返回类型
function<int(void)> func(int x) { return [x] { return 2 * x; }; }
- 当定义一个lambda时,编译器生成一个与lambda对应的新的类类型,当向一个函数传递lambda时,同时定义了一个新类型和该类型的一个对象
- C++20中支持默认构造,拷贝和赋值无状态的lambda表达式。
auto lamb {[] (int a, int b) { return a+b; }};
decltype(lamb) lamb1; // 默认构造
auto c { lamb }; // 拷贝构造
c = lamb; // 拷贝
函数外覆器
std::function<T>
( #include<functional>
):可以存储,移动,赋值,执行。
- 使用成员函数时,借以调用它们的那个对象必须被当作第一实参:
- 异常:执行一个函数调用却没有标的物可调用,会抛出异常:
std::bad_function_call
class C { public: void memfunc(int x, int y) const; };
std::function<void(const C&, int, int)>mf = &C::memfunc; // 将成员函数赋值给std::function对象
mf(C(), 42, 77); // 调用
- 可以通过类模板参数推导,简化function对象的创建
void func(int a, string_view b) { ... }
function f1 { func }; // 简化f1创建,不需要指定function模板参数
auto f2 { func }; // 更加简化,但编译器推导出来的f1类型是个函数指针,即void(fp*)(int, string_view)而不是std::function
其他辅助函数
标准库functional中的其他辅助函数
std::ref
绑定非const引用,将类型T转换成&Tstd::cref
绑定const引用,将类型T转换成const T&std::invoke()
可用于调用任何带有一组参数的可调用对象
void func(string_view str) { ... }
int main() {
std::invoke(func, "hello"); // 调用普通函数
std::invoke([] (cosnt auto& msg) { ... }, "hello");
}
- 哈希模板
std::hash<T>
:#include<functional>
- 定义了一个函数对象,实现了哈希函数,这个函数对象的实例定义了一个operator()
- 接受一个参数的类型key,返回一个类型为size_t的值,表示参数的哈希值