http://www.gamesdeity.com/

vJass完整版教程

版本:JassHelper
0.A.0.0

作者

站点

Vexorian(原作者)

http://www.wc3c.net/

JoyWoon(翻译和修订)

https://www.gamesdeity.com

 



 

前言

作者

尽管最终WEHelperWorld EditorJass编译器替换为PJass,但仍然会有一些很多的问题待解决,这就是开始这个项目的原因。

 

译者注:WEHelper是非常早期的WE插件工具,目前已经基本过时了。

 

在项目的过程中,后来我感觉更进了一步,并产生了将Jass扩展到面向对象编程思路的想法。

 

JassHelpervJass语言的编译器,支持vJass的很多功能,其中包括结构,库,文本宏等等……

 

虽然这不是真正的面对对象编程模式,但我依旧希望语法能够足够的强大。

 

vJass没有真正的继承性,这就是我不将其称之为对象类而是结构的原因。不过,vJass仍然有着允许多态接口功能,并且,由于还可以声明新结构的数据类型,因此,您可以写出一些具有伪继承性的代码。伪继承性在以下的教程中,会告诉你,能通过多种方式去实现。

 

我认为最终应该停止设计vJass的语言。因此在1.0.0版本之后,不会再更新任何新的内容,如果您有任何需求,请紧记,在1.0.0版本之后进行语法更改对vJass是很不健康的。

Z.0版本在JassHelper中引入了Zinc语言,这只是vJass的简单替代,在某些方面也会更加严谨。

译者

vJassJass的扩展,目前几乎所有的WEer都使用JassHelper,因此,vJass的通用性非常强。vJass的基本语法和Jass没有任何区别,只是在此基础之上增加了更多功能性的语法,以支持面向对象式的编程方式。

我着重要求读者一定要好好了解vJass的结构功能。该功能基本上将魔兽争霸3地图的开发引上了一个新的台阶。



 

目录

vJass完整版教程………………………………………………………………………………………. 1

版本:JassHelper 0.A.0.0………………………………………………………………. 1

前言…………………………………………………………………………………………………….. 2

作者…………………………………………………………………………………………….. 2

译者…………………………………………………………………………………………….. 3

目录…………………………………………………………………………………………………….. 4

一. 自由声明………………………………………………………………………………………. 8

全局变量…………………………………………………………………………………….. 8

本地函数…………………………………………………………………………………… 10

二. ……………………………………………………………………………………………….. 14

库的初始化………………………………………………………………………………. 20

静态ifs语句…………………………………………………………………………….. 23

静态成员…………………………………………………………………………………… 24

Scope ………………………………………………………………………………….. 26

公共成员…………………………………………………………………………………… 29

域的嵌套…………………………………………………………………………………… 32

三. 结构……………………………………………………………………………………………. 39

声明结构…………………………………………………………………………………… 41

创建和销毁结构………………………………………………………………………. 42

结构运用…………………………………………………………………………………… 45

实例成员…………………………………………………………………………………… 47

结构类型的全局变量………………………………………………………………. 49

静态成员…………………………………………………………………………………… 50

公共/私有结构…………………………………………………………………………. 51

方法…………………………………………………………………………………………… 53

封装形式…………………………………………………………………………………… 55

静态方法…………………………………………………………………………………… 57

析构处理…………………………………………………………………………………… 62

结构初始化………………………………………………………………………………. 64

接口…………………………………………………………………………………………… 66

重载…………………………………………………………………………………………… 80

结构的继承………………………………………………………………………………. 94

存根方法…………………………………………………………………………………… 99

Super 语句…………………………………………………………………………… 102

动态数组………………………………………………………………………………… 104

数组成员………………………………………………………………………………… 110

委托………………………………………………………………………………………… 113

Thistype 语句………………………………………………………………………. 117

四. 模块化……………………………………………………………………………………… 118

五. 将函数作为对象……………………………………………………………………… 123

函数接口………………………………………………………………………………… 127

Typecast 类型转换……………………………………………………………… 131

六. 将方法作为对象……………………………………………………………………… 133

方法是否存在………………………………………………………………………… 134

七. 数组结构…………………………………………………………………………………. 136

八. 键值…………………………………………………………………………………………. 139

九. 储存增强…………………………………………………………………………………. 141

介绍………………………………………………………………………………………… 141

数组大小………………………………………………………………………………… 143

