UE4 C++与标准C++在某些方面有些区别。本文针对函数参数这块,做一个简要的分析,如文章有所疏漏,可以在下方留言提醒,不吝赐教。
在看此文之前,假设你已经对ue4 c++有了基本的了解,以及对c++有一定的编程经验。
如果你想直接看文章的结构及总结,可以直接翻到文章末尾的总结。
为方便起见,首先在c++中创建一个Actor类。
并建立一个public变量MyNumber
UPROPERTY
并在这个Actor类的上方,创建一个结构体,用于测试传递对象作为参数时的情况
USTRUCT
然后在蓝图中创建一个Actor类,并继承这个Actor类,这样就可以开始我们的测试了。
一、值传递函数参数在蓝图中的表现形式
(1)int32
在c++中,添加这个函数。
UFUNCTION
在蓝图中,可以看到为这样的形式,也就是正常的值传递参数
简单测试一下
由于MyNumber默认值为0,传入Test1_1后为值传递,因此MyNumber的值不会由于传入了这个函数而发生变化,所以显然print的结果还是0.
(2)const int32
在c++中,添加这个函数。
UFUNCTION
在蓝图中,可以看到为这样的形式,也就是正常的值传递参数
由于const,那么MyNumber的值肯定不会发生变化了,并且在函数中也无法修改其值.
Log信息为
(3)关于默认参数
无论是int32还是const int32,都可以在c++中设定默认参数,并在蓝图中可以手动填充实参。
UFUNCTION
在蓝图中打开,默认值就发生了变化
如果不在c++中设定默认参数,则其默认值均为0。
(4)FMyTest和const FMyTest
若为参数并非基本类型,而是其他类型,如一个类,如上方定义的FMyTest
UFUNCTION
在蓝图中显示为
由于蓝图无法给这个自定义类型自动生成默认值,所以在蓝图中不会像int32一样能直接填写默认值
测试一下
可以看到,调用了FMyTest这个结构体的默认构造函数。
FMyTest
(5)FMyTest的函数默认参数
如果我们想像int32=100这样写一个函数默认参数呢?自然我们就想这么去写下。
UFUNCTION
然后发现
也就是说,我们无法对自定义类用这种默认函数参数的形式。只有去掉了UFUNCTION之后才可以对自定义类用函数默认参数。
void
如果能在c++中写自定义类型的函数默认参数,并用于蓝图,麻烦告知我一声,我谷歌了一圈没搜到。
二、引用传递函数参数在蓝图中的表现形式
(1)const int32&
其为const引用传递,即形参作为实参的别名传入函数中,相当于实参传入函数中,但不可更改实参的值。(虽然是c++基本常识,在这还是提一下)。无论在蓝图还是c++中,都符合这个规则。
a)不带有默认参数
在c++中,添加这个函数。
UFUNCTION
在蓝图中,可以看到为这样的形式
测试一下,不填充这个参数的值,就会发生错误警告
传入一个参数后
b)带有默认参数
UFUNCTION
若我们写成这种方式,在c++中就可以不传递函数参数,直接通过
test1_4
的方式就能调用这个函数。但在蓝图中,当我们不连接任何参数时,一样会报错。
也就是说,无论在c++中const int32&是否有默认参数,在蓝图中,都必须以引用的方式,传递一个参数到这个函数中。
(2)int32&
这个可以说是最迷惑人的函数传递方式,当你兴致勃勃地这么写函数参数时
UFUNCTION
你期望的结果是
但实际结果是,这个参数直接作为函数的返回值了,而并非你想要的上面的那种引用传递。在下面一小节会介绍如何真正引用传递函数参数的方式。
无论如何,我们先来看输出结果
可见,因为我们没有输入默认函数参数,所以ue4给他设定了一个初始值0,这个道理同上面int32作为参数的情况。
而且由于c++的语法(这时候ue4又讲究c++的语法了),你无法给一个非常量引用类型的变量赋予一个右值,所以你也无法赋予其默认参数(在VS中就会自动报错)
这种方式的最大作用,在于可以让我们返回多个参数。同样在标准c++中,也经常这么使用引用能让我们达到返回多个函数参数的目的
UFUNCTION
(3)神技之 UPARAM(ref) int32&
既然无法用int32&来达到引用传递函数参数的目的,那么如何才能做到这点。
此时UE4就特定为我们准备了一个宏 UPARAM(ref)
UFUNCTION
测试一下
可以看到,此时,MyNumber的值由原来的0变成了1,也就是说,真正实现了c++的引用传递。
(4)int32&&
在c++11中,可以通过右值引用,达到更省时省力的效果,但很可惜,只能在c++内部使用右值引用,无法将右值引用类型的函数参数暴露给蓝图。
UFUNCTION
三、指针传递函数参数在蓝图中的表现形式
由于引用的便捷性,我相信应该不会有要写int32 *这种需求的参数类型的函数吧。。。(有的话,麻烦告知我一声,而且编译时也不会通过)
个人认为一般就是传递多态的参数的时候需要用到指针,由于本人对于ue4智能指针的使用还没用过几次,而且暴露给蓝图的指针类型,基本都是原生指针,所以在这里不对智能指针的情况进行分析。
由于UE4所有需要带有反射类型的类,都必须继承自UObject,所以此处以UObject为例。并且UObject作为参数传递给蓝图使用时,只能用指针的形式。
并且注意,任何指针,都要注意其是否可能为空指针的情况。
(1)UObject*
同c++的指针,可以更改传入的内容。地址传递。
UFUNCTION
在蓝图中为
测试一下,由于Actor是继承于UObject的,所以可以将子类作为参数,传入父类的指针或引用中,实现动态多态。所以下面这个函数可以正常执行。
(2)const UObject*
这样传进来的UObject的值就不能发生更改。即*UObject的值不能发生改变。
UFUNCTION
同样,在蓝图中的表现形式为
(3)const UObject*const
由于const UObject只是底层const,即不能修改UObject的值,可以修改它的地址。针对上面的函数,如果你加一句
MyObject
这样可行的,不过肯定会在运行时报错。
而在c++,有这样一种const写法。即const UObject*const,底层+顶层 const
这样,你就无法用MyObject++了。
这样就能限定MyObject不被修改了。
UFUNCTION
在蓝图中为
在c++中,当你既想传入原本的这个参数,又不想改变其地址的时候,下式成立
const
但因为UE4的UObject只能以指针的形式传给蓝图。所以,这样写不成立。
UFUNCTION
(4)UObject*&
在学习数据结构时,特别是写树的时候,经常用到指针引用,来进行节点的连接和取消连接。
在UE4中,同样可以使用指针引用,来让传进来的指针的值和地址都会由于函数中的某些步骤而导致变化时,原来的实参的值和地址也会变化。
但由于UE4的引用的特殊性,按下面的方式写的话
UFUNCTION
没错,就是作为返回值了
此时,同样也可以用之前的UPARAM(ref)来解决这个问题
UFUNCTION
但此时传入的UObject必须为可写的对象,不可为只读对象
比如传入self时,会报错
所以这种类型参数的使用,只能用于可写的UObject对象。
(5)const UObject*&
无法用于蓝图,只能c++中这么使用
UFUNCTION
四、函数参数类型为数组(TArray)时
(1)TArray<int32>
(2)const TArray<int32>
(3)TArray<int32>&
(4)const TArray<int32>&
UFUNCTION
并且,除了TArray<int32>&对应的参数以外,其他所有参数,都必须连接一个参数,才能在蓝图中使用。其规则完全跟单纯int32的时候相同。
不连接的话
连接之后
(5)TArray<UObject*>
(6)const TArray<UObject*>
(7)TArray<UObject*>&
(8)const TArray<UObject*>&
当容器中的对象类型仅为UObject*时,其表现方式跟int32一样
UFUNCTION
(9)容器中对象类型为const UObject*,const UObject*const和UObject*&时
UFUNCTION
UFUNCTION
UFUNCTION
前面两个原因好像是不能将指针转换成常量指针这种,后面一个,已经看不出错在哪了。所以结论就是,UE4的TArray里面的对象,对于原生指针,只能用UObejct*这种方式,加const修饰的和加引用的指针,都无法作为TArray里面的对象。
五、函数的返回值在蓝图中的表现形式
(1)int32
UFUNCTION
与用int32&的形参不同的是,此时在蓝图中,会显示一个return value
(2)const int32
UFUNCTION
跟int32在蓝图中的表现形式一样。
(3)int32&
在c++中,返回值为引用的函数,可以作为左值使用,但是在蓝图中,是没有左右值的概念的。
并且int32&作为返回值时,其返回的变量,必须不能局部变量。因为返回局部变量的引用在c++中就是大忌。
因此,当你这么写时,肯定会报错
UFUNCTION
因此,我这边就用下面这个代码进行测试
UFUNCTION
(4)const int32&
UFUNCTION
大坑之返回值为const int32,int32&,const int32&。
在c++中,如果你函数的返回值为int32,const int32,int32&,const int32&。那么完全是不同的结果,但是在蓝图中,你会发现,无论你把返回值设为上面四种形式中的哪一种,它在蓝图中的结果,均为int32对应的结果。
首先我们写一个测试函数
UFUNCTION
然后见证奇迹
a.const int32
在c++中,如果你返回值是const,那么就代表返回值是一个右值,是无法作为一个左值使用的。即,你无法写出这样的代码。
但是在ue4中,我们的test4_2是这样的
UFUNCTION
按c++中的概念,其返回值是一个右值,是无法用于刚定义的ChangeInt()函数的。
但是在蓝图中,可以看到它竟然能连上去,并且能成功运行
当然,输出结果为0,MyNumber的值没有发生改变。
b.int32&
再来看看我们的test4_3,其返回值是number的引用,即在c++中,要是对这个返回值进行修改,number必然也会发生修改。
UFUNCTION
但是在蓝图中
结果还是1,并没有加上100
c.const int32&
看看最后的test4_4,这个返回值就根本不能被修改。
UFUNCTION
但是在蓝图中,可以看到其返回值成功传入了ChangeInt这个函数中,并且根据结果为1来看,这个返回值的类型也变成了int32.
d.结论
所以对于蓝图而言,返回值为常量或引用时,还是以值的方式返回。
这个结论同样适用于之前的参数为int32&的时候。如
UFUNCTION
在c++中是引用传递,但是,实际上你在蓝图中,只能看到这个作为返回节点,并不能作为函数参数的输入节点。因此,还是以int32的方式返回。
(5)int32&&
无法通过蓝图返回右值引用
UFUNCTION
(6)UObject*
(偷个懒,我也懒得加指针判空了,用的时候注意加就好!)
就以当前创建的Actor对象为例
UFUNCTION
测试一下
可见,是可以返回指针的。并可以返回这个对象本身。还是比较有用。
其作用效果,跟在参数列表中写UObject*类型的参数是一致的
UFUNCTION
但在蓝图中是会变成这样,造成视觉上的误解。但无论是test4_6的返回值还是MyActor传入test4_6_1后的值,都是this。
(7)const UObject*
无法作为返回值被蓝图调用,但在c++中可以正常使用
UFUNCTION
(8)UObject*&
可以作为返回值被蓝图调用
UFUNCTION
这种方式的返回方式,跟在函数参数列表中的UObject*&方式在蓝图中的表现方式一致,并且意义也相同。
通过下面这种方式也能实现一样的效果,但注意要传指针引用才能在蓝图中表现出一样的效果。
UFUNCTION
如果想要将MyActor变为参数,加个UPARAM(ref)即ok。(见test2_5)
注意:返回指针和返回指针引用,在c++完全是两回事,但是一旦用于蓝图,可能返回的均为指针(在蓝图中表现为引用的形式)。
因找不到很好的测试案例,望有心人可以提供一下测试的思路。但鉴于上面对返回类型为int32&的分析结果(c++中返回类型为int32&,但在蓝图中被UE4改成了int32)。
所以,此处假设返回值为指针引用时,其仍然跟返回指针的效果一致。
(9)TArray<int32>
此处用到了Initializer_list的方式进行初始化容器。
UFUNCTION
在蓝图中
(10)const TArray<int32>
首先我们先在c++中写一个改变容器内的数值的函数,让容器内每一个数的数值加10
UFUNCTION
然后写出这个函数
UFUNCTION
在蓝图中,表现为
想必已经猜到结果了,没错,跟之前const int32做返回值一致,其返回值也被从const TArray<int32>改成了TArray<int32>。所以当然可以连上ChangeArray
(11)TArray<int32>&
UFUNCTION
并在蓝图中,创建一个数组
测试一下
当然,结果一定为0 1 2,而不是10 11 12,因为返回值被从TArray<int32>&改成了TArray<int32>。
(12)const TArray<int32>&
UFUNCTION
测试一下
同样,返回值由const TArray<int32>&被改成了TArray<int32>
六、关于函数重载
在c++中,支持函数的重载,但暴露给蓝图的函数不支持函数重载。
UFUNCTION
显示两个函数发生冲突。
如果想支持重载,只能在c++内部用,而不暴露给蓝图。将UFUNCTION反射去掉就可以支持c++内部的函数重载
UFUNCTION
七、蓝图中能实现函数参数传递形式
1.能设定默认值的基本数据类型
像bool,FString,int32,int64等基本数据类型,是能在蓝图内设定其默认值的。
2.无法设定默认值的数据类型
大多数类型,都在蓝图内无法设定默认值,只能在c++中默认构造函数中设定默认值,或者在只用于c++的函数中用默认参数,比如FDateTime,FTransform等等。
3.值传递形式
有个很好的方式区分值传递和引用传递,当为值传递时,参数符号为这样
4.引用传递形式
当改成引用传递后,变成了菱形
5.在蓝图中没有const传递的形式,只能在c++中设定这样的函数
6.返回值均为指针(但是表现为引用类型)或者正常的类型,不带有引用和const属性
八、总结
(一)函数参数
1.值传递(第一部分)
- int32
- const int32
此部分还有对默认函数参数的分析。
2.引用传递(第二部分)
- const int32&;
- int32&;
- 神技之 UPARAM(ref) int32& ;
- int32&&(不能用于蓝图)
3.指针传递(第三部分)
- UObject*;
- const UObject*;
- const UObject*const;
- UObject*&;
- UPARAM(Ref)UObject*&;
- const UObject*&(不能用于蓝图)
4.容器方式传递(第四部分)
(1)TArray<int32>
(2)const TArray<int32>
(3)TArray<int32>&
(4)const TArray<int32>&
(5)TArray<UObject*>
(6)const TArray<UObject*>
(7)TArray<UObject*>&
(8)const TArray<UObject*>&
(9)容器中对象类型为const UObject*,const UObject*const和UObject*&时
无法放在TArray容器中。
(二)返回值(第五部分)
- int32;
- const int32;
- int32&;
- const int32&;(1-4的返回类型均被蓝图改为int32,详见上述分析--大坑之返回值为const int32,int32&,const int32&。)
- int32&&;(无法用于蓝图)
- UObject*;
- const UObject*;(无法用于蓝图)
- UObject*&;(6,8的返回类型应该都是UObject*,详见上述分析,但有待核实)
- TArray<int32>;
- const TArray<int32>;
- TArray<int32>&;
- const TArray<int32>&(9-12的返回类型均为TArray<int32>,详见上述分析)
(一)和(二)结论
在c++的返回值和函数参数中。如果在蓝图中,对应的参数为蓝图中的返回值,其中的const和&属性都被消除,但是若对应的参数为蓝图中的函数参数,其const和&属性都被保留。
(三)其他(六-七部分)
1.关于函数重载
蓝图无法实现,只能c++实现
2.关于蓝图中能实现的函数参数传递形式,详见第七部分
本只打算自己总结下,没想到扩展了这么多,也看到了一些坑,如有遗漏,欢迎补充,要是你觉得有用,可以赏个赞。
完