找回密码
 点一下
查看: 8404|回复: 39

1.5.0 “指针归来”函数/结构/数组引用的语法教程

[复制链接]
发表于 2012-6-23 21:43:33 | 显示全部楼层 |阅读模式
    前言:

    最近才想起来,我之前在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中加入的一种类似泛型的神奇类型,它允许我们直接传递函数引用。语法为:
funcref<函数名> 引用名



你可以看到,这里的语法非常的奇特,在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也几乎完全一样。

structref<结构类型名> 引用名

直接来看一个例子吧:

[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]

也就是说这三种对象完全可以互相嵌套。

不过要注意的是,不论是函数引用、结构引用还是数组引用,它们都无法被拿来当作函数的返回类型使用,切记。


以上,关于引用泛型的教程就结束了,有任何问题可以回帖提问。
 楼主| 发表于 2012-6-23 21:53:20 | 显示全部楼层
其实我仍不是很确定使用typedef来把数组类型包装成一个变量类型然后再arrayref<>它的方法是不是就是最官方的做法。这个写法当然能奏效,但是感觉走了点弯路。hmmm。不过我们可以这么写就是了。

有了新的引用泛型,我们就能很方便地自制类似vjass那种外包装语言了。比如利用函数引用和结构来模拟面向对象之类的。

Unit.die()
回复

使用道具 举报

 楼主| 发表于 2012-6-23 21:55:23 | 显示全部楼层
另外,在目前版本中,函数/结构/数组引用尚没有任何GUI支持。因此想要定义一个以结构或者数组或者另一个函数为参数的函数,就必须手写galaxy代码了。注意高亮问题。
回复

使用道具 举报

发表于 2012-6-23 22:00:11 | 显示全部楼层

Re:1.5.0 “指针的回归”函数/结构/数组引用的使用教程

话说这三种引用可以用来进行逻辑判断么?
!=、==之类的
回复

使用道具 举报

 楼主| 发表于 2012-6-23 22:28:04 | 显示全部楼层
!=和==的比较是可以的,

void doSomething()
{
    if(aFuncRef==bFuncRef)
    {
    }
}

但是不能把引用类型直接当成逻辑表达式来使用


void doSomething()
{
    if(aFuncRef)
    {
    }
}

这样就会出错,而且也不能用它们去和null比较,aFuncRef!=null也是非法的。因此无法通过和null比较得知一个函数引用是否是空的。不过却可以判断一个函数引用是否和另一个函数引用指向同一个对象。

所以事先准备一个空引用,然后用它来进行比较就可以判断一个引用使用是空的了。
回复

使用道具 举报

发表于 2012-6-23 23:44:46 | 显示全部楼层
求返回数组方法~
回复

使用道具 举报

 楼主| 发表于 2012-6-23 23:46:30 | 显示全部楼层

回 woaibusi 的帖子

woaibusi:求返回数组方法~ (2012-06-23 23:44)
其实这没关系,因为这东西相当于指针,你可以直接修改传入的值。

这样函数执行完以后,数组已经被修改了。所以返回不返回也无所谓了。
回复

使用道具 举报

发表于 2012-6-24 00:01:10 | 显示全部楼层
头目真是太强大了!
回复

使用道具 举报

发表于 2012-6-24 01:31:55 | 显示全部楼层
先收藏再说………………不过什么时候才能看懂呢………………
回复

使用道具 举报

发表于 2012-6-24 03:45:58 | 显示全部楼层
太厉害了...

内容来自[手机版]
回复

使用道具 举报

发表于 2012-6-24 08:09:52 | 显示全部楼层

回 麦德三世 的帖子

麦德三世:其实这没关系,因为这东西相当于指针,你可以直接修改传入的值。

这样函数执行完以后,数组已经被修改了。所以返回不返回也无所谓了。&#160;(2012-06-23 23:46)&#160;
嗯,这样的话脚本就可以更整洁更灵活了,真棒
回复

使用道具 举报

发表于 2012-6-24 12:05:36 | 显示全部楼层
typedef 只能在全局声明吧?
[codes=galaxy]
void a()
{
}
funcref <a> [2] aa;
typedef funcref<a >[2] func2;
void  aaaa()
{
    func2 aaaaa;
    arrayref<func2> arr2 = aaaaa;
    aa[1] = a;
    aaaaa[1] = a;  
    if (aa[1] == aaaaa[1])
    {
        TriggerDebugOutput(1,StringToText("a"),true);
    }
    if (aa[1] == arr2[1])
    {
        TriggerDebugOutput(1,StringToText("b"),true);
    }
}
[/codes]
测试下,成功的将一个函数引用数组放到数组引用里
这样的话,如果typedef可以放进函数中声明,或者其他不使用typedef的方法来声明数组引用
貌似数组引用可以嵌套了吧。
回复

使用道具 举报

 楼主| 发表于 2012-6-24 13:44:27 | 显示全部楼层
关于数组的嵌套,这里有一段非常有趣的代码。

[codes=galaxy]
typedef int[2] int2;
typedef arrayref<int2>[2] int2refx2;
typedef int2[2] int2x2;
typedef int[2][2] int2D;