二维数组………………………………………………………………………………… 145

具有更多索引空间的结构……………………………………………………. 148

十. Jass语法扩展…………………………………………………………………………. 152

冒号………………………………………………………………………………………… 152

换行注释………………………………………………………………………………… 152

十一. 文本宏…………………………………………………………………………………. 154

十二. 钩子……………………………………………………………………………………… 161

十三. 注入……………………………………………………………………………………… 163

十四. 从SLK文件加载结构………………………………………………………… 165

SLK文件………………………………………………………………………………… 165

结构类型………………………………………………………………………………… 166

十五. 代码的调…………………………………………………………………………. 169

十六. JassHelper功能…………………………………………………………………. 171

避免局部变量重影………………………………………………………………… 171

return bug修复程序…………………………………………………………… 172

导入外部脚本文件………………………………………………………………… 174

Zinc………………………………………………………………………………………… 177

编译忽略………………………………………………………………………………… 178

脚本优化………………………………………………………………………………… 179

外部工具………………………………………………………………………………… 181

换行修复………………………………………………………………………………… 184

命令行……………………………………………………………………………………. 185

更新………………………………………………………………………………………… 189

卸载………………………………………………………………………………………… 190

团队和感谢……………………………………………………………………………………. 191

更新日志………………………………………………………………………………………… 193

 

 



 

. 自由声明

vJass可以自由的声明全局变量和本地函数。

全局变量

魔兽争霸3WorldEditor在很多地方使用起来并不方便。包括声明全局变量,您需要不停的使用界面进行全局变量申明……

并且只允许您在固定的位置声明,例如在“自定义脚本”或“触发器编辑器”中声明变量。

而且在早期的时候,在不用“触发器编辑器”的情况下,甚至只能通过修改地图脚本.j文件来添加全局变量。或者使用常函数的方式来替代。

vJass完美解决了这个问题。

 

JassHelper的编译器将合并地图脚本中所有的全局变量声明块,并将它们移至地图脚本的顶端,最终将所有全局变量声明统一的放在一起,以保障以暴雪的地图脚本规范保存。

 

function something takes nothing returns nothing

    set somearray[SOMETHING_INDEX]=4

endfunction

 

globals

    constant integer SOMETHING_INDEX = 45

    integer array somearray

endglobals

 

现在这样写可以正常工作,但Jass有一个限制,您不能在全局变量的默认值中使用函数或非恒定值。

例如,可以用null1199230xFFFtruefalse,“ Hi”等。

您不能在变量赋值中调用任何函数或地图初始化中的内容。(尽管可以使用本地API函数,但是绝大数的本地API函数在全局变量声明中使用时往往会使地图加载线程崩溃)。
您也不能将某个全局变量分配给另一个全局变量,因为实际上没有任何方法可以控制全局变量声明的顺序。

 

注意:

  • 请保持良好的全局命名规范。有些代码规范对全局名称的使用要求纯大写形式。在common.jblizzard.j中,一般常量都应为大写,然后以system_variable作为良好的变量名。当然……您也可以坚持使用udg_前缀。



 

本地函数

此功能(在0.9.I.0版本中添加)类似于声明全局变量,但它是针对更高级用户的,因此,如果您不想了解这段中的任何内容,可以直接跳到下一段

 

Warcraft
III
支持在地图脚本中声明本地API函数,但是只能在地图脚本的全局变量声明(globals…endglobals)块之后申明本地API函数。和全局变量的使用一样,JassHelper将在地图上检测到这些声明并将它们移到地图脚本的正确位置。

 

到底什么是本地接口函数呢?

 

比如为AI脚本创建的一些本地API函数未在common.j中声明(这些API函数可以在\scripts\common.ai中找到),它们中的某些实际上有可能对Jass的编写者有用。

 

例如:

native UnitAlive  takes unit id returns boolean

 

API函数仅在\scripts\common.ai中被声明,如果我们在地图脚本中声明一次该函数,就可以以更直接的方式判断单位是否存活,而不是使用Bj包装的间接判断函数(其实是通过判断单位生命值是否<=0来判断存活性的)。

 

还有可能您用的是魔兽争霸III的修改版(modded)或hacked 版,里面有很多自定义的本地API函数(JAPI),这些都需要进行一次声明。但为这些自定义的API函数导入一个新的common.j可能对您来说太麻烦了。因此,在这种情况下,您可以在地图中将新的自定义API函数(JAPI)自由的进行的声明。

 

