块和数组

块(Blocks)通常用于迭代数组(Array)。 因此,Array 类提供了许多可以传递块的方法。

一个有用的方法 collect;会将数组的每个元素传递给一个 block,并创建一个新数组以包含该 block 返回的每个值。例如,这里的一个 block 会被传入一个数组中的每个整数(每个整数被分配给变量 x),它将其值加倍并返回它。

collect 方法会创建一个包含了每个按顺序返回的整数的新数组:

2blocks.rb
  1. b3 = [1,2,3].collect{|x| x*2}

上面的示例返回这个数组:[2,4,6]

在下一个示例中,block 返回原始字符串每个首字母大写的一个版本:

  1. b4 = ["hello","good day","how do you do"].collect{|x| x.capitalize }

所以 b4 现在是…

  1. ["Hello", "Good day", "How do you do"]

Array 类的 each 方法看起来与 collect 类似;它也依次传递每个数组元素以由 block 处理。但是,与 collect 不同,each 方法不会创建包含返回值的新数组:

  1. b5 = ["hello","good day","how do you do"].each{|x| x.capitalize }

这一次,b5 没有变化…

  1. ["hello", "good day", "how do you do"]

回想一下,有些方法 - 特别是以感叹号(!)结尾的方法 - 实际上改变了原始对象而不是产生新值。如果你想使用 each 方法来将原始数组中的字符串首字母大写,你可以使用 capitalize! 方法:

  1. b6 = ["hello","good day","how do you do"].each{|x| x.capitalize! }

所以 b6 现在是…

  1. ["Hello", "Good day", "How do you do"]

经过一番思考,你还可以使用 block 来迭代字符串中的字符(characters)。首先,你需要从字符串中拆分每个字符。这可以使用 String 类的 split 方法完成,如下所示:

  1. "hello world".split(//)

split 方法基于分隔符将字符串划分为子字符串,并返回包含这些子字符串的数组。这里 // 是一个定义零长度字符串的正则表达式;这具有返回单个字符的效果,因此我们最终创建了包含字符串中所有字符的数组。我们现在可以迭代这个字符数组,返回每个字符的大写版本:

  1. a = "hello world".split(//).each{ |x| newstr << x.capitalize }

因此,在每次迭代时,大写字符将附加到 newstr,并显示以下内容…

  1. H
  2. HE
  3. HEL
  4. HELL
  5. HELLO
  6. HELLO
  7. HELLO W
  8. HELLO WO
  9. HELLO WOR
  10. HELLO WORL
  11. HELLO WORLD

因为我们在这里使用 capitalize 方法(结尾没有 ! 字符),所以数组中的字符 a 保持原样,全部小写,因为 capitalize 方法不会改变接收对象(这里接收对象是传入 block 的字符)。

但请注意,如果你使用 capitalize! 方法修改原始字符,此代码将不会运行。这是因为 capitalize! 如果没有进行任何更改则会返回 nil,因此当遇到空格字符时,将返回 nil,并且我们尝试向字符串 newstr 追加(<<)一个 nil 值将会失败。

你还可以使用 each_byte 方法对字符串进行首字母大写转换。这将遍历字符串的字符,将每个字节(byte)传递给 block。这些字节采用了 ASCII 码的形式。因此,”hello world” 将以这些数值的形式传递:104 101 108 108 111 32 119 111 114 108 100

显然,你不能将整数大写,所以我们需要将每个 ASCII 值转换为一个字符。String 的 chr 方法执行此操作:

  1. a = "hello world".each_byte{|x| newstr << (x.chr).capitalize }