设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
楼主: DeathKing
打印 上一主题 下一主题

[讨论] Ruby/RGSS Tips 每日一更 [技术区的版聊帖?]

  [复制链接]

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
22
发表于 2010-9-24 06:50:17 | 只看该作者
本帖最后由 紫苏 于 2010-9-24 07:01 编辑

承接二十一楼:
2、在当前局部范围内使用闭包块进行跳转:
  1. lambda {
  2.   for i in 1..10
  3.     for j in i..10
  4.         p i, j
  5.         return if i + j > 12 # 跳出两层循环
  6.     end
  7.   end
  8. }.call
  9. p '跳出了循环'
复制代码
当然,如果你的循环过程独立于其它流程,那么用方法也是可以的;否则由于 Ruby 1.8 的方法没有进行上下文绑定(而闭包则保持了闭包被创建时那一刻的绑定),在方法内部是无法引用外部的变量的,因为它们分属于不同的栈帧,互不可见(正因如此,方法内的变量才被称为局部变量)
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
21
发表于 2010-9-23 07:35:35 | 只看该作者
其实话题不少,但两百字能说清楚的比较少,还是每天来打一瓶酱油好了 <--- 这是闲话

今天来说一下跳出两层以上循环的方法,为了节省字数每天只说一种方法——
1、使用内置的异常机制,在多层循环内部抛出一个异常,在多层循环外捕获这个异常即可:

  1. begin
  2.   for i in 1..10
  3.     for j in i..10
  4.         p i, j
  5.         raise if i + j > 12 # 跳出两层循环
  6.     end
  7.   end
  8. rescue
  9.   p '跳出了循环'
  10. end
复制代码

点评

to moy:catch(:symbol) do;xxxx;throw :symbol;end。  发表于 2010-9-23 22:59
moy
强烈要求看到其他方法...  发表于 2010-9-23 19:12

评分

参与人数 1星屑 +1776 收起 理由
DeathKing + 1776 200字是软规定,卓越贡献者的中秋礼包。 ...

查看全部评分

回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
20
发表于 2010-9-17 08:00:12 | 只看该作者
本帖最后由 紫苏 于 2010-9-17 08:03 编辑

左值——赋值运算符 `=' 左边的值
右值——赋值运算符 `=' 右边的值
Ruby 有一个继承自 Perl 的语法糖:平行赋值,它缩短了赋值语句的代码体积。平行有“并发”之意,即同时赋值,互不干扰。
  1. a,b,c=1,2,3
复制代码
这是最基本的平行赋值语法,1、2、3 按出现顺序被分别赋给了 a、b、c。
当最后一个右值是带了星号前缀的数组对象时,数组会被展开并把元素依次赋给对应的左值:
  1. arr = [ 2, 3 ]
  2. a,b,c = 1,*arr
复制代码
这和第一句代码的运行效果没区别。
当右值只有一个,而左值不止一个时,右值就会被当做或转换为数组对象,并依次把元素赋给匹配的左值:
  1. a,b,c = Time.now.to_a
  2. p a,b,c
复制代码
虽然平行赋值在实现层必然不是真正意义上的平行,但经过 Ruby 的抽象层后,在用户看来这确实是平行的,以下代码可以用来交换两个变量的值,而如果赋值有先后顺序(不并发),交换则不会成功:
  1. a,b = b,a
复制代码
如果左值数量多于右值,没有匹配右值的左值会被赋为 nil;如果右值数量多于左值,没有匹配左值的右值会被忽略。
如果最后一个左值有星号作为前缀,那么 Ruby 会保证它接收到的是一个数组。如果这时左值数量少于右值,那么剩下的右值会被一起封装到数组中赋给这个左值:
  1. *c = 1,2,3,4
  2. p c
复制代码
还可以给左值添加括号进行“嵌套赋值”,解决了数组形式的右值不能带着星号前缀出现在右值列表尾部之前的问题:
  1. (a,b,c),d = [1,2,3],4
  2. p a,b,c,d
复制代码
Ruby 的块参数的传递、Proc 参数的传递都使用平行赋值的规则:
  1. def foo
  2.   yield [1, [2, 3], 4], *[5, 6]
  3. end

  4. foo { |(a,(b,c),d),e,f| p a,b,c,d,e } # a、b、c、d、e 分别被赋予了 1、2、3、4、5
复制代码

点评

Parallel assignment,这是 Programming Ruby 的说法  发表于 2010-9-26 21:53
那个手册里叫多重赋值  发表于 2010-9-26 17:37
moy
这个我还真不知道...原来ruby的赋值运算符还有这种特性....受教了..  发表于 2010-9-17 08:38
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
19
发表于 2010-9-13 02:57:58 | 只看该作者
(为了控制在 200 字以内决定采用打酱油模式)

