本帖最后由 RyanBern 于 2015-8-24 12:13 编辑
[box=RoyalBlue]第7章节:尾声
[/box]
呼~写了这么久,这个脚本教程贴总算要杀青了。不过打开脚本编辑器,我们还有一些部分没有说到,比如说Interpreter类的脚本,我们就没深入讨论。不过,这对于一般的需要来说,已经足够了。那么在最后一章,我们要说一些零碎的内容。说是零碎,其实是编程的死角,这也是一个脚本党的必备知识。
7.1 个人的几个体会
7.1.1 关于alias
这个词我们在很久之前就已经提到了,但是一直没有说它的用法,现在就补上吧。alias的意思是“别名”,在这里是给函数取别名。具体的使用方法是:alias 新方法名 旧方法名,新方法名和旧方法名用空格隔开。那么这个功能有什么用呢?主要是利用在方法的重定义上。当我们要重新定义一个方法,又不想覆盖原来的方法,那么alias就派上用场了。请看下面的例子:class Person
attr_accessor :name
def initialize( name)
@name = name
end
def hello
print "我是" + @name
end
alias old_hello hello
def hello
print “Hello, I am ” + @name
end
end
# 测试一下
ryan = Person.new ( "Ryan" )
ryan.hello
ryan.old_hello
class Person
attr_accessor :name
def initialize( name)
@name = name
end
def hello
print "我是" + @name
end
alias old_hello hello
def hello
print “Hello, I am ” + @name
end
end
# 测试一下
ryan = Person.new ( "Ryan" )
ryan.hello
ryan.old_hello
如果按照上述定义,第一个语句会在屏幕上显示“Hello, I am Ryan”,第二个语句会在屏幕上显示“我是Ryan”,可见alias具有保留原有方法的功能。
在这里,我们看到alias的威力还不算很大。这个语句一般用作整合脚本上,特别是给不会脚本的人写脚本时,我们要写出一个完整的脚本,让用户贴在Main前面即可,但是有时候并不是这样,假设我们要修改前面默认脚本的内容,如果不用alias,那么只能进行重定义。但是对于一些方法,我们可以这样:
假如你给Game_Temp增加一个名叫test_value的属性,并在initialize中将它初始化,我们可以用三种方法。一是直接在Game_Temp上进行修改,不过,如果写脚本给伸手党,那就不太好,二是把整个Game_Temp需要重定义的地方定义一遍,对于Game_Temp这样的脚本来说,重定义一遍无异于整体复制,脚本显得很不整洁,三就是利用alias,具体实现过程如下:
class Game_Temp
attr_accessor :test_value
alias old_initialize initialize
def initialize
@test_value = 0
old_initialize
end
end
class Game_Temp
attr_accessor :test_value
alias old_initialize initialize
def initialize
@test_value = 0
old_initialize
end
end
在这里,我们是先对initialize进行别名,然后重定义,在定义过程中,调用了原方法。因为这里的initialize做的工作就是赋值初始化,因此这样写是没有问题的。
不过,需要注意的是,不能给同一个方法起两个相同的别名,这样说似乎比较别扭,我们来看下面的例子:
假如上面的代码已经定义好,那么我现在继续往Game_Temp里面增加一个叫test_bool的属性(Game_Temp中已经有了我们刚刚增加的test_value,利用的也是alias),由于我们现在的initialize的功能已经能够初始化test_value,因此我们利用alias时,进行重定义的肯定是initialize方法而非old_initialize方法,但是我们不能写下面的:
class Game_Temp
attr_accessor :test_bool
alias old_initialize initialize
def initialize
@test_bool = false
old_initialize
end
end
class Game_Temp
attr_accessor :test_bool
alias old_initialize initialize
def initialize
@test_bool = false
old_initialize
end
end
这样写的话,脚本肯定会出错,因为你又将initialize别名为old_initialize,而刚才那个名字已经有了对应的方法,这样就会出现一个符号表示两个方法的情况,引发内部冲突。因此这个时候就不能用old_initialize做别名了,应该换成别的。
7.1.2 关于类变量@@xxxx
类变量我们在讲类的时候顺便也提了一句,这种变量我们一般不会用到,不过好歹也说一下吧。类变量和通常我们讲的类当中的实例变量不同,类变量作用于整个类的上面。作为一个类生成的实例,这个实例本身可以访问该类的类变量。这样说有些模糊,我们看下面的例子:
class A
# 定义类变量 @@a
@@a = 1
def initialize
# 定义实例变量 @a
@a = 1
end
def a
return a
end
def a=( a)
@a = a
end
def ab
return @@a
end
def ab=( ab)
@@a = ab
end
end
a1 = A.new
a2 = A.new
a1.a = 2
a2.a = 3
a1.ab = 5
print a1.a # => 2
print a2.a # => 3
print a1.ab # => 5
print a2.ab # => 5
class A
# 定义类变量 @@a
@@a = 1
def initialize
# 定义实例变量 @a
@a = 1
end
def a
return a
end
def a=( a)
@a = a
end
def ab
return @@a
end
def ab=( ab)
@@a = ab
end
end
a1 = A.new
a2 = A.new
a1.a = 2
a2.a = 3
a1.ab = 5
print a1.a # => 2
print a2.a # => 3
print a1.ab # => 5
print a2.ab # => 5
在这里,@a表示我们通常用到的实例变量,@@a表示类变量,在这里我们看到,@a这个变量是每个实例私有的变量,而@@a这个变量是两个实例公用的变量。因此我们得到结论,只要是属于同一个类的同一个类变量,无论用这个类的哪个实例修改它,都会有同样的效果,无论用这个类的哪一个实例访问它,都会得到同样的值。
值得注意的是,类本身,也可以对类变量进行修改,不过要来借助类方法来完成。类方法和模块方法比较类似,定义和使用的模式基本相同。
class A
# 定义类变量 @@var
@@var = 0
# 定义类方法
def self .var
return @@var
end
def self .var =( var)
@@var = var
end
end
A.var = 5
p A.var # => 5
class A
# 定义类变量 @@var
@@var = 0
# 定义类方法
def self .var
return @@var
end
def self .var =( var)
@@var = var
end
end
A.var = 5
p A.var # => 5
7.1.3 相同和相等•clone•变量和指针
这个也是我们经常说的一个问题,Ruby是以C语言为基础语言编写的,实际上是对C做的一个优化。我们都知道C语言中有指针这个东西,而Ruby中我们却看不到它。不是说Ruby中没有指针,而是Ruby已经采取某种方式将指针优化掉了。我们在编写程序的同时,就在使用大量的指针,只是我们从未发觉而已。为什么又说起这个事情来呢,这是源于本人最近的一道作业题,我嫌用C太复杂,于是就用Ruby了,但是有一个地方怎么也通不过,一时想破脑袋也没想明白。不过后来好在经过F1指点,终于明白了问题所在,接下来我们就看一下。
我们在前面说了,赋值运算符“=”的作用是将右边的值赋给左边,传递过去的就是对象的引用。所以,当你把一个数组或者一个类的实例赋给一个变量,那么实际传递过去的就是变量的引用,如果你把这个值又赋给另外一个变量,那么依然是传递引用,对一个变量进行的操作必定会影响另一个。因此,有时候我们需要生成一模一样的一个对象,又不想让它们有任何关系,就要用到clone方法,这是Object类(最大的类)的方法,任何类的实例都可以调用,因此,写b = a.clone,就能把a和b区别开来,而且他们的内容也完全一样。我们知道利用比较运算符“==”可以判断两个对象是否相等,而这个运算符只能判断两个对象的值是否相等,例如,有b = a.clone,然后判断b == a,这个结果通常是true,不过,a和b并不相同,在Object类里面也有一个判断是否相同的方法equal?,如果执行b.equal?(a),我们得到的结果将会是false,因为equal?是判断二者是否相同的方法,在这里,a和b仅仅是内容相同,而它们实际上是两个“变量”(即内存的地址不同,这并不稀奇,就好比你和你的双生同胞不是同一个人一样)。
但是,即使是这样,也会遇到一些费解的问题。例如,有下面的定义:
class A
attr_accessor :data
def initialize
@data = [ ]
end
end
x1 = A.new
x1.data [ 0 ] = 0
x2 = x1.clone
x2.data [ 0 ] = 1
print x1.data [ 0 ]
class A
attr_accessor :data
def initialize
@data = [ ]
end
end
x1 = A.new
x1.data [ 0 ] = 0
x2 = x1.clone
x2.data [ 0 ] = 1
print x1.data [ 0 ]
在这里,我们看到屏幕上显示的是1,这个最初看起来是非常奇怪的事情。我们明明对x1做了复制,按理说x1和x2的应该只是内容一样而已,为什么对x2的修改也影响到x1呢?这里我们注意的是,clone进行的只是浅层次的复制,它只能复制对象里面所有变量的内容,而不能复制变量引用的内容。我们知道,一个变量如果表示数组,那实际上就是指向数组的引用,在这里@data就是一个数组,表示的就是数组的引用,而clone做复制时,仅仅把这个引用的值复制了过去,因此它们表示的还是同一个东西。除非你将x2的data重新定向,否则在x2的data上面的改动还是会影响x1。
7.1.4 sprintf表达式
说实话,学C语言的人在Ruby里面看到这个,应该感到无比亲切吧。这可以说是为数不多被保留下来的语句啊。sprintf这个名字蛮奇怪的,print是“打印、输出”的意思,那么后面的f,缩写的是format,表示“格式”,连在一起就表示“格式输出”,当然printf也是C的基本函数之一,前面的s,是表示输出的去向,s缩写的是string,表示这个函数将一定的内容输出到一个字符串内。在C语言中,sprintf的第一个参数是表示接受输入的字符串(字符数组指针),但是在Ruby里面,这个字符串直接作为sprintf的返回值。具体使用方法,如果学过C了,肯定都知道,如果不清楚就F1看看,其中“%”表示格式转换描述,例如写a = sprintf("%d %d", x1, x2),就是把x1和x2的值分别替换两个%d,然后做成字符串送到a中,我们注意%d是整数转换描述,如果x1表示的不是整数,那么就会出错的,因此,格式输出一定要严格控制,千万不能出现转换错误。
7.2 写在最后
这个RGSS1教程帖就这样结束了,不知道能坚持看到最后的你们,能不能有所收获呢?我不敢期望这个教程的效果有多么强大,但是,只要它能为大家写游戏做出一丝一毫的贡献,我就知足了。
每次写完一章,我都很期待大家的回帖,不过从第四章开始,这个帖子基本没有什么回帖的了,于是我就一直连帖下去,一直连帖,直到最后我发现大家似乎已经没有心情看下去了。我想这也是我个人方面的原因,第5章和第6章中间间隔将近一个月,或者说是我才疏学浅,但总之,我写这个东西的目的就是让大家能学会分拆脚本,根据默认脚本或者其他大神的作品的构造来提炼出自己的东西。
很多人看到脚本教程,给出的评价,大多都是“变量计算、类那里还行,到后面就什么也看不懂了”、“看完教程后就学会了一个p函数,别的都没学会”,我其实还满希望我的教程能摆脱这个评价,但是现在想想,或许这个教程帖也难逃此厄运。毕竟,如果没有经历系统的学习,没有理解计算机工作的原理,只是走马观花地看教程,恐怕也没什么效果。这样的教程写出来,结果往往都是,会脚本的人更厉害了,不会脚本的人依然没学到什么……总之,虽然截稿了,但是很郁闷。
6R站上已经有了很多RMXP的经典教程,而且侧重点各有不同。推荐大家在阅读教程帖子的时候,在多个教程之间比对,发现其中的不同,从而提出问题。毕竟所有的教程都会有疏漏和错误,写出一个完美的教程也实在是太过于困难。
最后,建议想要学习脚本的同学,一定要多看,多提问,多练习,少伸手。学习的初期可能做不出自己想要的东西,这没有关系,学习脚本切忌急功近利,心急往往很容易冲淡学习脚本的热情。起初建议大家模仿默认脚本来修改和练习,慢慢磨合,今后使用脚本才能更加得心应手。