条件判断

条件判断


  • https://www.kancloud.cn/imxieke/ruby-base/107292


什么是条件判断

接下来,我们来考虑一下如何将公历转换为平成纪年。首先,我们将输入的字符串转换为数值后减去 1988,最后输出运算结果,结束程序。

1989 年为平成元年,2014 年是平成 26 年。

# 将公历转换为平成纪年

ad = ARGV[0].to_i
heisei = ad - 1988
puts heisei
> ruby ad2heisei.rb 2013
25

但是,这个程序有点小问题。如果我们输入 1989 年以前的年份,返回值会变成 0 或者负数。

按道理,1989 年以前的年份是不能转换为平成 XX 年的,因此程序本不应允许输入示例中那样的年份。我们将程序稍微改进一下,若输入 1989 年以前的年份,程序则返回“无法转换”的提示。

在这样的情况下,为了实现程序在“某个条件时执行○○处理,否则执行 ×× 处理”,Ruby 为我们准备了条件判断语句。

条件判断语句主要有以下三种。

  • if 语句

  • unless 语句

  • case 语句

Ruby 中的条件

条件与真假值

我们在之前的章节已经介绍过了在条件判断中常用到的比较运算符。等号 ==,不等号 >< 等都是比较运算符。

比较的结果分为 truefalse 两种。顾名思义,比较结果正确时为 true ,错误时为 false

除了比较运算符外,Ruby 中还有很多可以作为条件判断的方法。例如,字符串的 empty? 方法,该字符串的长度为 0 时返回 true,否则返回 false

p "".empty?    #=> true
p "AAA".empty? #=> false

另外,除了 truefalse 外,还有其他值可作为条件判断的值。例如,用正则表达式进行匹配时,匹配成功返回该字符串的位置,匹配失败返回 nil

p /Ruby/ =~ "Ruby"    #=> 0
p /Ruby/ =~ "Diamond" #=> nil
  • 真 : false 、 nil 以外的所有对象

  • 假 : false 、 nil

也就是说,Ruby 会认为 falsenil 代表假,除此以外的所有值都代表真。因此,Ruby 中的真 / 假并非绝对等同于 true/falsetrue 代表真,false 代表假,同时,不返回 truefalse 的方法只要能返回 nil,也可作为条件判断的表达式来使用。另外,在 Ruby 中还有个约定俗成的规则,为了使程序更容易理解,返回真假值的方法都要以 ? 结尾。建议大家在写程序时也遵守这个规则。

逻辑运算符

在判断多个条件表达式时,我们会用到逻辑运算符 &&||

条件 1 && 条件 2

表示条件 1 为真,并且条件 2 也为真时,则整体的表达式返回真。两者中只要一个返回假时,则整体的表达式返回假。

相对地,

条件 1 || 条件 2

表示条件 1 为真,或者条件 2 为真时,整体的表达式返回真。两者同时为假时,则整体的表达式返回假。

还有表示否定的逻辑运算符:

!条件

表示相反的条件。也就是,条件为假时,表达式返回真;条件为真时,表达式返回假。例如,我们想判断整数 x 是否在 1 到 10 之间,if 语句可以这么写:

if x >= 1 && x <= 10

end

与上面的条件相反,表示“1 到 10 以外”时使用!,表达式可以写成 !(x >= 1 && x <= 10)。不过,像下面写成“小于 1,或者大于 10”可能更加直接,更便于理解。

if x < 1 || x > 10

end

条件判断对于控制程序的行为非常重要。过于复杂、难以理解的条件,会使程序的目的也会变得难以琢磨。建议大家在写程序时,注意尽量写便于理解的条件。

在 Ruby 中,还有与 &&||! 意思相同,但优先级略低的逻辑运算符 and、or、not。

if 语句

接下来,我们就来看看条件判断语句到底如何使用。if 语句是最基本的条件判断语句,用法如下:

if 条件 then
 处理
end

可以省略 then

