🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# **第 5 章 条件判断** 本章我们将详细讨论一下控制结构之一的条件判断,主要包括以下内容。 - **什么是条件判断。** - **条件判断中不可或缺的比较运算符、真假值 1、逻辑运算符。** - **条件判断的种类及其写法和使用方法。** 1也称布尔值。——译者注 ### **5.1 什么是条件判断** 接下来,我们来考虑一下如何将公历转换为平成纪年 2。首先,我们将输入的字符串转换为数值后减去 1988,最后输出运算结果,结束程序。程序如代码清单 5.1 所示。 2日本的纪年方法。1989 年为平成元年,2014 年是平成 26 年。——译者注 **代码清单 5.1 ad2heisei.rb** ~~~ # 将公历转换为平成纪年 ad = ARGV[0].to_i heisei = ad - 1988 puts heisei ~~~ 执行结果如下: > **执行示例** ~~~ > ruby ad2heisei.rb 2013 25 ~~~ 但是,这个程序有点小问题。如果我们输入 1989 年以前的年份,返回值会变成 0 或者负数。 > **执行示例** ~~~ -8 ~~~ 按道理,1989 年以前的年份是不能转换为平成 XX 年的,因此程序本不应允许输入示例中那样的年份。我们将程序稍微改进一下,若输入 1989 年以前的年份,程序则返回“无法转换”的提示。 在这样的情况下,为了实现程序在“某个条件时执行○○处理,否则执行 ×× 处理”,Ruby 为我们准备了条件判断语句。 条件判断语句主要有以下三种。 - **`if` 语句** - **`unless` 语句** - **`case` 语句** 接下来,我们将会介绍这些条件判断语句及其写法。 ### **5.2 Ruby 中的条件** 在说明条件语句之前,我们首先来看看在 Ruby 中是如何写条件的。 ### **条件与真假值** 我们在之前的章节已经介绍过了在条件判断中常用到的比较运算符。等号`==`,不等号`>`、`<` 等都是比较运算符。 比较的结果分为 `true` 和 `false` 两种。顾名思义,比较结果正确时为 `true`,错误时为 `false`。 除了比较运算符外,Ruby 中还有很多可以作为条件判断的方法。例如,字符串的 `empty?` 方法,该字符串的长度为 0 时返回 `true`,否则返回 `false`。 ~~~ p "".empty? #=> true p "AAA".empty? #=> false ~~~ 另外,除了 `true` 和 `false` 外,还有其他值可作为条件判断的值。例如,用正则表达式进行匹配时,匹配成功返回该字符串的位置,匹配失败返回 `nil`。 ~~~ p /Ruby/ =~ "Ruby" #=> 0 p /Ruby/ =~ "Diamond" #=> nil ~~~ 关于 Ruby 中的真假值的定义,可参考表 5.1。 **表 5.1 Ruby 的真假值** | **真** | `false` 、 `nil` 以外的所有对象 | |-----|-----| | **假** | `false` 、 `nil` | 也就是说,Ruby 会认为 `false` 与 `nil` 代表假,除此以外的所有值都代表真。因此,Ruby 中的真 / 假并非绝对等同于 `true/false`。`true` 代表真,`false` 代表假,同时,不返回 `true` 或 `false` 的方法只要能返回 `nil`,也可作为条件判断的表达式来使用。另外,在 Ruby 中还有个约定俗成的规则,为了使程序更容易理解,返回真假值的方法都要以 `?` 结尾。建议大家在写程序时也遵守这个规则。 ### **5.3 逻辑运算符** 在判断多个条件表达式时,我们会用到逻辑运算符 `&&` 和 `||`。 **条件 `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`。关于运算符的优先级,我们将在第 9 章 9.5 节讨论。 ### **5.4 if 语句** 接下来,我们就来看看条件判断语句到底如何使用。`if` 语句是最基本的条件判断语句,用法如下: **`if` 条件 `then`  处理 `end`** ※ 可以省略 `then` ![{%}](https://box.kancloud.cn/2015-10-26_562e01db79df9.png) 在这基础上可再加上 `elsif`、`else` : **`if` 条件 `1 then`  处理 `1` `elsif` 条件 `2 then`  处理 `2` `elsif` 条件 `3 then`  处理 `3` `else`  处理 `4` `end`** ※ 可以省略 `then` ![{%}](https://box.kancloud.cn/2015-10-26_562e01db89eca.png) Ruby 会按照从上到下的顺序进行判断。首先,条件 `1` 为真时程序执行处理 `1`。条件 `1` 为假时,程序再判断条件 `2`,若为真时执行处理 `2`。同样地,条件 `2` 为假时,程序再判断条件 `3……`本例中虽然只有 `4` 个条件分支,但根据实际需要可以添加无限个的分支。最后,如果前面所有条件都为假时则执行处理 4。 我们来看看使用 `elsif` 的例子(代码清单 5.2)。 **代码清单 5.2 if_elsif.rb** ~~~ 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` 结构。 ### **5.5 unless 语句** `unless` 语句的用法刚好与 `if` 语句相反。`unless` 语句的用法如下: **`unless` 条件 `then`  处理 `end`** ※ 可以省略 `then` ![{%}](https://box.kancloud.cn/2015-10-26_562e01dba2c4d.png) `unless` 语句的形式和 if 语句一样。但 `if` 语句是条件为真时执行处理,`unless` 语句则刚好相反,条件为假时执行处理。 下面是使用 `unless` 的例子(代码清单 5.3)。 **代码清单 5.3 unless.rb** ~~~ 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` 语句时同样的效果。 ### **5.6 case 语句** 条件有多个时,使用 `if` 与 `elsif` 的组合虽然也能达到判断多个条件的效果,但是如果需要比较的对象只有一个,根据这个对象值的不同,执行不同的处理时,使用 `case` 语句会使程序更简单,更便于理解。 `case` 语句的用法如下: **`case` 比较对象 `when` 值 `1 then`  处理 `1` `when` 值 `2 then`  处理 `2` `when` 值 `3 then`  处理 `3` `else`  处理 `4` `end`** ※ 可以省略 `then` ![{%}](https://box.kancloud.cn/2015-10-26_562e01dbb1030.png) 本例的比较对象的值有 3 个,但根据实际情况可以无限增加下去。 还有,`when` 可以一次指定多个值。下面的示例(代码清单 5.4)从数组 `tags` 的开头依次取出元素,判断元素值,输出相应的结果。 **代码清单 5.4 case.rb** ~~~ 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. ~~~ 我们再来看看其他例子。 **代码清单 5.5 case_class.rb** ~~~ 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` 方法,结束程序。 3在正则表达式中,`^` 表示匹配字符串的开始,`$` 表示匹配字符串的结束。——译者注 > **专栏** > === 与 case 语句 > 在 `case` 语句中,`when` 判断值是否相等时,实际是使用 `===` 运算符来判断的。左边是数值或者字符串时,`===` 与`==` 的意义是一样的,除此以外,`===` 还可以与`=~` 一样用来判断正则表达式是否匹配,或者判断右边的对象是否属于左边的类,等等。对比单纯的判断两边的值是否相等,`===` 能表达更加广义的“相等”。 ~~~ p (/zz/ === "xyzzy") #=> true p (String === "xyzzy") #=> true p ((1..3) === 2) #=> true ~~~ > 用 `if` 语句改写 `case` 语句的程序如下所示。请注意 `when` 指定的对象在`===`h 的左边。 > ![{%}](https://box.kancloud.cn/2015-10-26_562e01dbe5a4c.png) ### **5.7 if 修饰符与 unless 修饰符** `if` 与 `unless` 可以写在希望执行的代码的后面。像下面这样: ~~~ puts "a 比b 大" if a > b ~~~ 这与下面的写法是等价的。 ~~~ if a > b puts "a 比b 大" end ~~~ 使用修饰符的写法会使程序更加紧凑。通常,我们在希望强调代码执行的内容时会使用修饰符写法。同样地,在使用修饰符写法时,请大家注意程序的易读性。 ### **5.8 总结** 本章介绍了以下内容。 - **真假值** 真假值是条件表达式的返回值。 - **`nil` 或者 `false` 时为假** - **除此以外的值为真** - **条件判断语句** 条件判断语句有: - **`if` 语句** - **`unless` 语句** - **`case` 语句** - **比较** 用 `if` 语句、`unless` 语句做比较时,会用到比较运算符(==,!=,<,> 等)、以 ? 结尾的方法、逻辑运算符等。 - **if 语句、unless 语句** 两者皆为条件判断的基本语句。 - **case 语句** 在遇到像“根据对象的不同状态,采取不同的处理”那样的分情况处理时,我们会用到 `case` 语句。 分情况处理时,不同的对象所采取的判断方法也不一样。具体来说是根据 === 运算符的比较特性,实现分情况处理。更详细的说明请参考专栏“`===` 与 `case` 语句”。 大部分的编程语言都有条件判断。本书也在很多地方使用了条件判断。大家可以参考这些内容,逐渐熟练掌握什么时候该用哪种条件判断语句。 > **专栏** > **对象的同一性** > 所有的对象都有标识和值。 > 标识(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.0` 与 `1` 判断为相同的值会更加方便。在一般情况进行值的比较时使用 `==`,但是在一些需要进行更严谨的比较的程序中,就需要用到 `eql?` 方法。例如,`0` 与 `0.0` 作为散列的键时,会判断为不同的键,这是由于散列对象内部的键比较使用了 `eql?` 方法来判断。 ~~~ hash = { 0 => "0"} p hash[0.0] #=> nil p hash[0] #=> "0" ~~~