此功能有一项保护,JassHelper会自动删除重复的本地API函数声明。 (即,如果已经在common.j中声明了一次,它将从地图脚本中删除重复的声明,以确保您的地图可正常运行)。
这取决于你提供给JassHelpercommon.j是哪个版本。

 

如果出于某种原因,您(或newgen包)传递给JassHelpercommon.j版本与您希望使用的common.j版本不同,则需要考虑到这一点。

 

范例:

native GetUnitGoldCost takes integer unitid returns integer

function test takes nothing returns nothing

    call BJDebugMsg(“A footman consts : “+I2S( GetUnitGoldCost(‘hfoo’)+” gold coins” ) )

endfunction

 



 



 

.

WorldEditorJass的另一个问题是,无法控制地图脚本中触发器的顺序,所以要求用户在编写代码需时刻注意先后的顺序,这是一种反模块化的编程方式,可能使整个过程充满着毫无意义的操作,浪费不必要的精力。

 

vJass基本上解决了这个问题。

库(一种针对编译器的预处理方式),可以使最重要的函数在编译时自动被放在脚本的最前端,因此可以控制每个函数的顺序。
它还具有依存关系支持,因此您可以将各种函数归纳到不同的模块库,而不必担心它们互相之间调用时,需要过多考虑其在脚本中的位置。

 

语法很简单:

library 库名称

或者

library 库名称requires 依存库名

或者

library 库名称requires 依存库名1, 依存库名2

别忘了标记库的结束,使用关键字endlibrary 

 

例子:

library B

    function Bfun takes nothing returns nothing

    endfunction

endlibrary

 

library A

    function Afun takes nothing returns nothing

    endfunction

endlibrary

 

如果JassHelper在编译时,找到了此命令,它将确保将AfunBfun函数移到地图脚本的顶端,因此地图脚本的其余部分可以自由调用Afun() Bfun()函数。

 

注意:不确定如果从B库中的函数调用Afun()函数会发生什么。该命令只是将库移到顶部,我们并不知道B库是在A库之前还是之后。

 

如果B库中的函数需要调用A库中的函数,我们应该让JassHelper知道必须在B库之前添加A库。这就是‘requires’关键字存在的原因:

    library B requires A

        function Bfun takes nothing returns nothing

           call Afun()

        endfunction

    endlibrary

 

    library A

        function Afun takes nothing returns nothing

        endfuncti

    endlibrary

 

注意:出于某种原因:requiresneeds uses 都可以正常使用,并且在语法上都具有相同的功能。

 

它将Afun移动到地图的顶部,并将Bfun放置在其后,Bfun现在可以自由调用Afun()

 

一个库可以有多个依存关系,只需用逗号将它们分开:

    library C needs A, B, D

        function Cfun takes nothing returns nothing

            call Afun()

            call Bfun()

            call Dfun()

        endfunction

    endlibrary

 

    library D

        function Dfun takes nothing returns nothing

        endfunction

    endlibrary

 

    library B uses A

        function Bfun takes nothing returns nothing

            call Afun()

        endfunction

    endlibrary

 

    library A

        function Afun takes nothing returns nothing

        endfunction

    endlibrary

 

在脚本顶端的结果会是这样:

    function Afun takes nothing returns nothing

    endfunction

    function Dfun takes nothing returns nothing

    endfunction

    function Bfun takes nothing returns nothing

        call Afun()

    endfunction

    function Cfun takes nothing returns nothing

        call Afun()

        call Bfun()

        call Dfun()

    endfunction

 

或者可能是这样:

    function Dfun takes nothing returns nothing

    endfunction

    function Afun takes nothing returns nothing

    endfunction

    function Bfun takes nothing returns nothing

        call Afun()

    endfunction

    function Cfun takes nothing returns nothing

        call Afun()

        call Bfun()

        call Dfun()

    endfunction

 

或者:

    function Afun takes nothing returns nothing

    endfunction

    function Bfun takes nothing returns nothing

        call Afun()

    endfunction

    function Dfun takes nothing returns nothing

    endfunction

    function Cfun takes nothing returns nothing

        call Afun()

        call Bfun()

        call Dfun()

    endfunction

 

依存关系将改变我们的脚本存放顺序。因为每个库都根据函数的使用情况对依存关系进行了设置,所以3种可能的方式均不会导致任何编译错误。

 

