Ruby 控制流用法详解

作者:袖梨 2022-06-25

之前我们见过方法的调用,它会让程序从调用方法的地方跳到方法的定义主体那里。程序并不是按直线运行的,执行的顺序会受到一些规则还有一些被称为控制流的程序设计结构的影响。

Ruby 的控制流有下面这些:

条件执行(Conditional execution)— 根据表达式的真实性执行。
循环(Looping)— 一块儿重复执行的代码。
迭代(Iteration)— 调用方法的时候给它提供一块儿代码,方法在它执行的时候可以多次调用这块代码。
异常(Exceptions)— 处理异常情况。
按条件执行代码

Ruby 提供了一些基于条件控制程序流的方法。有两大类,if 还有 case 。

if 跟它的朋友们

if 用起来像这样:

if condition
# condition 如果是 true 就执行这里的代码
end
if 可以嵌套使用:

if condition
# condition 如果是 true 就执行这里的代码
if condition2
# condition2 如果是 true 就执行这里的代码
end
end
if 可以放到一行用,在条件的后面使用关键词 then,像这样:

if x > 10 then puts x end
或者使用分号:

if x > 10; puts x; end
条件执行经常关系到多个分支,你可能想在条件成功的时候做点事,失败的时候做另一些事儿。可以使用 else 或 elsif。用法是这样的:

if condition
# condition 是 true 就执行这里的代码
else
# condition 是 false 就执行这里的代码
end
加上 elsif 可以继续添加要判断的条件:

if condition1
# condition1 是 true 就执行这里的代码
elsif condition2
# condition1 是 false
# condition2 是 true 就执行这里的代码
elsif condition3
# condition1 与 condition2 都是 false
# condition3 是 true 就会执行这里的代码
end
做个实验:

print "输入一个整数:"
n = gets.to_i

if n > 0
puts "你输入的是正数"
elsif n puts "你输入的是负数"
else
puts "你输入的数字是 0"
end
再试一下否定形式的条件,可以使用 not 与 !,先试一下 not:

if not (x == 1)
再试一下 ! 号:

if !(x == 1)
unless 跟 if not 与 if ! 的效果一样,试一下 unless:

unless x ==1
19:59 **

21:15 **

if 还可以这样用:

puts 'big number!' if x > 100
上面的代码可以解释为:

if x > 100
puts 'big number!'
end
unless 也可以这样用:

puts 'big number!' unless x 如果一个 if 声明成功了,整个声明的值会用成功的那个分支来表示,做个实验:

x = 1

if x '负数'
elsif x > 0
'正数'
else
'零'
end
irb 会告诉你上面的 if 整个声明的值会是字符串 '正数' 。如果 if 声明没成功,会返回 nil 。

21:37 **

条件主体里的分配

2016年9月9日 上午8:52 ***

分配语法与条件表达式在两个点有交叉,在条件表达式的主体里,分配有可能发生也可能不会发生,还有就是在条件测试上:

if x = 1
y = 2
end
x = 1 是条件测试里的分配,y = 2 是条件主体里的分配。

条件主体里的分配

编译语言有编译时,还有运行时这两个说法。Ruby 是脚本语言,在运行脚本之前解释器会解析代码,这时候会做一些决定, 比如去识别与配置本地变量。

Ruby 解释器看到这样形式:识别符,等号,值,比如像这样:

x = 1
解释器会给 x 这个本地变量分配一个空间。

来看这个例子:

if false
x = 1
end

p x
p y
上面的例子里,分配 x 的动作并没有被执行,因为它是在一个失败的条件测试里包装着。不过 Ruby 的解释器看到了 x = 1,推断程序可能会用到本地变量 x ,解释器不在乎 x 是不是得到了一个值。它会给 x 一个 nil 值,所以输出 x ,得到的结果是 nil ,但是你输出一个不存在的本地变量的时候,就会出现严重错误。

条件测试里的分配

看个例子:

if x = 1
puts 'hi'
end
上面在条件测试里的东西是一个分配(x = 1),注意它并不是去比较是否相等,像 x == 1 。在条件测试里的分配跟在其它的地方分配是一样的,就是 x 的值设置成了 1。测试本身会认为条件是 if 1 ,也就是 true,这样条件主体里的东西会被执行,就会输出一个 hi 。

同时你会看到一个警告:

