|
前言:
最近才想起来,我之前在SC2 1.5 alpha的时候在老外那边写了1.5的“指针”教程,结果竟然一直忘记在GA写了。所以这里补一下。
当时发在老外那的原帖。
http://www.sc2mapster.com/forums ... ade-alpha/?post=123
总之,根据暴雪的patch note,1.5.0的Galaxy终于加入了“在函数之间传递函数/结构/数组”的能力(1.5最好的消息之一),简单来说就是两年前的SC2 beta中被砍掉的指针以另一种更安全的形式回归了(而且1.5 beta的正式名字是SC2 2012 Beta,真是有一种轮回的感觉,不过它的回归对所有脚本用户来说都是一个非常好的消息)。但是我们都知道,就和Jass一样。Galaxy语言是不被官方支持的,所以他们压根就没说要怎么样写脚本才能实现这一功能,更没讲语法。
当时我在alpha出来以后花了一整天时间总算是鼓捣出了相关语法,当时发在老外那了,也在群里发过,就是忘记发在论坛了。所以今天补充一下。(说起来有几个老外明明比我早半个月拿到1.5 alpha,但是它们就是一直没找到这个东西的语法hmmm,然后还发邮件来找我帮忙找)
按说没有任何提示的时候让你去猜语法几乎是不可能做到的,何况你看了下面的代码以后就会发现它的语法有多奇葩。不过其实好在我在mpq里翻东西的时候发现了一段有趣的错误信息:
e_invalidDataRef=Attempted to use an obsolete/uninitialized struct/array ref
e_invalidFuncRef=Attempted to use an uninitialized function ref
这显然是两条错误信息,当函数和数组引用发生语法错误的时候显示的。里头出现了 funcref arrayref structre 这三个关键字,然后我就把这三个关键字用各种方式组合,大写小写,加各种括号什么的,经过了上万次的手动测试和游戏和编辑器的崩溃(1.5 beta的稳定性还有些不足啊)。最终还是把它们鼓捣出来了。其实在编辑器里这三个关键字竟然不给语法高亮,实在太坏了,我在测试的时候其实错过了好几次。
总之以上其实纯粹是废话,只是简单说说我发现1.5的“指针”语法的过程而已。下面才是重点。
函数引用:funcref
funcref是galaxy 1.5中加入的一种类似泛型的神奇类型,它允许我们直接传递函数引用。语法为:
你可以看到,这里的语法非常的奇特,在1.5以前galaxy根本就没出现过尖括号<>这个东西。所以我能发现这语法也算是撞上了运气。要注意的是,funcref(包括下面的arrayref和structref)目前在编辑器里都没有高亮支持,所以都只会以普通的黑色字表示,大家注意不要输错关键字就是了。
下面就以实际的例子来说明函数引用的用法:
[codes=c]
fixed aFunction(int a, fixed b)
{
//随便做些什么
return a+b;
}
fixed anotherFunction(int c, fixed d)
{
//随便做些什么
return c-d;
}
funcref<aFunction> aFuncRef=aFunction;
void doSomething()
{
aFuncRef(1,2); //返回3
aFuncRef=anotherFunction;
aFuncRef(5,6); //返回-1
}
[/codes]
(注意,如果你想把上面这段代码直接复制到编辑器里,记得去掉注释部分后再复制,下同。因为galaxy是不支持直接输入汉字的。那些汉字注释是我加在这里方便理解的。)
由此可见,我们可以通过函数引用来指向一个函数,并把该引用当成变量来使用。aFuncRef在运行过程中可以随时更改它所指向的函数。但要注意的是,函数引用所指向的函数,其原型必须符合函数引用被定义时所指定的函数原型。。就上面这段代码的例子而言,aFuncRef指向的函数原型就必须和"aFunction"这个函数一致。
补充知识:什么是“函数原型一致”?
没学过编程的同学可能不理解原型这个词。简单来说“函数原型一致”就是两个函数的返回类型一致、参数数量一致、所有参数的类型一致(顺序也必须一样,参数名可以不同)
因此如果上面的代码中“anotherFunction”这个函数的原型有所变化,那么aFuncRef=anotherFunction;这条语句就会出现语法错误而拒绝执行。
就算单纯地修改它的返回类型或是只改掉一个参数的类型,也会导致函数原型不一致
[codes=c]
fixed anotherFunction(int c, int d) //把第二个参数的类型改为int
{
//随便做些什么
return c-d;
}
void doSomething()
{
aFuncRef=anotherFunction; //语法错误
}
[/codes]
细心的同学可能已经发现,上面的代码里面出现了int和fixed之间的计算以及直接把int当成fixed来返回的写法。这是因为Galaxy重新放宽了一部分类型转换约束,现在允许int隐式地转换为fixed(反过来还是不行),而使用typedef定义的子类型和父类型之间也可以当成同一个类型的变量来在函数之间进行传递。
函数引用不仅可以用作全局和局部变量,也可以用在函数的参数里。
[codes=c]
void doSomethingX(funcref<aFunction> fr)
{
fr=anotherFunction; //语法错误
}
[/codes]
虽然这看上去是理所当然的事情(既然它是一种变量类型,那么当然可以放在函数参数里),不过下面要说的数组引用和结构引用就稍微有点不一样了。
结构引用:structref
structref是和funcref非常相似的泛型,它允许我们建立对结构的引用。语法和funcref也几乎完全一样。
直接来看一个例子吧:
[codes=c]
struct aStruct {
int a;
funcref<aFunction> anotherFuncRef;
};
aStruct a;
void doSomeOtherThing(structref<aStruct> stru)
{
stru.anotherFuncRef=anotherFunction;
stru.anotherFuncRef(6,5);
}
void main()
{
doSomeOtherThing(a);
}
[/codes]
上面这段代码同时使用了函数引用和结构引用。由于函数引用的存在,现在我们可以把函数塞进结构里面了(开心么?面向对象爱好者们?),然后结构本身还可以作为函数的参数被传递。
结构引用所指向的结构,其结构类型必须为结构引用被定义时所指定的。结构引用只看结构类型是否符合,而不管结构的构成,因此如果两个结构类型所拥有的属性完全一致,在这里还是不能通用的。
另外要注意的是:结构引用的语法和函数引用还有一点不同之处,结构引用只能用作局部变量和和函数参数,不可用作全局变量。
因此下面这样是不行的
[codes=c]
aStruct a;
structref<aStruct> stru=a; //结构引用不能存在于函数体之外,简单而言就是不能作为全局变量。
[/codes]
但可以当作局部变量或者参数来使用。
[codes=c]
void StructRefTest(structref<aStruct> stru1)
{
structref<aStruct> stru2=a;
}
[/codes]
也可以把它们塞进全局数组里![codes=c]
structref<aStruct>[1] G_struRef;
[/codes]
这样做就和全局变量没区别了。这显然是Galaxy编译器的一个bug,不过我宁可它不修正。
数组引用:arrayref
最后是数组引用。数组引用花了我最长的时间,其实当初我发上老外论坛的时候都还没鼓捣出来数组引用的语法。为啥呢,数组这个东西的类型由两部分组成,一个是数组元素的类型,一个是数组的尺寸。但是不论以什么组合写到arrayref<>里面都没用。一直报错。
结果。发现可以用typedef把一个数组类型包装为一个单独的变量类型。于是就可以正常塞到arrayref里面去了。比如我们可以把int[2]弄成int2;
[codes=c]
typedef int[2] int2;
int[2] abc;
void TestArrayRef(arrayref<int2> arr) {
TriggerDebugOutput(1, FixedToText(arr[0], c_fixedPrecisionAny), true);
}
void doSomethingArray()
{
abc[0]=123;
abc[1]=321;
TestArrayRef(abc);
}
[/codes]
数组引用所指向的数组,其数组元素的类型和尺寸必须和数组引用被定义时所指定的类型和尺寸完全一致。。
比如如果我把上面的abc改成"int[3] abc;",那么它就无法传递给TestArrayRef()函数了:
[codes=c],
typedef int[2] int2;
int[3] abc;
void TestArrayRef(arrayref<int2> arr) {
TriggerDebugOutput(1, FixedToText(arr[0], c_fixedPrecisionAny), true);
}
void doSomethingArray()
{
abc[0]=123;
abc[1]=321;
TestArrayRef(abc); //语法错误,数组尺寸不符
}
[/codes]
在定义abc的时候,不只可以用"int[2] abc",也可以用"int2 abc",正如上文所言,在新的galaxy语法中,使用typedef所定义的变量类型,父类型和子类型之间是可以自动隐式转换的。因此书写时就会十分方便。不需要时刻去核对数组的尺寸是否一样了。
另外,和结构引用一样,数组引用只能用作局部变量和和函数参数,不可用作全局变量。
但和结构引用一样,你同样也可以把数组引用塞进一个全局数组里![codes=c]
arrayref<int2>[1] G_arrayRef;
[/codes]
综合
上面提到了,通过使用1.5的三个引用泛型,我们可以把函数塞进结构里,把结构塞进函数参数或局部变量里,把数组塞进函数参数或局部变量里。而结构本来就可以被塞进数组里,数组也可以被塞进结构里。
那么函数是否也可以被塞进数组里面呢?
答案是可以。
[codes=c]
fixed aFunction(int a, fixed b)
{
//随便做些什么
return a+b;
}
funcref<aFunction>[2] aFuncRefArray;
void main(){
aFuncRefArray[0]=aFunction;
aFuncRefArray[0](3,4);
{
[/codes]
也就是说这三种对象完全可以互相嵌套。
不过要注意的是,不论是函数引用、结构引用还是数组引用,它们都无法被拿来当作函数的返回类型使用,切记。
以上,关于引用泛型的教程就结束了,有任何问题可以回帖提问。 |
|