请记住以下几点:

  • 库名称会区分大小写。
  • 如果库A依存库B,库B又依存库A,则会产生一个循环,JassHelper将报出语法错误。
  • 如果库A依存的库依存库B,而库B依存库A,则循环仍然存在。
  • 库不能嵌套库。
  • 自版本0.9.B.0起,库会定义一个全局变量,库的依存情况决定了这个变量的添加顺序。

 

库的初始化

通常来说,很难确定谁先谁后,因此库还具有initializer关键字,可以定义库的初始化函数,该函数将在地图初始化过程中优先被执行。

 

您可以在库的名称后添加initializer
<
函数名>,并使用ExecuteFunc使其优先执行,ExecuteFunc 会使用一个新的线程。因为太多的库会进行初始化,导致init线程进行了繁重的操作,所以我们可以更好地防止init线程崩溃。 在initializer 关键字之后,也可以使用依存语句:requiresneeds uses 

 

只要使用了initializers
关键字的库函数,都会被添加在脚本前端中运行。 因此,如果库A依存库B并且两个库都有initializers函数,则Binitializers 函数将在A之前被调用。

 

注意,initializer初始化函数不能有任何参数。

library A initializer InitA requires B

    function InitA takes nothing returns nothing

       call StoreInteger(B_gamecache , “a_rect” , Rect(-100.0 , 100.0 , –100.0 , 100  ) )

    endfunction

endlibrary

 

library B initializer InitB

    globals

        gamecache B_gamecache

    endglobals

    function InitB takes nothing returns nothing

        set B_gamecache=InitGameCache(“B”)

    endfunction

endlibrary

Binitializer函数 将在Ainitializer
函数之前在初始化线程中调用。

 

提示:

  • library_once关键字的工作方式与library完全相同,但是您可以两次声明相同的库名,它只会忽略第二个声明,并避免添加其内容而不是显示语法错误,与textmacros结合使用时非常有用。
  • 较旧的vJass版本的库语法不同,以//!开头! 最终不建议使用,现在将弹出语法错误。

注意:从0.9.Z.0开始,库的声明将创建一个名为“LIBRARY_库名称”布尔常量,默认值为真。因此依存的库可以是<可选的>(在requires 之后添加一个optional 关键字前缀),如果未找到所需依存的库,不会发生语法错误。 并且可以使用:static ifs判断库是否存在。



 

静态ifs语句

static
ifs
与普通ifs相似,不同之处在于

a)条件必须使用布尔值常量,and 操作符以及not 操作符。

b)在编译期间会对此进行分析。条件不匹配的代码都会被忽略编译。

library OptionalCode requires optional UnitKiller

        globals

            constant boolean DO_KILL_LIB = true

        endglobals

 

        function fun takes nothing returns nothing

            local unit u = GetTriggerUnit();

            //以下代码会杀死该单位,但是也许可以使用外部库的”UnitKiller’ 函数去执行。

            //仅当DO_KILL_LIBtrue并且UnitKiller的库在脚本中时会执行其中代码。

            static if DO_KILL_LIB and LIBRARY_UnitKiller then

            //请使用static if来判断UnitKiller库是否存在。

            //常规的if不会在编译时删除这些代码,因此将导致语法错误。

                call UnitKiller(u);

            else

                call KillUnit(u);

            endif

        endfunction

 

    endlibrary

 

    library UnitKiller

 

        function UnitKiller(unit u)

            call BJDebugMsg(“Unit kill!”);

            call KillUnit(u);

        endfunction

 

    endfunction

 

 

静态成员

通过添加一些库来对域进行控制是一个好主意,而私有成员(private members)则是保护用户避免冲突的好方法。

library privatetest

    globals

        private integer N=0

    endglobals

    private function x takes nothing returns nothing

        set N=N+1

    endfunction

 

    function privatetest takes nothing returns nothing

        call x()

        call x()

    endfunction

endlibrary

 

library otherprivatetest

    globals

        private integer N=5

    endglobals

    private function x takes nothing returns nothing

        set N=N+1

    endfunction

 

    function otherprivatetest takes nothing returns nothing

        call x()

        call x()

    endfunction

endlibrary

 

请注意,这两个库都有相同名称的全局变量和函数,但是这不会引起任何语法上的错误,因为private 预处符将确保私有成员在仅可使用的域内使用,并且不会与域外名称相同的内容冲突。