warning: found = in conditional, should be ==
Ruby 会认为要测试的 if x =1 会一直成功,条件主体也就会一直被执行,所以 Ruby 会认为你可能是输错了,就会给你个警告提示一下,警告会建议你使用 == 操作符,它可以测试两个值是否相等。

Ruby 知道这样做不是一个错误:

if x = y
跟 x = 1 不一样,x = y 作为条件测试不一定会成功。

上午9:37 ***

case

上午9:51 **

case 声明用一个表达式开始,一般就是一个单独的对象或者变量。然后是一些可能的匹配,每个可能的匹配都会包含在一个 when 声明里,它里面还有一块儿代码。最终只会有一个匹配胜出,然后就会执行这个 when 里面的代码。

了解 case 的使用,先来看个例子:

print '是否退出?(yes/no):'
answer = gets.chomp

case answer
when 'yes'
puts '再见!'
exit
when 'no'
puts '我们继续!'
else
puts '不知道您在说啥,假设是 no'
end

puts '继续程序 ... '
一个 case 声明,使用 case 这个关键词开始,然后就是一个 when 区块,还有一个可选的 else ,结束 case 要使用一个 end 关键词。只有一个匹配会最终胜出,然后去执行它里面的代码。上面的例子里,如果输入的是 “yes”,程序就会退出。如果是 “no" 或其它的值,会让程序可以继续运行。

在一个 when 里面,你可以添加多个匹配:

case answer
when 'y', 'yes'
puts '再见!'
exit
# etc
上面的例子,我们在 when 里面使用逗号分隔开了一些可能的匹配,它有点像是 或 操作符。上面的意思是如果输入是 “y” 或 “yes” 就输出 “再见” 并退出程序。

when 是怎么一回事儿

每个 Ruby 对象都有一个 case equality 方法,名字是 === ,调用 === 的结果就是判定一个 when 是不是被匹配了。

上面的例子,我们可以这样重写一下:

if 'yes' === answer
puts '再见!'
exit
elsif 'no' === answer
puts '我们继续!'
else
puts '不知道您在说啥,假设是 no'
end
站在中缀操作符(infix operator)的角度看 === ,它其实就是方法的 Syntactic sugar:

if 'yes' .===(answer)
再看一下为什么当 answer 包含 yes 的时候会返回 true:

'yes' === answer
调用三等(===)方法会返回 true,是因为三等方法是为字符串定义的。当你问一个字符串它自己是不是三等另一个字符串(string1 === string2),你问的其实就是字符串自己的内容,一个一个的跟另一个字符串去比较,如果匹配,就会返回 true,不然就会返回 false。

每个类都可以定义自己的 === 方法,可以用自己的 case-equality 逻辑。

case/when 就是 object === other_object 的伪装,object === other_object 其实是 object. === (other_object) 的伪装。

对象 case 行为

class Ticket
attr_accessor :venue, :date
def initialize(venue, date)
self.venue = venue
self.date = date
end

def ===(other_ticket)
self.venue === other_ticket.venue
end
end

ticket1 = Ticket.new('Town Hall', "07/08/13")
ticket2 = Ticket.new('Conference Center', "07/08/13")
ticket3 = Ticket.new('Town Hall', "08/09/13")

puts "ticket1 的位置是: #{ticket1.venue}"

case ticket1
when ticket2
puts '跟 ticket2 在同一个位置'
when ticket3
puts '跟 ticket3 在同一个位置'
else
puts '没有匹配'
end
执行的结果是:

ticket1 的位置是: Town Hall
跟 ticket3 在同一个位置
返回值

如果在 when/else 从句里有成功的,case 声明返回的值就是执行这个双句里的代码的结果。如果要匹配的全都失败了,整个声明就会返回 nil 。

上午10:40 ***

Loops

上午11:02 ***

无条件循环

使用 loop,像这样:

loop codeblock
代码块有两种形式,一种是使用花括号({}),一种使用 do 还有 end 操作符。下面的例子演示了这两种形式:

loop { puts 'looping forever!' }

loop do
puts 'looping forever!'
end
你不能让循环永远继续下去,你需要让它在某个点上停下来。

控制循环

使用 break 关键词:

n = 1
loop do
n = n + 1
break if n > 9
end
使用 next 跳过当前的循环:

n = 1
loop do
n = n + 1
next unless n == 10
break
end
有条件循环

有条件的循环用的关键词是 while 与 until 。

while

一个条件如果是 true 就去循环。用 while 开始,end 结束,在它们之间的代码就是每次循环要执行的东西:

n = 1
while n puts n
n = n + 1
end
puts '完成!'
上面这块代码输出的是:

1
2
3
4
5
6
7
8
9
10
完成!
n

while 也可以放到循环的结束,要配合使用 begin/end 关键词:

n = 1
begin
puts n
n = n + 1
end while n puts '完成!'
while 放在开始与结尾有点区别,如果你把 while 放在开始的地方,条件如果是 false ,循环一次都不会被执行。但是如果你把 while 放在结尾,如果 while 条件是 false,循环会被执行一次。

until

n = 1
until n > 10
puts n
n = n + 1
end
循环的主体会被执行,直到条件是 true。

while 与 until 修饰符

使用 until:

n = 1
n = n + 1 until n == 10
puts '数到 10 了!'
也可以使用 while:

n = 1
n = n + 1 while n puts '数到 10 了!'
做个实验:

a = 1
a += 1 until true
a 的值仍然是 1 ,因为 true 已经是 true 。

再做个实验:

a = 1
begin
a += 1
end until true
在 begin 与 end 中间的代码会被执行一次。

循环一个列表的值

做个实验:

numbers = [1, 2, 3]
for n in numbers
puts n
end
执行的结果是:

1
2
3
上午11:30 ***

迭代

下午6:51 **

迭代器的原料

在一个对象上调用方法,控制会传递给方法的主体(一个不同的作用域),方法执行完以后,控制会返回给调用方法的那个地方之后的点上。再来认识两个新的结构,代码块(code block) 还有关键词 yield 。

之前我们看见过这样一个例子:

loop { puts 'looping forever!' }
上面的代码会一直输出那个信息,想一下,这到底发生了什么?在 loop 里为什么会执行 puts 。答案是:loop 是一个迭代器。迭代器是一个 Ruby 方法,调用这种方法的时候需要加点特别的料,它会期望你给它提供一个代码块。一个大括号就会划分出来一个代码块。

loop 方法可以访问代码块里的代码,就是方法可以调用或者叫执行这个代码块。想让自己的迭代器可以做这件事,你就可以使用关键词 yield。代码块与 yield 就是一个迭代器的主料。

自制的迭代器

写一个自己的 loop:

def my_loop
while true
yield
end
end
或者再简单点:

def my_loop
yield while true
end
调用它:

my_loop { puts 'my-looping forever!' }
为 my_loop 提供一个代码块,方法可以把控制让给它,当方法把控制让给代码块以后,代码块里的代码会被运行,完成以后控制又会交还给方法。

剖析方法调用

Ruby 里的方法调用有几种形式:

接收者对象或变量

方法名
参数列表(可选,默认是( ) )
代码块(可选,无默认)
注意参数列表与代码块分别是两种不同的调用方法的形式。下面这些都是合法的 Ruby 方法调用:

loop { puts 'hi' }
loop() { puts 'hi' }
string.scan(/[^,]+/)
string.scan(/[^,]+/) {|word| puts word}
调用方法时使用代码块与不使用代码块的区别是,方法是否可以 yield。如果有代码块,方法就可以 yield,如果没有,就不能,因为没有可以 yield 的东西。

有些方法不管你是不是给它提供一个代码块,它都会去做一些事情。比如说 String#split,这个方法会根据你传递给它的分割符,去分离它的字符串接收者,然后把分离出来的元素放到一个数组里。但是如果你传递给这个方法一个代码块,split 会把分离的项目 yield 给一个代码块,在代码块里你可以随便处理每个子字符,可以输出它,可以把它放到数据库里等等。

大括号与 do/end 代码块

do/end ,还有大括号,使用这两种形式都可以划分出一个代码块。先做个实验:

>> array = [1,2,3]
=> [1, 2, 3]
>> array.map {|n| n * 10}
=> [10, 20, 30]
>> array.map do |n| n * 10 end
=> [10, 20, 30]
>> puts array.map {|n| n * 10}
10
20
30
=> nil
>> puts array.map do |n| n * 10 end
#<0x007ff60922ccf0>




































“”




























































































































































































相关文章

精彩推荐