喜欢 C printf 语法的人在 Ruby 中也得到了满足——Kernel::sprintf 是 C sprintf 的面向对象版,它不需要调用者传递缓冲区,而是自己(被调用者)分配一个字符串对象返回给调用者。它的基本作用是格式化字符串,输出按照指定格式格式化的一串数据,而这些数据本身可能不是字符串:
  1. p sprintf('大家好,我是 %s,我今年 %d 岁。', '张三', 23)
复制代码
这里即是把 '张三' 这个字符串对象(对应 %s 指示符)和 23 这个 Fixnum 对象(对应 %d 指示符)格式化到了前面的那句话中。
实际上 Ruby 还提供了更简便的方法,就是 String#% 方法。以下代码等价于以上代码:

  1. p '大家好,我是 %s,我今年 %d 岁。' % [ '张三', 23 ]
复制代码
% 前面是格式,后面是格式中指定了的额外数据,如果额外数据只有一个,那可以省略数组的外壳:
  1. p '大家好,我今年 %d 岁。' % 23
复制代码
对 printf 语法有兴趣的朋友可以参考:http://www.cplusplus.com/reference/clibrary/cstdio/printf/

点评

好像 sprintf 不能把 100000 轉為 "100,000" 這樣的格式。剛才在討論區看到這個問題,想說用 sprintf,居然發覺他不給力。200 字滿賽!  发表于 2010-9-13 06:27
moy
200字果然是高难度, sprintf 给力啊~~  发表于 2010-9-13 04:07
回复 支持 反对

使用道具 举报

Lv3.寻梦者

孤独守望

梦石
0
星屑
3132
在线时间
1535 小时
注册时间
2006-10-16
帖子
4321

开拓者贵宾

18
发表于 2010-9-12 08:17:45 | 只看该作者
(决不超过200字)
不用妄想修改RPG模块内类的initialize方法来追加数据,因为这个方法压根就没被调用过。
除非你的手上有method_defined?(不是关键字defined?),否则不要alias RGSS内建类。否则,F12君会生气的。
F12会抛出一个信号错误,叫做Reset。未定义?是在第一次抛出时被定义。
除非你用了紫苏的句柄脚本,否则一切句柄皆不可靠。同时开俩游戏,第一个就玩完。
手机打字好痛苦…OTL

点评

@禾西:那时是序列化到文件了,实际创建对象是在 load_data 的时候,所以只要在原有基础上重定义 _dump 和 _load 并覆盖原rxdata数据就可以做额外的初始化  发表于 2010-9-12 09:50
RPG模塊的內類是在你打開工程,Edit數據庫的時候被調用的。這個時候的RGSS腳本還有一旁畫圈圈,根本不給力。  发表于 2010-9-12 08:33
200 太考验操作了,你这意识绝对是高端……话说 RPG 内类的对象可以重写 _load 或者 new 追加初始化操作啥的  发表于 2010-9-12 08:24
菩提本非树,明镜本非台。回头自望路漫漫。不求姻缘,但求再见。
本来无一物,何处惹尘埃。风打浪吹雨不来。荒庭遍野,扶摇难接。
不知道多久更新一次的博客
回复 支持 反对

使用道具 举报

Lv2.观梦者

神隐的主犯

梦石
0
星屑
283
在线时间
271 小时
注册时间
2008-2-22
帖子
7691

贵宾

17
发表于 2010-9-11 00:33:50 | 只看该作者

2010-9-11

让我们来看看这个代码:
  1. def show_times
  2.    yield
  3.    yield
  4. end
  5. show_times { p "Hello World" }
复制代码
以上的运行结果是什么? 应该是出现两次 "Hello World".
为什么呢? 这个需要从 block 说起.

block 说的通俗点就是回调函数,所谓的回调函数就是一个通过函数指针调用的函数。
上面的代码其实可以这么理解, yield 就是调用方法内部提供的 block, 而这个 block 就是指 { p "Hello World" } 。这个和 数组什么的 each 一样。yield 其实可以简单的理解成先抢占一个位置,终于占这个位置为什么,等到需要的时候再决定。yield可以带参数。

这里只是一个无参数的例子,让我们再来看看这个例子:
  1. class Text_Class
  2.   def initialize(n, &b)
  3.     @n, @b = n, b
  4.   end
  5.   def show_message(index)
  6.     p "#{@n} is #{@b.call(index)}"
  7.   end
  8. end
  9. text = Text_Class.new("Text") {|index| index * 2}

  10. text.show_message(1) #=> 2
  11. text.show_message(4) #=> 8