但在这种情况下,私有成员仅能在声明它们的库的域范围内使用。

 

Scope 

有时,您不希望代码到达脚本的前端(它实际上并不算是函数库),但仍想对一组全局变量和函数使用私有关键字private。 这就是我们定义域scope 关键字的原因。

 

scope 关键字的语法:

scope 名称

[…脚本内容…]

endscope

 

如此,域内的函数和声明可以自由的在域内使用,但外部代码将无法使用。
(注意,库应被视为具有内部范围的域)

 

此域内有许多应用程序:

scope GetUnitDebugStr

 

    private function H2I takes handle h returns integer

        return h

        return 0

    endfunction

 

    function GetUnitDebugStr takes unit u returns string

        return GetUnitName(u)+”_”+I2S(H2I(u))

    endfunction

endscope

 

在这种情况下,该函数使用H2I,但是H2I是一个非常常见的函数名称,因此可能与外部声明同样名称的脚本发生冲突,您可以自己为H2I函数名称添加一个独立的前缀,或者将库设置为依存其他拥有H2I的库,但有时可能过于复杂,而且为了降低模块之间的耦合性,建议使用私有关键字private,这样您可以在该域中自由使用H2I,而不必担心,如果在其他地方声明了另一个H2I,并且它不是私有函数也没关系。域对私有成员的运用保持最高优先级(当私有成员名称和外部公共名称一样时)。

 

这对于全局变量更为重要,例如为了封装,则可能希望禁止外部直接访问域内的全局变量,而只允许访问某些开放的函数,以保持一种包络感。

 

私有(private)工作的方式实际上是通过重命名scopename(随机数)__自动前缀到私有成员的标识符名称上。 随机数是一种使其真正保密的方法,因此人们甚至不能通过添加前缀来使用它们。
使用double __是因为我们认为这是识别预处理程序生成变量/函数的方法,因此您应避免在人工声明的标识符名称中使用double __。 读取输出文件时(例如,当PJass返回语法错误时),能够识别预处理器生成的标识符非常有用。

 

使用ExecuteFunc或真值更改事件,您必须使用SCOPE_PRIVATE(请参见下面的内容)

 

提示:作用域支持初始化函数,就像库一样,实现上也有所不同,这是因为它们使用普通调用而不是ExecuteFunc调用,如果您需要初始化繁重的函数,最好使用库初始化程序,或使用ExecuteFunc调用子函数。

 

注意:类似于库的方式,域曾经的语法是//!。目前已不支持旧语法,这将导致错误。

 

公共成员

公共成员与私人成员密切相关,因为他们的行为基本相同,不同之处在于公共成员的名字不会随机化,并且可以在域范围之外使用。
对于在称为SCP域中声明为公共的变量/函数,您可以仅在作用域内使用声明的函数/变量名称,但是要在作用域外使用它,则可以使用SCP_前缀将其调用。

 

一个例子应该更容易理解:

    library cookiesystem

        public function ko takes nothing returns nothing

            call BJDebugMsg(“a”)

        endfunction

 

        function thisisnotpublicnorprivate takes nothing returns nothing

             call ko()

             call cookiesystem_ko() //cookiesystem_ 字首可用可不用

        endfunction

    endlibrary

 

    function outside takes nothing returns nothing

         call cookiesystem_ko() //cookiesystem_ 字首是必须的

    endfunction

 

公用函数成员可由ExecuteFunc或实数变量事件使用,但是当用作字符串时,它们始终需要其库/域名称的前缀:

    library cookiesystem

        public function ko takes nothing returns nothing

            call BJDebugMsg(“a”)

        endfunction

 

        function thisisnotpublicnorprivate takes nothing returns nothing

             call ExecuteFunc(“cookiesystem_ko”) 

                //无论是否在库范围内都需要前缀

             call ExecuteFunc(“ko”) 

                //这很可能会使游戏崩溃。

             call cookiesystem_ko()

                //不需要前缀,但可以这么用。

             call ko() 

                //因为不需要前缀,所以该行正常运行。

        endfunction

    endlibrary

 

或者,您可以使用SCOPE
PREFIX
请参见下文

 