在这基础上可再加上 elsifelse

if 条件 1 then
 处理 1
elsif 条件 2 then
 处理 2
elsif 条件 3 then
 处理 3
else
 处理 4
end

可以省略 then

Ruby 会按照从上到下的顺序进行判断。首先,条件 1 为真时程序执行处理 1。条件 1 为假时,程序再判断条件 2,若为真时执行处理 2。同样地,条件 2 为假时,程序再判断条件 3……本例中虽然只有 4 个条件分支,但根据实际需要可以添加无限个的分支。最后,如果前面所有条件都为假时则执行处理 4。

来看看使用 elsif 的例子

a = 10
b = 20
if a > b
  puts "a 比b 大"
elsif a < b
  puts "a 比b 小"
else
  puts "a 与b 相等"
end

这是一个比较 a、b 大小的程序。比较结果分为 a 比 b 大、a 比 b 小或者 a 与 b 相等三种情况。这种情况下,我们可以使用 if ~ elsif ~ else 结构。

unless 语句

unless 语句的用法刚好与 if 语句相反。unless 语句的用法如下:

unless 条件 then
 处理
end

可以省略 then

unless 语句的形式和 if 语句一样。但 if 语句是条件为真时执行处理,unless 语句则刚好相反,条件为假时执行处理。

a = 10
b = 20
unless a > b
  puts "a 不比b 大"
en

这个程序执行后输出“a 不比 b 大”。unless 语句的条件 a > b 为假,所以程序执行了 puts 方法。

unless 语句也可以使用 else

unless 条件
 处理 1
else
 处理 2
end

这个与下面的 if 语句是等价的。

if 条件
 处理 2
else
 处理 1
end

对比以上两种写法,我们可以知道处理 1 和处理 2 的位置互换了,if 语句通过这样的互换,能达到与使用 unless 语句时同样的效果。

case 语句

条件有多个时,使用 ifelsif 的组合虽然也能达到判断多个条件的效果,但是如果需要比较的对象只有一个,根据这个对象值的不同,执行不同的处理时,使用 case 语句会使程序更简单,更便于理解。

case 语句的用法如下:

case 比较对象
when 值 1 then
 处理 1
when 值 2 then
 处理 2
when 值 3 then
 处理 3
else
 处理 4
end

可以省略 then

本例的比较对象的值有 3 个,但根据实际情况可以无限增加下去。

还有,when 可以一次指定多个值。下面的示例从数组 tags 的开头依次取出元素,判断元素值,输出相应的结果。

tags = [ "A", "IMG", "PRE" ]
tags.each do |tagname|
  case tagname
  when "P","A","I","B","BLOCKQUOTE"
    puts "#{tagname} has child."
  when "IMG", "BR"
    puts "#{tagname} has no child."
  else
    puts "#{tagname} cannot be used."
  end
end

执行示例

> ruby case.rb
A has child.
IMG has no child.
PRE cannot be used.

我们再来看看其他例子

array = [ "a", 1, nil ]
array.each do |item|
  case item
  when String
    puts "item is a String."
  when Numeric
    puts "item is a Numeric."
  else
    puts "item is something."
  end
end

执行示例

> ruby case_class.rb
item is a String.
item is a Numeric.
item is something.

在本例中,程序判断传过来的对象类型是字符串(String 类)还是数值(Numeric 类),或者均不是以上两者,然后再输出相应的结果。

在这里,我们同样是使用 case 语句,不过判断的主体与之前的例子有点区别。本例中的 when 实际并不是直接判断传过来的字符串,而是先查找该对象属于哪个类,然后再根据这个类的信息来进行条件判断。

我们还可以根据正则表达式的匹配结果进行不同处理。下面是使用正则表达式做判断的 case 语句的例子。

text.each_line do |line|
  case line
  when /^From:/i
    puts "发现寄信人信息"
  when /^To:/i
    puts "发现收信人信息"
  when /^Subject:/i
    puts "发现主题信息"
  when /^$/
    puts "头部解析完毕"
    exit
  else
    ## 跳出处理
  end