复制代码
稍微复杂一点。一步一步来看。

先注意到 def initialize(n, &b) 里面的参数 &b 。如果参数变量之前带有 & 前缀的话,那么在定义的时候, Ruby会将 block 转换成一个 Proc ,再赋值给 b 这个参数。所以,上面的例子 text.show_message(1) 中, Ruby 会将数字 1 传给 index 变量,然后由声明的时候提供的一个块 {|index| index * 2} 来执行。

block 的用处很灵活,也很实用。最简单的一个实例就是 File 文件的操作,来看下面的例子:

  1. class File   
  2.    def File.open(*args) #将File.new方法需要的一篮子参数放到*args数组里   
  3.      result = f = File.new(*args) #将数组*args分散开并传给File.new , 这里的 *args 表示展开数组,而不是定义变长参数.
  4.      if block_given? #block_given? 方法属于Kernel模块,而此模块包含在Object中,   
  5.         begin #定义一个begin结构,以便即使有错误,也可以执行ensure语句   
  6.            result = yield f # f 传递到块中,result 接受了块的返回值。   
  7.         ensure   
  8.            f.close #不管有没有错误,都会在退出块时,可以自动的关闭文件   
  9.         end #可以确保缓存的东西都写入文件,并且可以交出占用的资源。   
  10.     end   
  11.     return result #如果不存在块,直接返回 result
  12.   end   
  13. end   

  14. File.open("Game.ini", "r") do |file|
  15.   while line = file.gets
  16.     p line
  17.   end
  18. end

复制代码
这个例子就更复杂了点,不过好在有注释。不过这里需要先知道这么几个小知识
1. block_given? 方法
   当某个方法有定义 block 的时候, block_given? 将返回 true。就像之前的例子一样。
2. begin...ensure...end
   异常处理,无论有没有异常发生,程序都会执行 ensure...end 之间的代码。
3. 定义后的 File.open 方法其实有两种行为,当有定义 block 的时候, 将执行 block ,并且退出 block 的时候将自动关闭文件,这样不是很方便么?
   其实上面的例子还可以写成:

  1. file = File.open("Game.ini", "r")
  2.   while line = file.gets
  3.     p line
  4.   end
  5. file.close
复制代码
相比之前的代码,是不是感觉冗余了好多。

点评

yield不就是"交出去"的意思嗎Orz. 簡單來說, 就是把這個位置空出來. 然後 "block" 就是"占據". 經過中文翻譯之後這個簡單概念居然成天書了...  发表于 2010-9-12 08:45
屈从什么的太邪恶了,还是翻译为“让”比较好……其实本来想发一篇关于纤程的,就是 Ruby 的块可以实现二元的合作式多任务模型(也就是协程或纤程的概念)   发表于 2010-9-11 06:52
yield意为屈从,可以理解成将执行权暂时递交给block,block的返回值也可以用于赋值。另外传入实参时也可以用&展开一个proc作为block  发表于 2010-9-11 01:00

《天空之城 —— 破碎的命运》
回复 支持 反对

使用道具 举报

Lv2.观梦者

旅之愚者

梦石
0
星屑
255
在线时间
812 小时
注册时间
2007-7-28
帖子
2148

贵宾

16
发表于 2010-9-10 00:33:34 | 只看该作者
本帖最后由 六祈 于 2010-10-22 21:58 编辑

2010-9-10
咱也来抢一楼,今天聊聊参数的话题吧~

比如def method_a(arg1 , arg2 , ……);end
这样子是最普通的~
method_a有两个参数分别是arg1和arg2,这两个会作为局部变量被传入到方法内部,完成方法并返回后应该会由GC回收

默认参数,是指参数有默认值
形如def method_b(arg1 , arg2 = 3);end
这样调用方法时,如果仅仅传入一个实参,则arg2为默认的3【默认参数需要放在常规参数后面】

剩余参数,指将个数不明的参数作为一个数组的元素传入
形如def method_c(arg1 , *args);end
则使传入的参数中,第一个赋值给arg1,其余的成为一个数组args,以args[0]等方式调用

另外,在方法调用的参数里也可以使用*,那个是将数组展开作为参数,如下:
def method_c2(arg1,arg2,arg3);end;method_c2(*[1 , 2 , 3]);end