注意:如果在名为InitTrig的函数上使用公共成员关键字public,则会以特殊方式处理它,而不是成为ScopeName_InitTrig,它将变为InitTrig_ScopeName,因此您可以在触发器中使用对应的作用域名称使其存在于一个域/库内,并使其成为公共成员。而不需要手动再回到域的内部创建InitTrig_Correctname(不推荐)。



 

域的嵌套

域可以嵌套,不要将这句话和“库可以嵌套”混淆,实际上,您甚至不能在的范围内定义。 但是,您可以在库的范围内或者其他域的范围内定义

 

一个域在另外一个域内被定义,被视为子域。 子域被认为是父域的公共成员。

 

子域不能被声明为私有或全局成员。

 

子域相对于其父域,与普通域相对于整个地图脚本的情况相同。

 

由于子域始终是公共成员,因此您可以在父域之外访问子域的公共成员,但是它需要父项的前缀和子项的前缀。

 

一个例子 :

    library nestedtest

        scope A

          globals

            private integer N=4

          endglobals

 

          public function display takes nothing returns nothing

            call BJDebugMsg(I2S(N))

          endfunction

        endscope

 

        scope B

            globals

                public integer N=5

            endglobals

 

            public function display takes nothing returns nothing

                call BJDebugMsg(I2S(N))

            endfunction

        endscope

 

        function nestedDoTest takes nothing returns nothing

            call B_display()

            call A_display()

        endfunction

 

    endlibrary

 

    public function outside takes nothing returns nothing

        set nestedtest_B_N= –4

        call nestedDoTest()

        call nestedtest_A_display()

    endfunction

 

下一个示例将导致语法错误:

    library nestedtest

        globals

            private integer N=3

        endglobals

 

        scope A

          globals

            private integer N=4 //错误:N已经被申明

          endglobals

        endscope

    endlibrary

 

它实际上是由解析器中的一个限制引起的,存在一个冲突,该冲突是由于将N用作父级,然后再将其声明给子级而引起的。 但是,以下版本不会导致语法错误:

    library nestedtest

        scope A

          globals

            private integer N=4

          endglobals

        Endscope

 

        globals

            private integer N=3

        endglobals

 

    endlibrary

 

这样做看起来的确是一样的,但是由于在父库之前声明了子域的N,所以解析器不再感到困惑。

 

要记住的另一件事是,与普通全局变量不同,私有/公共全局变量不能在声明之前使用,否则JassHelper会认为它们只是普通变量。

 

域不能重复声明,不能有两个名称相同的域。 但是也有例外,如果两个子域是不同父域范围的子域(例如不同库中的),虽然它们具有相同的名称,但是因为编译器在编译时,实际上根据其父域或库编译成了不同的名称。

    library nestedtest

      scope A

        function kkk takes nothing returns nothing

            set N=N+5  

            //JassHelper解析器到达这一行时,它还没有看到私有整数N的声明,因此它假定N是使用的全局变量并且不进行任何替换

        endfunction

      endscope

 

   endlibrary

 

   scope X

       scope A

       //再次声明域A不会造成任何问题,因为实际上是X_A,所以先前声明的A域实际上是nestedtest_A

          function DoSomething takes nothing returns nothing

          endfunction

       endscope

    endscope

 

域没有任何嵌套限制,但是请注意,根据域嵌套的深度,其私有/公共成员的变量和函数名称会越来越长。 较长的变量名可能会影响游戏运行时的性能,但并不是很多。可以通过地图优化器来防止此效率问题,地图优化器一般会自动缩短函数、变量的名称。

SCOPE_PREFIX SCOPE_PRIVATE 语句

在域/库中,SCOPE_PREFIXSCOPE_PRIVATE都是可以使用的字符串常量。

 

SCOPE_PREFIX将返回当前域的名称(以Jass字符串形式),并与一个下码连接。(为公共成员添加了前缀)

 

SCOPE_PRIVATE将为私有成员返回当前前缀的名称(作为Jass字符串)。

scope test

    private function kol takes nothing returns nothing

        call BJDebugMsg(“…”)

    endfunction

    function lala takes nothing returns nothing

         call ExecuteFunc(SCOPE_PRIVATE+”kol”)

    endfunction

endscope

 

在示例中,我们允许lala() 通过ExecuteFunc调用私有函数kol

 

keyword关键字语句

