深入理解C++ 中的可调⽤对象

C++中的可调⽤对象总结

可调用对象用处⼴泛:

  • ⽐如在使⽤⼀些基于范围的模板函数时,如 sort(It first, It last, Compare cmp)all_of()find_if() 等),需要传⼊⼀个可调⽤对象客制化处理。
  • 在处理⼀些回调函数、触发函数时,也会使⽤可调⽤对象。

满足以下条件的为可调用对象:

  • 是一个函数或类成员函数
  • 是一个函数指针
  • 是一个重载operator()的类对象
  • lambda表达式
  • 是一个可转型为函数指针的类对象
  • 是一个类成员函数指针
  • bind表达式、std::function()
  • 协程

普通函数

cpp 复制代码
int add(int a, int b) {
	return a + b;
}
int main() {
	int num1, num2;
	std::cout << "Enter two numbers: ";
	std::cin >> num1 >> num2;
	int sum = add(num1, num2);
	std::cout << "The sum of " << num1 << " and " << num2 << " is: " << sum << s << "\n";
	return 0;
}

类成员函数

cpp 复制代码
#include <iostream>
using namespace std;
class Box{
public:
	double length; // ⻓度
	double breadth; // 宽度
	double height; // ⾼度
	// 成员函数声明
	double getVolume(void);
	void setLength( double len );
	void setBreadth( double bre );
	void setHeight( double hei );
};
// 成员函数定义
double Box::getVolume(void)
{
	return length * breadth * height;
}
void Box::setLength( double len )
{
	length = len;
}
void Box::setBreadth( double bre )
{
	breadth = bre;
}
void Box::setHeight( double hei )
{
	height = hei;
}
int main( )
{
	Box box; // 声明 box,类型为 Box
	double volume = 0.0; // ⽤于存储体积
	// 详述
	box.setLength(6.0);
	box.setBreadth(7.0);
	box.setHeight(5.0);
	// 体积
	volume = box.getVolume();
	cout << "box 的体积:" << volume <<endl;
	return 0;
}

类静态成员函数

类中的静态成员函数作⽤在整个类的内部,类静态成员函数属于类而非对象 。静态成员函数只能访问对应类内部的静态数据成员(因为静态成员函数没有this指针 )。类的static函数在类内声明、类外定义时,类内使用static修饰,类外则不能加static关键字,否则会出现编译错误。

cpp 复制代码
class Box{
private:
	int _non_static;
	static int _static;
public:
	int a(){
		return _non_static;
	}
	static int b(){
		//_non_static=0; 错误
		//静态成员函数不能访问⾮静态成员变量
		return _static;
	}
	static int f(){
		//a(); (不对,静态成员函数不能访问⾮静态成员函数)
		return b();
	}
};

int Box::_static= 0;// static静态成员变量可以在类的外部修改

int main(){
	Box box;
	Box* pointer=&box;
	box.a();
	pointer->a();
	Box::b(); // 类名::静态成员函数名
	return 0;
}

与类成员函数的区别

  • 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
  • 普通成员函数有 this 指针,可以访问类中的任意成员;⽽静态成员函数没有 this 指针。

仿函数

仿函数就是重载了()运算符的类对象,函数功能通过重载()实现。(⾏为类似函数,故称仿函数)。实际上就是创建⼀个类,该类重载了()运算符,使得类的实例可以像函数⼀样被调⽤。这允许你在函数对象内部保存状态,并在调⽤时执⾏操作。

简单示例

cpp 复制代码
class Foo
{
	void operator()()
	{
		cout << __FUNCTION__ << endl;
	}
};
int main()
{
	Foo a;
	//定义对象调⽤
	a.operator()();
	//直接通过对象调⽤
	a();
	//通过临时对象调⽤
	Foo()();
}

高级用法-状态保持

仿函数可以具有成员变量,因此在多次调用之间可以保持状态。这在算法需要记录或更新某些值的情况下非常有用。

考虑一个例子:对一个vector统计长度小于5的string的个数

优缺点

优点

  • 状态保持:仿函数可以具有成员变量,因此在多次调用之间可以保持状态。
  • 灵活的接口设计:仿函数可以根据需要定制接口,以适应特定的算法或场景。例如,可以根据算法需要添加额外的成员函数或数据成员。
  • 更好的封装:仿函数可以将数据和操作封装在一个单独的对象中,这有助于实现更清晰、更模块化的代码。

缺点

  • 需要单独实现一个类

函数指针

