三、函数指针、回调函数 与 UE 委托

1. 函数指针

函数的二进制代码存放在内存四区(栈区、堆区、数据段、代码段)中的代码段,函数的地址是它在内存中的起始地址。如果把函数的地址作为参数传递给函数,就可以在函数中灵活的调用其它函数。
使用函数指针的三个步骤:

  • 声明函数指针;
  • 指向函数地址;
  • 通过函数指针调用函数。

1.1 声明函数指针

声明普通指针时,必须提供指针的类型。
同样,声明函数指针时,也必须提供函数类型,即 返回值参数列表 (函数名和形参名不是)。
image

相应的函数指针声明如下:
image

1.2 指向函数地址

int no = 001;
string msg = "8933";

// 函数指针声明 返回值  参数列表
int (*Pfunc) (int, string);

// 指向函数地址
Pfunc = CreateSession;

1.3 通过函数指针调用函数

#include<iostream>
using namespace std;

bool CreateResult(int id, string info)
{
    cout << "id : " << id << ", " << info << endl;
    return true;
}

// 返回值 函数名 参数列表 (形参类型 形参名称, ...)
int CreateSession(int no, string str)
{
    cout << "no : " << no << "创建" << str << "完成" << endl;
    int id = no;
    string info = "创建成功";
    // 函数指针声明 返回值  参数列表
    bool (*Pfunc) (int, string);
    // 指向函数地址
    Pfunc = CreateResult;
    // 调用函数
    return Pfunc(id, info);
}

int main()
{
    int no = 001;
    string msg = "8933";
    
    // 函数指针声明 返回值  参数列表
    int (*Pfunc) (int, string);
    // 指向函数地址
    Pfunc = CreateSession;
    // 调用函数
    cout << Pfunc(no, msg) << endl;

    return 0;
}

image

2. 回调函数

函数指针的应用,回调函数。
函数逻辑大体相同,但某处需要特定,
则可以使用函数指针做参数,等具体执行的时候传入特定的逻辑:回调函数。

#include<iostream>
using namespace std;

// 返回值 函数名 参数列表 (形参类型 形参名称, ...)
void CreateSession(int no, string str, void(*CreatePtr)(int no, string str))
{
    cout << "no : " << no << "创建" << str << "准备" << endl;
    // ~ 特定方式创建 ~
    CreatePtr(no, str);
    // ~ 特定方式创建 ~
    cout << "no : " << no << "创建" << str << "完成" << endl;
}

// L 特定创建
void CreateByL(int no, string str)
{
    cout << "L 特定创建";
    cout << "no : " << no << "创建" << str << endl;
}
// M 特定创建
void CreateByM(int no, string str)
{
    cout << "M 特定创建";
    cout << "no : " << no << "创建" << str << endl;
}

int main()
{
    int no = 001;
    string msg = "8933";
    CreateSession(no, msg, CreateByL);
    CreateSession(no, msg, CreateByM);
    return 0;
}

3. UE 委托

委托的基本类型:单播委托多播委托动态委托
委托的使用:声明、绑定、调用

3.1 单播委托

单播委托指只能绑定一个函数指针的委托。
可绑定 无返回值有返回值 的函数。

//无返回值函数委托声明
DECLARE_DELEGATE(DelegateName);			//无参
DECLARE_DELEGATE_OneParam(DelegateName, Param1Type);	//1个参数
DECLARE_DELEGATE_XXXParams(DelegateName, Param1Type, ...);	//多参
//有返回值函数委托声明
DECLARE_DELEGATE_RetVal(RetValType, DelegateName);	//有返回值无参
DECLARE_DELEGATE_RetVal_OneParam(RetValType, DelegateName, Param1Type);	//有返回值,1个参数
DECLARE_DELEGATE_RetVal_XXXParams(RetValType, DelegateName, Param1Type, ...);	//有返回值,多参

单播声明

// 声明单播委托 DECLARE_DELEGATE(委托变量名)
DECLARE_DELEGATE(FTestDelegate);

class XXX_API AMyTestActor : public AActor
{
	GENERATED_BODY()
public:	
	AExecuteDelegateActor();
protected:
	virtual void BeginPlay() override;
public:	
	virtual void Tick(float DeltaTime) override;
public:
	// 当变量使用
	// 单播委托不能声明BlueprintAssignable标识符。动态多播可以声明BlueprintAssignable
	FTestDelegate TestDelegate;
}

单播绑定