keyword关键字语句允许您在不声明实际函数/变量/等的情况下为作用域范围内声明的替换指令。出于多种原因,它很有用,最重要的原因是,您不能在其范围内私有/公共成员声明之前就使用它们,在大多数情况下,此限制仅是一个麻烦,要求您更改对应的声明位置,在某些情况下,这也是JASS的局限性。

 

例如,两个相互递归的函数可以使用.evaluate相互调用,但是如果您还希望函数是私有的,那么不使用关键字就无法做到这一点:

scope myScope

 

   private keyword B 

    //为避免与域的外部冲突,我们可以将B声明为私有。

   private function A takes integer i returns nothing

       if(i!=0then

           return B.evaluate(i-1)*2 

    //因为B被声明为作用域的私有成员,我们现在可以使用evaluate安全调用它

       endif

       return 0

   endfunction

 

   private function B takes integer i returns nothing

       if(i!=0then

           return A(i-1)*3

       endif

       return 0

   endfunction

endscope

 

. 结构

结构将Jass引入面向对象的编程范畴。

 

如果不先举一个例子,我将无法解释它:

struct pair

    integer x

    integer y

endstruct

function testpairs takes nothing returns nothing

    local pair A=pair.create()

    set A.x=5

    set A.x=8

 

    call BJDebugMsg(I2S(A.x)+” : “+I2S(A.y))

 

    call pair.destroy(A)

endfunction

 

如您所见,您可以在一个结构中存储多个值,然后就可以像使用另一个Jass类型一样使用该结构。请注意,这里的成员的语法类似于大多数编程语言的语法。



 

声明结构

使用结构之前,您需要先声明它。 语法只是 struct <结构名>endstruct关键字。

 

要声明成员,您只需使用<类型> <名称> [=初始值]

 

在上面的示例中,我们声明了一个名为pair的结构类型,该结构类型具有2个成员:xy,它们没有设置初始值。

 

通常,将初始值分配给成员是一个好主意,这样您就不必在创建结构对象之后再手动初始化它们,通常的默认值为空值,但是取决于您要解决的问题,取决于您是否需要其他的初始值。



 

创建和销毁结构

结构是伪动态的,您经常需要创建和销毁结构,并且应该创建一个结构之后将其分配给变量。

 

创建结构的语法是(实际上是获取其唯一ID):结构类型名称.create()

 

在上述结构的情况下,您将必须使用pair.create()
获得新的结构。

 

JassHelper只是一个预处理器,而不是hack,因此,vJass还是受Jass自身的局限性,在这种情况下,结构体使用数组的值限制为8191个,而且我们不能使用索引0(结构体为null), 因此限制为8190个实例。 此限制适用于vJass每种类型的实例,因此您具有8190个成对结构类型的对象,并且仍然能够具有许多其他类型结构的实例。这意味着,如果您继续创建更多其他类型的结构而不会受到破坏。

 

在创建实例达到结构实例极限的情况下,structtype.create()将返回0

 

通常情况下,实例极限不是一个令人担心的问题,除非您想创建链表或类似的东西,否则8190是一个庞大的数目。

 

例如,如果仅将结构用于法术实例数据,则甚至不能达到9个以上的实例。 并且许多其他实际应用程序基本不会超过2000个实例。

 

除非,不再使用的实例结构不会被摧毁(Destroy)。 在那种情况下,我们应该想办法解决这种问题。(对于结构,与句柄不同,不销毁它们不会增加内存使用率,但是有达到极限的风险)。

 

通过计算,每秒创建一个结构实例,并且忘记删除它,则地图需要2小时16分钟才能达到该结构类型的极限。

 

无论如何,如果您想要让其他人使用它,并且你不确定在运行过程中是否会达到极限,可以在调用create()之后使用0进行判断,并以某种方式阻止该过程,以防发生错误。

 

使用结构,如果达到限制并且您没有办法去捕获,可能在之后会引起一些冲突,根据其使用的方式,冲突的强度可能为null或很大。

 

如果在编译脚本时打开调试模式,则一旦达到限制,create() 将显示警告消息。

 

要销毁一个结构,您只需使用destroy方法,该方法可以用作实例方法或类方法,在上面的示例中,调用pair.destroy(a) 用于销毁实例,但是您也可以调用a.destroy()达到同样的效果。

 

在您尝试销毁0null)结构的情况下,如果打开了调试模式,则destroy将不起作用或显示警告消息。



 

结构运用

只需以声明普通类型的变量/函数/参数的方式声明结构值即可。

 

声明结构并创建成员后,就可能需要访问它们,通常是(struct value).(member name)

 

访问成员后,其用法与变量的用法非常相似。 您可以使用set语句,也可以在表达式中将其用作值。

struct pair

    integer x=1

integer y=2

endstruct

function pair_sum takes pair Apair B returns pair

 local pair C=pair.create()

    set C.x=A.x+B.x

    set C.y=A.y+B.y

 return C

endfunction

function testpairs takes nothing returns nothing

 local pair A=pair.create()

 local pair B=pair_sum(A, A)

 local pair C=pair_sum(A,B)

 

    call BJDebugMsg(I2S(C.x)+” : “+I2S(C.y))

    //用完结构实例后,别忘了摧毁它们,避免溢出

    call B.destroy()

    call C.destroy()

    call pair.destroy(A)

endfunction

 

将显示“ 36



 

实例成员

因此,您可以声明任何类型的结构成员,甚至是结构类型。 但是,您不能声明数组成员,在更高版本中此限制应该已被解除。

struct pairpair

    pair x=0 

    //您不能使用pair.create(),而应该只对初始默认值使用常量。

    pair y=0

endstruct

function testpairs takes nothing returns nothing

    local pairpair A=pairpair.create()

    local pair x

 

    set A.x=pair.create()

    set A.y=pair.create()

 

    set x=A.y 

    //注意,我们将A.y保存在备用变量中,以便我们可以销毁它。

    set A.y= pair_sum(A.x,A.y) 

    //这取代了A.y,这就是我们保存它的原因

 

    call BJDebugMsg(I2S(  A.y.x )+” : “+I2S( A.y.y )) 

    //注意 .嵌套

    call A.x.destroy()

    call A.y.destroy()

    call A.destroy()

    call x.destroy()

endfunction

 



 

结构类型的全局变量

您可以具有结构类型的全局变量。 由于Jass的限制,您无法直接对其进行初始化。

globals

     pair globalpair=0 //合法

     pair globalpair2= pair.create() //不合法

endglobals

 

您将不得不在init函数中分配它们。



 

静态成员

静态成员的行为就像struct语法中的全局变量一样,只需在成员语法之前添加static关键字即可。 也可以有静态数组。

 

static关键字也可以运用于结构的方法。



 

公共/私有结构

您可以在域范围内自由声明公共、私有结构以及结构变量

scope cool

    public struct a

        integer x

    endstruct

 

    globals

        a x

        public a b

    endglobals

 

    public function test takes nothing returns nothing

        set b = a.create()

        set b.x = 3

        call b.destroy()

    endfunction

endscope

 

function test takes nothing returns nothing

    local cool_a x=cool_a.create()

    set a.x=6

    call a.destroy()

endfunction

 



 

方法

方法(method)就像函数一样,不同之处在于它们与结构类型相关联。普通的方法也与结构实例相关联(在本例中为“ this”)

再一次需要一个例子:

    struct point

        real x=0.0

        real y=0.0

 

        method move takes real txreal ty returns nothing

            set this.x=tx

            set this.y=ty

        endmethod

 

    endstruct

 

    function testpoint takes nothing returns nothing

        local point p=point.create()

        call p.move(56,89)

 

        call BJDebugMsg(R2S(p.x))

    endfunction

 

this表示当前实例指针的关键字,普通的方法是实例方法,这意味着它们是从已经分配的该类型的变量/值中调用的,它将指向该实例。 在上面的示例中,我们在方法内部使用此方法来分配xy,当调用p.move()时,最终会改变p指向的实体结构的xy属性。

method语法:您可能会注意到方法语法与函数语法非常相似。

this替代写法:您还可以使用一个 .  。 当您在实例方法中时。(可以这么写,set .member = value

 

方法与普通函数的不同之处在于,可以在任何地方调用它们(全局变量声明时除外),并且您不一定可以在其中使用waitssync nativesGetTriggeringTrigger()(但是您可以使用任何其他事件响应),
您也许可以使用它们,但这取决于多种因素,因此不建议您完全使用它们。 在下一版本中,编译器在找到它们时甚至可能引发语法错误。



 

封装形式

封装是一种面向对象的编程概念,在该概念中,您可以授予结构成员的访问权限,换句话说,它对于结构是私有的还是公共的。

    struct encap

        real a=0.0

        private real b=0.0

        public real c=4.5