GameMonkey参考手册官方资料翻译
- 时间:2015年04月02日 15:39:55 来源:魔法猪系统重装大师官网 人气:14441
GameMonkey 脚本参考手册
使用lua已经1年多了, 至今还常常惊叹于其作者设计的简洁和提供给用户的一套机制, "工具就在那里摆着, 去发挥你的想象力吧"~~~ lua的接口最好的体现了提供机制而不是策略的思想. 在游戏编程精粹中, 看到一篇关于介绍脚本语言的文章, 其中不光介绍了lua, 也介绍了GameMonkey :) 大概做了一下了解, 发现国内好像尚无使用的先例, 资料也比较少, 本着学习借鉴, 开拓思路的态度, 决定翻译GameMonkey的官方资料, 希望能对需要的人有帮助. 其中也有我自己认为要详细说一下的, 提醒一下的, 用rotc注出来了
comments 注释
// 和c++一样注释到本行末
/*
和c / c++ 一样的注释块
*/
注释块会被编译器忽略掉, 它的作用是给代码做注释或者临时使一段代码失效[调试脚本时常用]
变量和常量
GameMonkey不像c, Pascal那样的强类型语言, 它更像是Basic语言.
GM中的变量是以下类型中的一个:
null -- 没有值, 这有类型
int -- 32bit的有符号整数
float -- 32bit的浮点数
string -- null结尾的ansi字符串
table -- 数组/hash容器
function -- 函数
user -- 用户自定义类型
rotc: 如何理解null 是一种类型, 而不是一个值?
对c++er 和 cer来说, null/NULL基本就是0, 或(void*)0, 它的确是一个具体的值. 这里说GM中的null是个类型, 可能会在理解上有一定的困难. 根源是静态类型和动态类型造成的. 静态类型语言中, 类型和值是分裂开来的, 对于静态类型语言中的变量i来说, 如果能够通过编译, 那么i的类型就肯定是确定的. 静态类型语言中的类型, 只是用来说明如何对&i这样一个内存地址做解释(但问题在于这个说明是在编译期就必须决定的). 也就是说c中的变量直接映射到了内存和如何解释内存. 而动态语言通过引入一个间接层, 其值的结构是 Value(type, realvalue), 也就是说, 一个变量由一个内存和附带一个指示如何解释该内存的标志(及类型)组成的. 这样的好处是显而易见的, 可以在运行时改变变量类型(也就是改变对内存的解释方式), 下面演示动态语言中如何实现变量赋值时决定类型.
比如我创造了一门动态语言, 这门语言中有2个类型, 那么这样实现
enum {null, man, woman}; // 两个类型加一个null类型
struct Value{ Value(){type = null; pData = 0;} char type; void* pData}; // 动态语言中的变量
struct Man {Man(int h, int c){housevalue = h; carvalue = c;} int housevalue; int carvalue}; // 男类型内容是房产和车产
struct Woman { Woman(char* name) {strcpy(sweetname, name);} char sweetname[12]; }; // 女类型有一个可爱的名字
在我的脚本中:
Value pp; // 定义个一个变量, 注意, 现在这个变量不是一个Man, 也不是一个Woman, [但它有类型--null, 但是它没有值]
pp = Man(5,3); //制造一个富家男, 注意pp 现在的类型由null变成man, 值是一个Man
// 实现 void operator = (Value& v, Man& m) {
v.type = man; // 赋类型
v.pData = &m; // 赋值
}
pp = Woman(“X姐"); // 制造了一个X姐[芙蓉姐, 凤姐], 注意pp现在的类型由man变成women了, 值是一个Woman
// 实现 void operator = (Value& v, Man& m) {
v.type = woman; // 赋类型
v.pData = &m; // 赋值
}
pp = null;
// 实现 ..... v.type = null;
当你掩去c++的实现时, 脚本:
Value pp;
pp = Man(5, 3);
pp = Woman(“X姐”);
pp = null;
上面就展示了如何在脚本语言中实现所谓的一个变量既能存int (Man), 又能存string(Woman), 还能只有类型没有值(type==null)的奥秘, 其实就是引入了一个间接层, 把静态语言编译时就要确定某个内存地址要怎么解释, 变成了{解释方式, 内存}这种形式, 这样的话, 可以在运行期改变解释方式和值[当然他们是匹配的], [ 可以注意到, 动态分配内存是支持这种实现的不可缺少的机制, 垃圾收集的需求也伴随而来]
最后总结: null表示不解释~~~:) 你懂的
GM中, 变量名是大小写敏感的, __t0 和 __t1保留做内部使用.
变量名 = [a..zA..Z] + [a..zA..Z_0..9]*
例子:
a = null; // a 没有值
b = 4; // b 是int类型
c = 4.4; // c 是float类型
d = “hello”; // d 是string类型
e = table(); // e是一个空的表
f = function() {}; // f 是一个函数
更多的例子:
a = ‘SMID’; // a 是一个int, 值为(‘S’<<24 | ‘M’<<16 | ‘I’<<8 | ‘D’)
b = .23; // b 是一个float
c = 2.4f; // c 是一个float
d = ‘c:\windows\sys’; // d是一个string
语言和它的标准函数总是试图保留值, 然而不是保留类型. 具体规则是当不同类型的变量在一起运算时, 高级别的类型将被保留. 类型从低级到高级的顺序是: int, float, string.
例子:
print(“hello” + 4); // 输出: hello 4, 4的类型被提高
print(2 + 5); // 输出: 7, int类型被保留
print(2.0 + 5); // 输出: 7.0, 5的类型被提高
print(sqrt(17.0)); // 输出: 4.1231, float类型被保留
print(sqrt(17)); // 输出: 4, int类型被保留
int类型赋值的例子:
a = 179; // 十进制
a = 0xB3; // 十六进制
a = 0b0011001 // 二进制
a = ‘BLCK’; // 字符转成4个byte分别赋予int的四个byte中
float类型赋值例子:
b = 45.34; // float十进制
b = .345; // float
b = 289.0; // float
b = 12.34f; // c风格float
b = 2.3E-3; // 科学计数法
字符串赋值例子:
c = “c:\\path\\file.ext”; // 标准双引, 用\做转义字符
c = ‘c:\path\file.ext’; // 和上面一样, 单引情况下, \不做转义字符用
c = “Mary says \”hello\””; // 相当于'Mary says "hello"'
c = 'Chris' 's bike'; // 相当于'Chris's bike', 也就是说在单引内部表示单引的方法是连续两个单引
c = “My ” “house”; // 相当于"My house"
基础类型可以使用标准内建库进行显示的转换, Int(), Float(), String()
例子:
a = 10;
b = a.String(); // 这样是最好的, 显示的调用类型转化函数, 返回转化后的值
b = “” + a; // 这样不好, 赋值会将a的类型提升到string, 但是效率底下
b = (10).String(); // 丑陋的
b = 10.String(); // 错误的, 编译不过, 因为编译器不认同这种语法
引用类型变量的可引用类型有String, Function, Table, User. 当这些变量被赋值时, 并不发生full copy, 而只是让变量指向具体的obj
例子:
a = table(“apple”, "orange"); // a是一个指向table的引用
b = a; // b 现在和a指向同一个table
b[1] = "banana"; // 设置b[1]
print(a[0], a[1]); // >> banana orange
print(b[0], b[1]); // >> banana orange
当一个变量被赋新值时, 该变量原来持有的值就有可能丢失掉了.
例子:
Oper = function(a, b){
return a + b
}; // Oper现在指向一个函数
Oper = “hello”; // Oper现在指向字符串, 原来的函数被丢失了
函数
语法: function() { };
一个函数体是一个值, 而函数是一个类型 {type = GM_FUNCTION, value=function...}
注意: 记住在将函数赋值给变量后面那个分号, 这是语法必须的
例子
// 将一个创建一个rect table的函数赋值给CreateRect
CreateRect = function(posX, posY, sizeX, sizeY){
rect = table(x=posX, y=posY, width=sizeX, height=sizeY);
rect.Area = function() {return .width * height; };
return rect;
};
myRect = CreateRect(0, 0, 5, 10); // 创建一个用于描述rect的table
area = myRect.Area();
// 可以用:代替.来隐式的传递一个this指针
Size = function(){
return .width * .height;
};
s = myRect:Size(); // 调用时, myRect会当做this指针传入Size中
作用域
和作用域有关的一些关键字, 语法:
global
Local
member
this
this.
.
函数中的变量.
默认情况下, 一个在函数中使用的变量就是这个函数的本地变量. 如果要声明一个全局变量, 需要使用global关键字. 访问成员变量必须通过this或者是使用member关键字声明它是一个成员变量. 在局部使用的变量可以用local关键字声明.
例子:
Access = function(){ // Access 是一个local变量, 它引用着一个函数
apple = 3; // apple 是函数的一个local变量
global apple; // 把apple声明成全局作用域
local apple; // 把apple声明成局部作用域
member apple; // 把apple声明成 this的member变量
this.apple; // 明确的访问this.apple
.apple // 隐式的访问this.apple
};
例子:
a = 13; // a 是一个local作用域变量
print(b); // b是null
global b = function() { // b是一个全局作用域的变量, 类型是GM_FUNCTION
global c = 2; // c是一个全局作用域的变量
d = 3; // d是函数局部作用域变量
{ if (c == 2)
{ local e = 3; } // e 从这一刻开始成为函数局部作用域变量, 注意没有块作用域变量
}
print(e); // e = 3
}
在查找一个变量时, 按照local 和 parameters, 然后global的顺序查找.
成员变量有微妙的不同:
h = function() { // h 是一个local变量
global a = 3; // a 是一个全局变量
member n; // n可以从this被访问和创建
d = 3; // d是函数局部作用域
this.b = 3; // b是member作用域
.b = .x + 1; // b, x都是member作用域
print(b); // b 是 null, 因为这里并没有local的b
print(n); // 就像print(this.n)一样, 因为上面显示声明过了
};
全局作用域中的语句.
x = 7; // local
global x = 8; // global
a = function(y) {
local x = 5; // function local
dostring(“print(x);”); // 这里打出8, 和lua一样, dostring总是在全局环境下编译运行的, 无法访问function的变量和parameters
};
变量可以是虚拟机全局作用域的, 也可以是某个函数作用域的, 或者是某个obj比如table的成员作用域的. 当执行一个文件或者是执行一个字符串的时候, 和lua一样, 文件或者是字符串被编译成一个无名的函数, 所以默认情况下, 其中最外层的未加特别申明的变量是该无名函数的函数作用域的.
this总是存在的. 它或者是null, 或者是一个有效的值. 你可以传递this, 或者是使用重载冒号操作符默认的this. 这一特性多用在创建诸如类似模板行为, 那些地方的obj的操作往往只有run-time时才能确认. this也用在创建线程, 例子:
obj:thread(obj.DoThings) // 开始一个线程, 并把obj作为this传递给它
obj:MakeFruit(apple, orange) // 调用MakeFruit, 并把obj当做this传给它
语法和操作符
! Not 逻辑取反
~ 每一个bit位取反
^ bit位使用与或 XOR
| bit位使用或 OR
& bit位使用与 AND
>> bit位右移
<< bit位左移
~= bit位取反赋值
^= bit位 XOR 赋值
|= bit位 OR 赋值
&= bit位 AND 赋值
>>= bit位 右移 赋值
<<= bit位 左移 赋值
= 赋值
' 单引 其中的字符会当做int值
" 双引 字符串(处理转义字符)
` 反引 字符串(不处理转义字符)
[] 方阔 用index取talbe元素
. 取table元素
: 给函数传递this
+ 数学+
- 数学-
* 数学*
/ 数学/
% 模取余
+=, –=, *=, /=, %= 数学运算并赋值
{} 界定语句块
; 语句结束标志
<, <=, >, >=, == 数学比较
&&或and 逻辑AND
|| 或or 逻辑OR
Tables 表
语法: table( = , ...);
table(, …);
{=, …, };
{, …, };
table可以被同时认为是array 和 map. 因为table中可以容纳data和function, 所以table也可以被认为是class, table中也可以容纳别的table, 这时它也已被认为是Tree.
初始化table的例子:
fruit = table("apple", "banana", favorite= "tomato", "cherry");
fruit = {"apple", "banana", favorite="tomato", "cherry"};
这时, fruit的样子就是:
fruit[0] = “apple”;
fruit[1] = “banana”;
fruit[2] = “cherry”;
fruit[“favorite”] = "tomato"; 也可以写作是 fruit.favorite = "tomato"
可以注意到, fruit.favorite="tomato"并没有占据 element[2], 虽然它在逻辑上应该是element[2]的位置, 但是它不是一个index索引成员, 是一个{key, value}成员.
从表中取得元素的例子.
a = thing.other; // other 是table thing中的一个成员
b = thing[“other”]; // 相当于b = thing.other
c = thing[2]; // c取得了thing中的第三个indexd索引成员
index = 3;
d = thing[index]; // 用int做下标, 就可以把table当数组访问
accoc = “fav”;
e = thing[accoc]; // 用string做下标, 就可以把table当map访问
注意, thing["Fav"]和thing["fav"]是两个不同的东西, 因为GM是大小写敏感的. 这样做设计上的考虑是:
1) 赋值可能是string, 也可能是任何类型的值.
2) 要做到大小写无关, 底层需要一些额外的工作量, 这会产生一定量的效率问题.
设置table中成员的值的例子.
thing.other = 4;
thing[3] = “hello”;
嵌套表的例子:
matrix = { {1, 2, 3,}, {4, 5, 6}, {7, 8, 9,}, } //
print(“matrix[2][1] = ”, matrix[2][1]); // 输出"matrix[2][1] = 8"
关键字if和else
语法: if ( ) { }
或者 if ( ) { } else { }
或者 if ( ) { } else if ( ) { } else { }
例子:
foo = 3;
bar = 5;
if ((foo * 2) > bar){
print(foo * 2, “is greater than”, bar);
}
else{
print(foo * 2, “is less than”, bar);
}
// 输出: 6 is greater then 5
if 会计算条件表达式的值, 并根据其结果的true/false来选择执行那一段语句.
if 在计算条件时, 会像大多数语言那样, 并且实现了短路求值, 下面是一些例子:
if (3 * 4 + 2 > 13) == if ( ( (3*4) + 2) > 13 )
if (3 > 0 || 0 > 1) 3 > 0恒真, 那么永远不会去对0 > 1求值
对c程序员的提示: 你不能把condition和一个单语句无语句块标示的statements写在同一行, 这是语法不允许的
例: if ( a > 3) b = 4; // 错误, b = 4 必须被{}包起来
关键字for
语法: for (; ; ) { }
例子:
for (index = 0; index < 6; index = index + 2){
print(“Index = ”, index);
}
输出是:
Index = 0
Index = 2
Index = 4
for 语句的执行和大多数语言一样, 循序是
1. 执行 statement1
2. 执行condition, 是false就退出for
3. 执行statements
4. 执行statement2, goto 2
关键字foreach
语法: foreach ( and in ) {
使用lua已经1年多了, 至今还常常惊叹于其作者设计的简洁和提供给用户的一套机制, "工具就在那里摆着, 去发挥你的想象力吧"~~~ lua的接口最好的体现了提供机制而不是策略的思想. 在游戏编程精粹中, 看到一篇关于介绍脚本语言的文章, 其中不光介绍了lua, 也介绍了GameMonkey :) 大概做了一下了解, 发现国内好像尚无使用的先例, 资料也比较少, 本着学习借鉴, 开拓思路的态度, 决定翻译GameMonkey的官方资料, 希望能对需要的人有帮助. 其中也有我自己认为要详细说一下的, 提醒一下的, 用rotc注出来了
comments 注释
// 和c++一样注释到本行末
/*
和c / c++ 一样的注释块
*/
注释块会被编译器忽略掉, 它的作用是给代码做注释或者临时使一段代码失效[调试脚本时常用]
变量和常量
GameMonkey不像c, Pascal那样的强类型语言, 它更像是Basic语言.
GM中的变量是以下类型中的一个:
null -- 没有值, 这有类型
int -- 32bit的有符号整数
float -- 32bit的浮点数
string -- null结尾的ansi字符串
table -- 数组/hash容器
function -- 函数
user -- 用户自定义类型
rotc: 如何理解null 是一种类型, 而不是一个值?
对c++er 和 cer来说, null/NULL基本就是0, 或(void*)0, 它的确是一个具体的值. 这里说GM中的null是个类型, 可能会在理解上有一定的困难. 根源是静态类型和动态类型造成的. 静态类型语言中, 类型和值是分裂开来的, 对于静态类型语言中的变量i来说, 如果能够通过编译, 那么i的类型就肯定是确定的. 静态类型语言中的类型, 只是用来说明如何对&i这样一个内存地址做解释(但问题在于这个说明是在编译期就必须决定的). 也就是说c中的变量直接映射到了内存和如何解释内存. 而动态语言通过引入一个间接层, 其值的结构是 Value(type, realvalue), 也就是说, 一个变量由一个内存和附带一个指示如何解释该内存的标志(及类型)组成的. 这样的好处是显而易见的, 可以在运行时改变变量类型(也就是改变对内存的解释方式), 下面演示动态语言中如何实现变量赋值时决定类型.
比如我创造了一门动态语言, 这门语言中有2个类型, 那么这样实现
enum {null, man, woman}; // 两个类型加一个null类型
struct Value{ Value(){type = null; pData = 0;} char type; void* pData}; // 动态语言中的变量
struct Man {Man(int h, int c){housevalue = h; carvalue = c;} int housevalue; int carvalue}; // 男类型内容是房产和车产
struct Woman { Woman(char* name) {strcpy(sweetname, name);} char sweetname[12]; }; // 女类型有一个可爱的名字
在我的脚本中:
Value pp; // 定义个一个变量, 注意, 现在这个变量不是一个Man, 也不是一个Woman, [但它有类型--null, 但是它没有值]
pp = Man(5,3); //制造一个富家男, 注意pp 现在的类型由null变成man, 值是一个Man
// 实现 void operator = (Value& v, Man& m) {
v.type = man; // 赋类型
v.pData = &m; // 赋值
}
pp = Woman(“X姐"); // 制造了一个X姐[芙蓉姐, 凤姐], 注意pp现在的类型由man变成women了, 值是一个Woman
// 实现 void operator = (Value& v, Man& m) {
v.type = woman; // 赋类型
v.pData = &m; // 赋值
}
pp = null;
// 实现 ..... v.type = null;
当你掩去c++的实现时, 脚本:
Value pp;
pp = Man(5, 3);
pp = Woman(“X姐”);
pp = null;
上面就展示了如何在脚本语言中实现所谓的一个变量既能存int (Man), 又能存string(Woman), 还能只有类型没有值(type==null)的奥秘, 其实就是引入了一个间接层, 把静态语言编译时就要确定某个内存地址要怎么解释, 变成了{解释方式, 内存}这种形式, 这样的话, 可以在运行期改变解释方式和值[当然他们是匹配的], [ 可以注意到, 动态分配内存是支持这种实现的不可缺少的机制, 垃圾收集的需求也伴随而来]
最后总结: null表示不解释~~~:) 你懂的
GM中, 变量名是大小写敏感的, __t0 和 __t1保留做内部使用.
变量名 = [a..zA..Z] + [a..zA..Z_0..9]*
例子:
a = null; // a 没有值
b = 4; // b 是int类型
c = 4.4; // c 是float类型
d = “hello”; // d 是string类型
e = table(); // e是一个空的表
f = function() {}; // f 是一个函数
更多的例子:
a = ‘SMID’; // a 是一个int, 值为(‘S’<<24 | ‘M’<<16 | ‘I’<<8 | ‘D’)
b = .23; // b 是一个float
c = 2.4f; // c 是一个float
d = ‘c:\windows\sys’; // d是一个string
语言和它的标准函数总是试图保留值, 然而不是保留类型. 具体规则是当不同类型的变量在一起运算时, 高级别的类型将被保留. 类型从低级到高级的顺序是: int, float, string.
例子:
print(“hello” + 4); // 输出: hello 4, 4的类型被提高
print(2 + 5); // 输出: 7, int类型被保留
print(2.0 + 5); // 输出: 7.0, 5的类型被提高
print(sqrt(17.0)); // 输出: 4.1231, float类型被保留
print(sqrt(17)); // 输出: 4, int类型被保留
int类型赋值的例子:
a = 179; // 十进制
a = 0xB3; // 十六进制
a = 0b0011001 // 二进制
a = ‘BLCK’; // 字符转成4个byte分别赋予int的四个byte中
float类型赋值例子:
b = 45.34; // float十进制
b = .345; // float
b = 289.0; // float
b = 12.34f; // c风格float
b = 2.3E-3; // 科学计数法
字符串赋值例子:
c = “c:\\path\\file.ext”; // 标准双引, 用\做转义字符
c = ‘c:\path\file.ext’; // 和上面一样, 单引情况下, \不做转义字符用
c = “Mary says \”hello\””; // 相当于'Mary says "hello"'
c = 'Chris' 's bike'; // 相当于'Chris's bike', 也就是说在单引内部表示单引的方法是连续两个单引
c = “My ” “house”; // 相当于"My house"
基础类型可以使用标准内建库进行显示的转换, Int(), Float(), String()
例子:
a = 10;
b = a.String(); // 这样是最好的, 显示的调用类型转化函数, 返回转化后的值
b = “” + a; // 这样不好, 赋值会将a的类型提升到string, 但是效率底下
b = (10).String(); // 丑陋的
b = 10.String(); // 错误的, 编译不过, 因为编译器不认同这种语法
引用类型变量的可引用类型有String, Function, Table, User. 当这些变量被赋值时, 并不发生full copy, 而只是让变量指向具体的obj
例子:
a = table(“apple”, "orange"); // a是一个指向table的引用
b = a; // b 现在和a指向同一个table
b[1] = "banana"; // 设置b[1]
print(a[0], a[1]); // >> banana orange
print(b[0], b[1]); // >> banana orange
当一个变量被赋新值时, 该变量原来持有的值就有可能丢失掉了.
例子:
Oper = function(a, b){
return a + b
}; // Oper现在指向一个函数
Oper = “hello”; // Oper现在指向字符串, 原来的函数被丢失了
函数
语法: function(
一个函数体是一个值, 而函数是一个类型 {type = GM_FUNCTION, value=function...}
注意: 记住在将函数赋值给变量后面那个分号, 这是语法必须的
例子
// 将一个创建一个rect table的函数赋值给CreateRect
CreateRect = function(posX, posY, sizeX, sizeY){
rect = table(x=posX, y=posY, width=sizeX, height=sizeY);
rect.Area = function() {return .width * height; };
return rect;
};
myRect = CreateRect(0, 0, 5, 10); // 创建一个用于描述rect的table
area = myRect.Area();
// 可以用:代替.来隐式的传递一个this指针
Size = function(){
return .width * .height;
};
s = myRect:Size(); // 调用时, myRect会当做this指针传入Size中
作用域
和作用域有关的一些关键字, 语法:
global
Local
member
this
this.
.
函数中的变量.
默认情况下, 一个在函数中使用的变量就是这个函数的本地变量. 如果要声明一个全局变量, 需要使用global关键字. 访问成员变量必须通过this或者是使用member关键字声明它是一个成员变量. 在局部使用的变量可以用local关键字声明.
例子:
Access = function(){ // Access 是一个local变量, 它引用着一个函数
apple = 3; // apple 是函数的一个local变量
global apple; // 把apple声明成全局作用域
local apple; // 把apple声明成局部作用域
member apple; // 把apple声明成 this的member变量
this.apple; // 明确的访问this.apple
.apple // 隐式的访问this.apple
};
例子:
a = 13; // a 是一个local作用域变量
print(b); // b是null
global b = function() { // b是一个全局作用域的变量, 类型是GM_FUNCTION
global c = 2; // c是一个全局作用域的变量
d = 3; // d是函数局部作用域变量
{ if (c == 2)
{ local e = 3; } // e 从这一刻开始成为函数局部作用域变量, 注意没有块作用域变量
}
print(e); // e = 3
}
在查找一个变量时, 按照local 和 parameters, 然后global的顺序查找.
成员变量有微妙的不同:
h = function() { // h 是一个local变量
global a = 3; // a 是一个全局变量
member n; // n可以从this被访问和创建
d = 3; // d是函数局部作用域
this.b = 3; // b是member作用域
.b = .x + 1; // b, x都是member作用域
print(b); // b 是 null, 因为这里并没有local的b
print(n); // 就像print(this.n)一样, 因为上面显示声明过了
};
全局作用域中的语句.
x = 7; // local
global x = 8; // global
a = function(y) {
local x = 5; // function local
dostring(“print(x);”); // 这里打出8, 和lua一样, dostring总是在全局环境下编译运行的, 无法访问function的变量和parameters
};
变量可以是虚拟机全局作用域的, 也可以是某个函数作用域的, 或者是某个obj比如table的成员作用域的. 当执行一个文件或者是执行一个字符串的时候, 和lua一样, 文件或者是字符串被编译成一个无名的函数, 所以默认情况下, 其中最外层的未加特别申明的变量是该无名函数的函数作用域的.
this总是存在的. 它或者是null, 或者是一个有效的值. 你可以传递this, 或者是使用重载冒号操作符默认的this. 这一特性多用在创建诸如类似模板行为, 那些地方的obj的操作往往只有run-time时才能确认. this也用在创建线程, 例子:
obj:thread(obj.DoThings) // 开始一个线程, 并把obj作为this传递给它
obj:MakeFruit(apple, orange) // 调用MakeFruit, 并把obj当做this传给它
语法和操作符
! Not 逻辑取反
~ 每一个bit位取反
^ bit位使用与或 XOR
| bit位使用或 OR
& bit位使用与 AND
>> bit位右移
<< bit位左移
~= bit位取反赋值
^= bit位 XOR 赋值
|= bit位 OR 赋值
&= bit位 AND 赋值
>>= bit位 右移 赋值
<<= bit位 左移 赋值
= 赋值
' 单引 其中的字符会当做int值
" 双引 字符串(处理转义字符)
` 反引 字符串(不处理转义字符)
[] 方阔 用index取talbe元素
. 取table元素
: 给函数传递this
+ 数学+
- 数学-
* 数学*
/ 数学/
% 模取余
+=, –=, *=, /=, %= 数学运算并赋值
{} 界定语句块
; 语句结束标志
<, <=, >, >=, == 数学比较
&&或and 逻辑AND
|| 或or 逻辑OR
Tables 表
语法: table(
table(
{
{
table可以被同时认为是array 和 map. 因为table中可以容纳data和function, 所以table也可以被认为是class, table中也可以容纳别的table, 这时它也已被认为是Tree.
初始化table的例子:
fruit = table("apple", "banana", favorite= "tomato", "cherry");
fruit = {"apple", "banana", favorite="tomato", "cherry"};
这时, fruit的样子就是:
fruit[0] = “apple”;
fruit[1] = “banana”;
fruit[2] = “cherry”;
fruit[“favorite”] = "tomato"; 也可以写作是 fruit.favorite = "tomato"
可以注意到, fruit.favorite="tomato"并没有占据 element[2], 虽然它在逻辑上应该是element[2]的位置, 但是它不是一个index索引成员, 是一个{key, value}成员.
从表中取得元素的例子.
a = thing.other; // other 是table thing中的一个成员
b = thing[“other”]; // 相当于b = thing.other
c = thing[2]; // c取得了thing中的第三个indexd索引成员
index = 3;
d = thing[index]; // 用int做下标, 就可以把table当数组访问
accoc = “fav”;
e = thing[accoc]; // 用string做下标, 就可以把table当map访问
注意, thing["Fav"]和thing["fav"]是两个不同的东西, 因为GM是大小写敏感的. 这样做设计上的考虑是:
1) 赋值可能是string, 也可能是任何类型的值.
2) 要做到大小写无关, 底层需要一些额外的工作量, 这会产生一定量的效率问题.
设置table中成员的值的例子.
thing.other = 4;
thing[3] = “hello”;
嵌套表的例子:
matrix = { {1, 2, 3,}, {4, 5, 6}, {7, 8, 9,}, } //
print(“matrix[2][1] = ”, matrix[2][1]); // 输出"matrix[2][1] = 8"
关键字if和else
语法: if (
或者 if (
或者 if (
例子:
foo = 3;
bar = 5;
if ((foo * 2) > bar){
print(foo * 2, “is greater than”, bar);
}
else{
print(foo * 2, “is less than”, bar);
}
// 输出: 6 is greater then 5
if 会计算条件表达式的值, 并根据其结果的true/false来选择执行那一段语句.
if 在计算条件时, 会像大多数语言那样, 并且实现了短路求值, 下面是一些例子:
if (3 * 4 + 2 > 13) == if ( ( (3*4) + 2) > 13 )
if (3 > 0 || 0 > 1) 3 > 0恒真, 那么永远不会去对0 > 1求值
对c程序员的提示: 你不能把condition和一个单语句无语句块标示的statements写在同一行, 这是语法不允许的
例: if ( a > 3) b = 4; // 错误, b = 4 必须被{}包起来
关键字for
语法: for (
例子:
for (index = 0; index < 6; index = index + 2){
print(“Index = ”, index);
}
输出是:
Index = 0
Index = 2
Index = 4
for 语句的执行和大多数语言一样, 循序是
1. 执行 statement1
2. 执行condition, 是false就退出for
3. 执行statements
4. 执行statement2, goto 2
关键字foreach
语法: foreach (