int[2] testArray;
int2 testInt2;
int[2][2] test2dArray;
int2refx2 testInt2refx2;

void testArrayRef2x2(arrayref<int2x2> arr){
    arr[0][0]=1;
}
void testArrayRef2D(arrayref<int2D> arr){
    arr[0][0]=1;
}
void testArrayRef2Rx2(arrayref<int2refx2> arr){
    arr[0]=testInt2;
    arr[1]=testArray;
    arr[0][0]=1;
}
void main()
{
    testArrayRef2x2(test2dArray);
    testArrayRef2D(test2dArray);
    testArrayRef2Rx2(testInt2refx2);
}
[/codes]

执行完main()以后,test2dArray[0][0]的值被改成了1,而testInt2[0]的值也被改成了1。

3个arr[0][0]=1的语句看似相同,其实前两个例子和第三个例子的实际意义完全不同哦。
回复

使用道具 举报

发表于 2012-6-24 13:51:37 | 显示全部楼层

回 麦德三世 的帖子

麦德三世:关于数组的嵌套,这里有一段非常有趣的代码。


typedef int[2] int2;
typedef arrayref<int2>[2] int2refx2;
....... (2012-06-24 13:44)
   这让我学C语言的脸往拿放啊....
   真心怀疑写多了这种语言会导致我逻辑错误....
  不过还好,总是有指针了,...55555555

typedef这个东西最近才学了.....有点头大,定义新类型,貌似是这样的吧....
回复

使用道具 举报

发表于 2012-6-24 14:01:37 | 显示全部楼层
[codes=galaxy]
typedef int[2] int2;
typedef arrayref<int2>[2] int22;
typedef arrayref<int22>[2] int222;
int2 i2;
int22 i22;
int222 i222;

void fun()
{
    i2[0] = 0;
    i22[0] = i2;
    i222[0] = i22;
    i222[0][0] = i2;
    i222[0][0] = i22[0];
    i222[0][0][0] =0;
}
[/codes]
看样子可以用这样的递归数组引用做数据库
使用与数组完全相同,但是还可以可以整块的替换数组

唯一的问题就是不能全局……
回复

使用道具 举报

发表于 2012-6-24 14:16:55 | 显示全部楼层

回 疯人¢衰人 的帖子

疯人¢衰人:typedef int[2] int2;
typedef arrayref<int2>[2] int22;
typedef arrayref<int22>[2] int222;
int2 i2;
....... (2012-06-24 14:01)
   看了 这段代码,无语了,不过确实可以这样用啊,就貌似就是多维数组的定义
回复

使用道具 举报

 楼主| 发表于 2012-6-24 14:20:36 | 显示全部楼层

回 疯人¢衰人 的帖子

疯人¢衰人:


唯一的问题就是不能全局……

怎么可能,我不是已经给出了全局化数组引用的方法了么?

——只要把它塞进另一个全局数组里就好了。


structref<aStruct>[1] struRef;
arrayref<int2>[1] arrayRef;
回复

使用道具 举报

 楼主| 发表于 2012-6-24 14:22:43 | 显示全部楼层

回 lia77593 的帖子

lia77593:   看了 这段代码,无语了,不过确实可以这样用啊,就貌似就是多维数组的定义 (2012-06-24 14:16)
多维数组和数组引用数组其实还是不一样的。
回复

使用道具 举报

发表于 2012-6-24 14:31:38 | 显示全部楼层

回 麦德三世 的帖子

麦德三世:怎么可能,我不是已经给出了全局化数组引用的方法了么?

——只要把它塞进另一个全局数组里就好了。
....... (2012-06-24 14:20)
写的时候没注意之前的帖子……

呃,自己用了但是还没发现
回复

使用道具 举报

发表于 2012-6-24 14:36:45 | 显示全部楼层
[code=galaxy]

typedef int[2] int2;
typedef arrayref<int2>[2] int22;
typedef arrayref<int22>[2] int222;
typedef int2 [2] int2_2;
typedef int[2][2] int2X2;
typedef arrayref<int2X2>[2] int2D;
int2 i2;
int22 i22;
int222 i222;
int2_2 i2_2;
int2X2 i2X2;
int2D i2D;
void fun()
{
    i2[0] = 0;
    i22[0] = i2;
    i222[0] = i22;
    i222[0][0] = i2;
    i222[0][0] = i22[0];
    i222[0][0][0] =0;
    //i2_2[1] = i2;Bulk copy not supported
    //i222[0] = i2_2;Bulk copy not supported
   
    //i2X2 = i2_2;Bulk copy not supported
    i2D[0] = i2X2;
    i2D[0] = i2_2;
}
[/codes]

说明一下,带注释的是编译出错的。

    //i2_2[1] = i2;Bulk copy not supported
    //i222[0] = i2_2;Bulk copy not supported
    i2D[0] = i2X2;
    i2D[0] = i2_2;
看来typedef有点类似宏代换的效果。也就是int2X2完全等同于int2_2.

数组引用不能全局声明,但是数组引用数组可以……
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-12-24 08:40 , Processed in 0.078775 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表