关键字参数,指在形参最后用一个哈希表作为关键词参数
例子:def method_d(arg1 , keys = {});end
           而在调用时,可以这样写method_d(1 , {:a => 1 , :b =>2})【哈希作为最后一个参数时,可以省略大括号,称为裸哈希】
           则在方法体内部,就可以用用keys[:a]和keys[:b]分别取得1和2的值

【另外剩余参数和关键词参数不可同时使用,理由自己想吧】


另外再提下块,块也可以是参数,比如数组的迭代方法each,如果自己写的话,如下:
  1. class Array
  2.   def each(&block)
  3.     for i in 0...self.length
  4.       block.call(self[i])
  5.     end
  6.   end
  7. end
复制代码
这里的&指将方法调用时的代码块赋值给block这个变量,然后在方法体内迭代

而在方法调用时,也可以使用&,但意义不同,使用方法如下:
pro = Proc.new{|a| puts a}
[*1..8].each(&pro)
即可完成相当于:
[1,2,3,4……,8].each do |i|
puts i
end
的功能
即在实参中对一个变量前加&可以隐式调用该变量的【to_proc】方法并将之传入方法的代码块参数

点评

回zh大人:zh大人你说的也是正确的。另外愚者说的是在传递实参时,前面加&就会隐式调用一次to_proc方法  发表于 2010-9-26 17:42
to_proc是这功能?不是把一个method或其他代码块转换成Proc的吗  发表于 2010-9-26 17:23
moy
前半部分理解无障碍...块..╮(╯_╰)╭..咱还是回去再翻翻F1好了...  发表于 2010-9-10 01:19
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
15
发表于 2010-9-7 12:28:23 | 只看该作者
2010-09-07 Continuation
Ruby 1.8 里有一个内置类 Continuation,中文直翻是“延续”,这是计算机科学中一个比较底层的概念,它表示进程执行的某一特定时刻的计算过程的实例。通俗的讲,它就是 Ruby 中比 Binding 更加底层的一个能保存当前进程执行环境的物件,就好比游戏里,我们通过存档就能在环境改变之后重新读档,从档案记录的环境,记录的那一刻开始。“延续”一词,即是暗示了从记录的计算过程中的一个点继续执行之意。
Ruby 程序的执行使用了相比于堆要更高效的栈结构来进行函数调用和局部变量的保存,而 Continuation 正是保存了进程运行在某一时刻时的栈内存状态,之后用户可以手动地去调用这个 Continuation 对象,让程序从这一个预先保存的记录点运行。这相当于不少语言中支持的 goto 语句跳转的一个超级版,因为 goto 只能进行局部跳转,而 Continuation 能上天入地,翻江倒海,一个筋斗十万八千里(实际上在 C 标准库中,Continuation 的功能是等同于 setjmp 和 longjmp 这两个函数所提供的功能的)。
有什么用?RM 有一个快捷键为 F12 的重置游戏的功能,当触发了这个命令时,RM 会让解释器从头开始重新执行脚本编辑器里的脚本,但并没有初始化符号表、GC 和堆结构。这使得原有的全局变量、常量、模块、类得以保存,但也可能会让我们与我们的老朋友——stack level too deep——再度重逢。由于脚本中大量使用 alias 别名方法,当 F12 按下后,没有进行判断的 alias 语句再次执行,就会导致两次同名一个方法后产生的经典 BUG(关于这个问题,这里不再赘述,论坛上已经有太多相关的帖子,请自行搜索 stack level too deep)。这个问题可以通过 method_defined? 等方法判断将要 alias 的方法是否已经定义来解决(alias 一次后方法就已定义了,第二次不会再定义),但并不是所有脚本编写者都有这个习惯。这里我们介绍一种使用 Continuation 的通用解法:
在 Main 脚本里的第一行插入一句:
  1. callcc{ |$__f12_no_reeval| }
复制代码
这一句脚本让 RM 在执行到 Main 脚本时将一个保存了当前执行环境的 Continuation 对象赋给了 $__f12_no_reeval 这个全局变量;接着在脚本编辑器开头插入:
  1. $__f12_no_reeval and $__f12_no_reeval.call
复制代码
这句是先判断 $__f12_no_reeval 是否已初始化(即已经执行到 Main 脚本一次以上),如果是则调用这个 Continuation,让程序直接跳转到 Main 脚本开头的环境继续执行,避免了从开头到 Main 脚本之间的所有脚本的重新执行。由于产生冲突的、包含 alias 的脚本在这些脚本之中,保证只执行它们一次就从根本杜绝了这个问题的滋生。