end

这是一个解析电子邮件头部的程序。为了简化程序,我们并没有考虑有多个头部的情况,而且电子邮件里的内容我们也没取出来。在这里,大家掌握程序的大概的处理流程就可以了。

each_line 方法逐行读取电子邮件正文数据 text,并将每行的内容赋值给变量 line。这个是处理文件、文本数据时的典型的写法。

接着 case 语句判断得到的字符串的内容,执行不同的处理。以 From: 开头时输出“发现寄信人信息”,以 To: 开头时输出“发现收信人信息”,以 Subject: 开头时输出“发现主题信息”。

最后的 when 判断的 /^$/,表示行的开头后马上就接着是行尾的意思 3,也就是说,这是表示空行的正则表达式。电子邮件的头部和正文间一定会以空行作间隔,因此根据这个规则我们就可以把空行作为头部结束的标志。当 when 遇到空行,输出“头部解析完毕”的信息后调用 exit 方法,结束程序。

在正则表达式中,^ 表示匹配字符串的开始,$ 表示匹配字符串的结束。

=== 与 case 语句

case 语句中,when 判断值是否相等时,实际是使用 === 运算符来判断的。左边是数值或者字符串时,===== 的意义是一样的,除此以外,=== 还可以与 =~ 一样用来判断正则表达式是否匹配,或者判断右边的对象是否属于左边的类,等等。对比单纯的判断两边的值是否相等,=== 能表达更加广义的“相等”。

p (/zz/ === "xyzzy")    #=> true
p (String === "xyzzy")  #=> true
p ((1..3) === 2)        #=> true

用 if 语句改写 case 语句的程序如下所示。请注意 when 指定的对象在===h 的左边。

if 修饰符与 unless 修饰符

if 与 unless 可以写在希望执行的代码的后面。像下面这样:

puts "a 比b 大" if a > b

这与下面的写法是等价的。

if a > b
  puts "a 比b 大"
end

使用修饰符的写法会使程序更加紧凑。通常,我们在希望强调代码执行的内容时会使用修饰符写法。同样地,在使用修饰符写法时,请大家注意程序的易读性。

对象的同一性

所有的对象都有标识和值。

标识(ID)用来表示对象同一性。Ruby 中所有对象都是唯一的,对象的 ID 可以通过 object_id(或者 __id__)方法取得。

ary1 = []
ary2 = []
p ary1.object_id    #=> 67653636
p ary2.object_id    #=> 67650432

我们用 equal? 方法判断两个对象是否同一个对象(ID 是否相同)。

str1 = "foo"
str2 = str1
str3 = "f" + "o" + "o"
p str1.equal?(str2)    #=> true
p str1.equal?(str3)    #=> false

对象的“值”就是对象拥有的信息。例如,只要对象的字符串内容相等,Ruby 就会认为对象的值相等。Ruby 使用 == 来判断对象的值是否相等。

str1 = "foo"
str2 = "f" + "o" + "o"
p str1 == str2    #=> true

除了 == 以外,Ruby 还提供 eql? 方法用来判断对象的值是否相等。==eql? 都是 Object 类定义的方法,大部分情况下它们的执行结果都是一样的。但也有例外,数值类会重定义 eql? 方法,因此执行后有不一样结果。

p 1.0 == 1      #=> true
p 1.0.eql?(1)    #=> false

凭直觉来讲,把 1.01 判断为相同的值会更加方便。在一般情况进行值的比较时使用 ==,但是在一些需要进行更严谨的比较的程序中,就需要用到 eql? 方法。例如,00.0 作为散列的键时,会判断为不同的键,这是由于散列对象内部的键比较使用了 eql? 方法来判断。

hash = { 0 => "0"}
p hash[0.0]    #=> nil
p hash[0]      #=> "0"