函数 描述
BindSP 绑定SharedPtr指向对象的函数,即纯C++类的函数。因为纯C++类一般会使用TSharedPtr用于管理内存
BindThreadSafeSP 绑定线程安全的共享成员函数委托
BindRaw 纯C++类变量,可以用其绑定委托
BindUFunction 如果成员函数有UFUNCTION宏表示,可用此绑定委托
BindUObject 绑定继承UObject类的成员函数委托
BindStatic 绑定静态(全局)函数委托,BindStatic(&MyClass::StaticFunc);
BindLambda 绑定一个Lambda函数
BindWeakLambda 绑定弱引用Lambda函数
UnBind 取消绑定委托
//绑定纯C++类函数到委托
TSharedRef<FMyClass> MyClassObj(new FMyClass());
TestDelegate.BindSP(MyClassObj, &FMyClass::XXXFunc);

//绑定UObject子类函数到委托
TestDelegate.BindUObject(this, &UMyUObjectClass::XXXFunc);		//当前类内绑定
UMyUObjectClass* MyUObjectClassObj;								//其它类内绑定
TestDelegate.BindUObject(MyUObjectClassObj, &UMyUObjectClass::XXXFunc);

单播调用

函数 说明
IsBound 检查是否绑定一个委托
Execute 不检查绑定而执行委托
ExecuteIfBound 当绑定一个委托时,执行调用

3.2 多播委托

多播委托可以绑定多个函数,但不能有返回值

多播声明

//多播委托
DECLARE_MULTICAST_DELEGATE(DelegateName);
DECLARE_MULTICAST_DELEGATE_ONEPARAM(DelegateName, Param1Type);
DECLARE_MULTICAST_DELEGATE_XXXPARAMS(DelegateName, Param1Type,...);

//动态多播委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE(DelegateName);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ONEPARAM(DelegateName, Param1Type, Param1Name);

多播绑定

image

多播调用

Broadcast调用后,会执行所有绑定的委托,但是委托的执行顺序尚未定义

函数 说明
Broadcast() 执行所有绑定的委托

3.3 动态委托

动态委托有多播,也有单播
可以序列化,其函数可以按命名查找,但执行速度比常规委托

动态声明

//动态单播委托
DECLARE_DYNAMIC_DELEGATE(DelegateName);
DECLARE_DYNAMIC_DELEGATE_ONEPARAM(DelegateName, Param1Type);
DECLARE_DYNAMIC_DELEGATE_XXXPARAMS(DelegateName, Param1Type,...);

//动态多播委托,可以暴露给蓝图
DECLARE_DYNAMIC_MULTICAST_DELEGATE(DelegateName);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ONEPARAM(DelegateName, Param1Type, Param1Name);

暴露给蓝图

动态单播委托变量(属性)可以作为函数的参数,暴露给蓝图,在蓝图中动态绑定一个委托。

DECLARE_DYNAMIC_DELEGATE(FMyDynamicDelegate);

public:
	//不能声明为蓝图实现函数,cpp文件必须有实现
	UFUNCTION(BlueprintCallable)
	void DynDelTestFunc(FMyDynamicDelegate MyDynamicDelegate)
	{	//这里为方便说明,直接在声明处定义
		//调用出入的动态委托
		MyDynamicDelegate.ExecuteIfBound();
	}
	
public:
	FMyDynamicDelegate MyDynamicDelegate;

接着蓝图中调绑定委托:
image

动态多播委托也可以暴露给蓝图使用。
如果暴露给蓝图,需要给委托变量的 UPROPERTY 宏添加 BlueprintAssignable 标识符。

DECLARE_DYNAMIC_MULTICAST_DELEGATE_ONEPARAM(FDelegateTest, class AActor*, MyActor);

UPROPERTY(BlueprintAssignable)
FDelegateTest TestDelegate;

动态绑定

image

动态调用

image

3.3 应用场景

  • 单播委托: 当只需在C++ 中绑定和调用,且只有一个函数需要绑定委托时,可以使用单播委托。
  • 多播委托绑定多个函数,但不能有返回值,其它功能与单播委托一样。
  • 动态委托动态绑定,可以序列化,也就是说可在蓝图中使用
  • 动态多播委托: 蓝图中的事件调度器(EventDispatcher),可以用其在C++和蓝图中绑定和调用委托。

参考资料:【UE4笔记】各种Delegate委托的区别和应用

所有记录:游戏开发:C++学习

posted @ 2024-04-18 19:45  bok_tech  阅读(588)  评论(0)    收藏  举报