关于 Continuation 的额外知识:
在 Ruby 1.8 中,Continuation 的实现模型是与线程的实现模型一致的——线程调度器在进行绿色线程切换的时候,会进行类似 Continuation 的环境跳转。在 Ruby 1.9 中,由于 YARV 虚拟机采用的是本地线程,每个线程拥有自己的私有栈,他们之间就产生了区别。有趣的是,在 Ruby 1.9 中又引入了一个新的和 Continuation 类似的概念,那就是我们的 Fiber(纤程),一种轻量级的线程,它基本可以被看作是 Continuation 的替代品,同时由于效率比 Continuation 高(Continuation 保存栈内存状态的过程开销是巨大的),Ruby 社区就是否保留 Continuation 进行了激烈的辩论,就 1.9 而言,Continuation 仍然被保留下来了。下一次我们就来看看这个 Ruby 1.9 中的新东西在 RM 使用的 Ruby 1.8 中如何局限性地使用。

点评

嘛嘛嘛,还是大点的好。。。  发表于 2010-9-26 17:18
老板,莫每次都扔多大的一个,tips还是要简单、适用而且比较面向新人向的好 TAT  发表于 2010-9-7 22:37
回复 支持 反对

使用道具 举报

头像被屏蔽

Lv1.梦旅人 (禁止发言)

梦石
0
星屑
50
在线时间
3 小时
注册时间
2010-6-19
帖子
40
14
发表于 2010-9-5 13:47:40 | 只看该作者
提示: 作者被禁止或删除 内容自动屏蔽
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
13
发表于 2010-9-5 11:01:40 | 只看该作者
本帖最后由 紫苏 于 2010-9-5 11:08 编辑

2010-09-05 绑定 Binding

在计算机科学中,“绑定”一词是指一个更复杂、更大型的物件的引用的创建。例如当我们编写了一个函数,这个函数名就绑定了该函数本体,我们可以通过函数名来引用并调用该函数,这被称为名称绑定;又如当 Ruby 通过 API 去调用了 C 语言写的库函数时,这就是一个语言绑定;再如面向对象语言中的方法调度 obj.method,这也是一个名称绑定,它会根据接收者 obj 具体的对象类型来确定应该引用哪个对象类型的 method 方法,而如果 obj 在编译时就能确定,那便可称之为静态绑定(早绑定),早期的静态类型语言(如 C)使用的是早绑定;如果 obj 在运行时才能确定,那便可称为动态绑定(迟绑定),动态类型语言(如 Ruby)使用的是迟绑定,而有些语言则同时支持早绑定和迟绑定,如 C++ 的虚函数使用迟绑定,普通函数则使用早绑定。
在 Ruby 中,Kernel 有一个方法 binding,它返回一个 Binding 类型的对象。这个 Binding 对象就是我们这里说的绑定,它封装了当前执行上下文中的所有绑定(变量、方法、语句块、self 的名称绑定),而这些绑定直接决定了面向对象语言中的执行环境。比如,当我们调用 p 时,实际上是进行了 self 和 p 的绑定,而 p 具体是哪个方法,是由 self 的类型来决定的,如果我们在顶层,而 Kernel#p 又没有被重写,那 p 就是一个用来显示对象细节的方法。可以说有了一个绑定的列表,我们就有了一个完整的面向对象上下文的拷贝,就好比上帝在 12 分 37 秒复制了一份世界,而这个世界与原本世界的环境一模一样,既有这朵花,又有那株草。Ruby 的 Binding 对象的概念和 Continuation 有共通之处,但 Continuation 主要用于实际堆、栈内存的环境跳转,而 Binding 则比较高层。
这个 Binding 对象有什么用?主要是用于 eval 这个函数。eval 的第一个参数是需要 eval 的一段脚本字符串,而第二个可选参数则接受一个 Binding 对象。当指定了 Binding 时,eval 会在传递给它的 Binding 所封装的执行环境里执行脚本,否则是在调用者的执行环境里执行。我们可以通过这个机制来进行一些不同上下文之间的通信,或者是在一个上下文即将被销毁之前保存该上下文环境以留他用,如:
  1. def foo
  2.   bar = 'baz'
  3.   return binding
  4. end

  5. eval('p bar', foo)
复制代码
这里我们通过 foo 返回的 Binding 获取到了局部上下文销毁前的局部变量 bar 的值,而在不使用 binding 的情况下,局部变量 bar 在 foo 外层是不可见的。
最后,Ruby 有一个预定义的常量:TOPLEVEL_BINDING,它指向一个封装了顶层绑定的对象,通过它我们可以在其它上下文中通过 eval 在顶层上下文环境中执行脚本。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-11-2 22:24

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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