与数据项相似,函数也有地址。函数的地址就是存储它的机器语⾔代码内存的开始地址。通常情况下,这些地址对⽤⼾⽽⾔并不重要,但对于程序⽽⾔,却很有⽤。⽐⽅说,可以编写将另⼀个函数的地址作为参数的函数,这样第⼀个函数就能够找到第⼆个函数,并运⾏它。与直接调⽤另⼀个函数相⽐,这种⽅法虽然很笨拙,但是它允许在不同的时间传递不同函数的地址,这也就意味着可以在不同的时间使⽤不同的函数。

获取函数地址

函数名,不加()

声明并调用函数指针

使用using而非old style声明函数指针!
注意两种方式绑定函数的区别!

  1. 使用 using 声明函数指针
cpp 复制代码
#include<iostream>

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

int main() {
    // 使用 using 声明函数指针类型
    using AddFunctionPtr = int (*)(int, int);
    // 创建函数指针变量并指向 add 函数
    AddFunctionPtr my_add_function = &add;
    // 使用函数指针调用 add 函数
    int result = my_add_function(3, 4);
    return 0;
}
  1. old style
cpp 复制代码
#include<iostream>

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

int main() {
    // 使用 old style 声明函数指针
	int (*my_add_function )(int, int);
    // 指向 add 函数
    my_add_function = &add;
    // 使用函数指针调用 add 函数
    int result = my_add_function(3, 4);
    // int result = (*my_add_function)(3, 4);	// ok
    return 0;
}

lambda表达式

Lambda表达式是Modern C++的⼀个语法糖 。 Lambda表达式用于构造闭包:能够捕获作用域中的变量的无名函数对象。

语法定义

  1. 没有显式模板形参的 lambda 表达式(可以不泛型)
cpp 复制代码
  [捕获列表]     [前属性]       [形参列表] [说明符]   [异常]         [后属性]        [返回类型]         [约束]       [函数体]
[capture list] front-attr(opt) (params) specs(opt) exception(opt) back-attr(opt) railing-type(opt) requires(opt) {body}
  1. 有显式模板形参的 lambda 表达式(必然泛型)(C++20起)

捕获

捕获 是一个含有>=0个捕获符 的逗号分隔列表,可以默认捕获符 开始。捕获列表定义了可以从 lambda 函数体之内访问的外部变量。默认捕获符 只有&(按引用) 和 =(按复制),它们都将隐式捕获被使用的自动存储期变量(函数能看到的任一变量)。

注意:以默认捕获符隐式捕获的当前对象(*this),将始终按引用捕获,即使默认捕获符是 =

单个捕获符

  1. 简单的按复制捕获
  2. 作为包展开的简单的按复制捕获
  3. 带初始化器的按复制捕获
  4. 简单的按引用捕获
  5. 作为包展开的简单的按引用捕获
  6. 带初始化器的按引用捕获
  7. 当前对象的简单的按引用捕获
  8. 当前对象的简单的按复制捕获
  9. 初始化器为包展开的按复制捕获
  10. 初始化器为包展开的按引用捕获
  • 不可重复捕获
    • 当默认捕获符是 & 时,后继的简单捕获符不能以 & 开始

      cpp 复制代码
      struct S2 { void f(int i); };
      void S2::f(int i)
      {
          [&]{};          // OK:默认按引用捕获
          [&, i]{};       // OK:按引用捕获,但 i 按值捕获
          [&, &i] {};     // 错误:按引用捕获为默认时的按引用捕获
          [&, this] {};   // OK:等价于 [&]
          [&, this, i]{}; // OK:等价于 [&, i]
      }
    • 当默认捕获符是 = 时,后继的简单捕获符必须以 &、*this (C++17 起)、this (C++20 起) 之一开始。

      cpp 复制代码
      struct S2 { void f(int i); };
      void S2::f(int i)
      {
          [=]{};        // OK:默认按复制捕获
          [=, &i]{};    // OK:按复制捕获,但 i 按引用捕获
          [=, *this]{}; // C++17 前:错误:无效语法
                        // C++17 起:OK:按复制捕获外围的 S2
          [=, this] {}; // C++20 前:错误:= 为默认时的 this
                        // C++20 起:OK:同 [=]
      }
    • 任何捕获符只可以出现一次,并且名字不能与任何形参名相同:

      cpp 复制代码
      struct S2 { void f(int i); };
      void S2::f(int i)
      {
          [i, i] {};        // 错误:i 重复
          [this, *this] {}; // 错误:"this" 重复(C++17)
       
          [i] (int i) {};   // 错误:形参和捕获的名字相同
      }

std::function()

协程

相关推荐
Theodore_10222 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
‘’林花谢了春红‘’3 小时前
C++ list (链表)容器
c++·链表·list
----云烟----4 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024064 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic4 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it4 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康4 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神5 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
机器视觉知识推荐、就业指导5 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
宅小海5 小时前
scala String
大数据·开发语言·scala