4.2 字符串和方法

我们学习 Ruby 主要使用的工具是 Rails 控制台,它是用来和 Rails 应用交互的命令行工具,在 2.3.3 节介绍过。控制台基于 Ruby 的交互程序(irb)开发,因此能使用 Ruby 语言的全部功能。(4.4.4 节会介绍,控制台还可以访问 Rails 环境。)

如果使用云端 IDE,我建议使用一些 irb 配置参数。使用简单的 nano 文本编辑器,把代码清单 4.8 中的内容写入家目录里的 .irbrc 文件:[2]

$ nano ~/.irbrc

代码清单 4.8 中的内容作用是简化 irb 提示符,以及禁用一些烦人的自动缩进行为。

代码清单 4.8:添加一些 irb 配置

~/.irbrc

IRB.conf[:PROMPT_MODE] = :SIMPLE
IRB.conf[:AUTO_INDENT_MODE] = false

不管加没加这些设置,控制器的启动方法都是在命令行中执行下面的命令:

$ rails console
Loading development environment
>>

默认情况下,控制台在开发环境中启动,这是 Rails 定义的三个独立环境之一(另外两个是测试环境和生产环境)。这三个环境的区别对本章不重要,7.1.1 节会详细介绍。

控制台是学习的好工具,你可以尽情地探索它的用法。别担心,你(几乎)不会破坏任何东西。如果在控制台中遇到了问题,可以按 Ctrl-C 键结束当前执行的操作,或者按 Ctrl-D 键直接退出。在阅读本章后续内容的过程中,你会发现查阅 Ruby API 很有帮助。API 中有很多信息(或许太多了),例如,如果想进一步了解 Ruby 字符串,可以查看 String 类的文档。

4.2.1 注释

Ruby 中的注释以井号 #(也叫“哈希符号”,或者更诗意一点,叫“散列字元”)开头,一直到行尾结束。Ruby 会忽略注释,但是注释对人类读者(往往也包括代码的编写者)很有用。在下面的代码中

# 根据所在的页面返回完整的标题
def full_title(page_title = '')
  .
  .
  .
end

第一行就是注释,说明其后方法的作用。

在控制台中一般不用写注释,不过为了说明代码的作用,我会按照下面的形式加上注释,例如:

$ rails console
>> 17 + 42   # 整数加法运算
=> 59

阅读的过程中,在控制台中输入或者复制粘贴命令时,如果愿意你可以不加注释,反正控制台会忽略注释。

4.2.2 字符串

对 Web 应用来说,字符串或许是最重要的数据结构,因为网页的内容就是从服务器发送给浏览器的字符串。我们先在控制台中体验一下字符串:

$ rails console
>> ""         # 空字符串
=> ""
>> "foo"      # 非空字符串
=> "foo"

这些是字符串字面量,使用双引号(")创建。控制台回显的是每一行的计算结果,本例中,字符串字面量的结果就是字符串本身。

我们还可以使用 + 号连接字符串:

>> "foo" + "bar"    # 字符串连接
=> "foobar"

"foo" 连接 "bar" 得到的结果是字符串 "foobar"。[3]

另一种创建字符串的方式是通过特殊的句法 #{} 进行插值操作:[4]

>> first_name = "Michael"    # 变量赋值
=> "Michael"
>> "#{first_name} Hartl"     # 字符串插值
=> "Michael Hartl"

我们先把 "Michael" 赋值给变量 first_name,然后将其插入字符串 "#{first_name} Hartl" 中。我们也可以把两个字符串都赋值给变量:

>> first_name = "Michael"
=> "Michael"
>> last_name = "Hartl"
=> "Hartl"
>> first_name + " " + last_name    # 字符串连接,中间加了空格
=> "Michael Hartl"
>> "#{first_name}  #{last_name}"    # 作用相同的插值
=> "Michael Hartl"

注意,最后两个表达式的作用相同,不过我倾向于使用插值的方式。在两个字符串中间加入一个空格(" ")显得很别扭。

打印字符串

打印字符串最常用的 Ruby 方法是 puts(读作“put ess”,意思是“打印字符串”):

>> puts "foo"     # 打印字符串
foo
=> nil

puts 方法还有一个副作用:puts "foo" 先把字符串打印到屏幕上,然后返回空值字面量——nil 在 Ruby 中是个特殊值,表示“什么都没有”。(为了行文简洁,后续内容会省略 ⇒ nil。)

从前面的例子可以看出,puts 方法会自动在输出的字符串后面加入换行符 \n。功能类似的 print 方法则不会:

>> print "foo"    # 打印字符串(和 puts 作用一样,但没添加换行符)
foo=> nil
>> print "foo\n"  # 和 puts "foo" 一样
foo
=> nil

单引号字符串

目前介绍的例子都使用双引号创建字符串,不过 Ruby 也支持用单引号创建字符串。大多数情况下这两种字符串的效果是一样的:

>> 'foo'          # 单引号创建的字符串
=> "foo"
>> 'foo' + 'bar'
=> "foobar"

不过,两种方式之间有个重要的区别:Ruby 不会对单引号字符串进行插值操作:

>> '#{foo} bar'     # 单引号字符串不能进行插值操作
=> "\#{foo} bar"

注意,控制台返回的是双引号字符串,因此要使用反斜线转义特殊字符,例如 #

如果双引号字符串可以做单引号能做的所有事,而且还能进行插值,那么单引号字符串存在的意义是什么呢?单引号字符串的用处在于它们真的就是字面值,只包含你输入的字符。例如,反斜线在很多系统中都很特殊,例如在换行符(\n)中。如果有一个变量需要包含一个反斜线,使用单引号就很简单:

>> '\n'       # 反斜线和 n 字面值
=> "\\n"

和前面的 # 字符一样,Ruby 要使用一个额外的反斜线来转义反斜线——在双引号字符串中,要表达一个反斜线就要使用两个反斜线。对简单的例子来说,这省不了多少事,但是如果有很多需要转义的字符就显得出它的作用了:

>> 'Newlines (\n) and tabs (\t) both use the backslash character \.'
=> "Newlines (\\n) and tabs (\\t) both use the backslash character \\."

最后,有一点要注意,单双引号基本上可以互换使用,源码中经常混用,没有章法可循,对此我们只能默默接受——“欢迎进入 Ruby 世界”!

4.2.3 对象和消息传送

在 Ruby 中,一切皆对象,包括字符串和 nil 都是。我们会在 4.4.2 节介绍对象技术层面上的意义,不过一般很难通过阅读一本书就理解对象,你要多看一些例子才能建立对对象的感性认识。

对象的作用说起来很简单:响应消息。例如,一个字符串对象可以响应 length 这个消息,返回字符串中包含的字符数量:

>> "foobar".length        # 把 length 消息传给字符串
=> 6

一般来说,传给对象的消息是“方法”,是在这个对象上定义的函数。[5]字符串还可以响应 empty? 方法:

>> "foobar".empty?
=> false
>> "".empty?
=> true

注意,empty? 方法末尾有个问号,这是 Ruby 的约定,说明方法的返回值是布尔值,即 truefalse。布尔值在流程控制中特别有用:

>> s = "foobar"
>> if s.empty?
>>   "The string is empty"
>> else
>>   "The string is nonempty"
>> end
=> "The string is nonempty"

如果分支很多,可以使用 elsifelse + if):

>> if s.nil?
>>   "The variable is nil"
>> elsif s.empty?
>>   "The string is empty"
>> elsif s.include?("foo")
>>   "The string includes 'foo'"
>> end
=> "The string includes 'foo'"

布尔值还可以使用 &&(和)、||(或)和 !(非)操作符结合在一起使用:

>> x = "foo"
=> "foo"
>> y = ""
=> ""
>> puts "Both strings are empty" if x.empty? && y.empty?
=> nil
>> puts "One of the strings is empty" if x.empty? || y.empty?
"One of the strings is empty"
=> nil
>> puts "x is not empty" if !x.empty?
"x is not empty"
=> nil

因为在 Ruby 中一切都是对象,那么 nil 也是对象,所以它也可以响应方法。举个例子,to_s 方法基本上可以把任何对象转换成字符串:

>> nil.to_s
=> ""

结果显然是个空字符串,我们可以通过下面的方法串联(chain)验证这一点:

>> nil.empty?
NoMethodError: undefined method `empty?' for nil:NilClass
>> nil.to_s.empty?      # 消息串联
=> true

我们看到,nil 对象本身无法响应 empty? 方法,但是 nil.to_s 可以。

有一个特殊的方法可以测试对象是否为空,你或许能猜到这个方法:

>> "foo".nil?
=> false
>> "".nil?
=> false
>> nil.nil?
=> true

下面的代码

puts "x is not empty" if !x.empty?

演示了 if 关键字的另一种用法:你可以编写一个当且只当 if 后面的表达式为真值时才执行的语句。还有个对应的 unless 关键字也可以这么用:

>> string = "foobar"
>> puts "The string '#{string}' is nonempty." unless string.empty?
The string 'foobar' is nonempty.
=> nil

我们需要注意一下 nil 对象的特殊性,除了 false 本身之外,所有 Ruby 对象中它是唯一一个布尔值为“假”的。我们可以使用 !!(读作“bang bang”)对对象做两次取反操作,把对象转换成布尔值:

>> !!nil
=> false

除此之外,其他所有 Ruby 对象都是“真”值,数字 0 也是:

>> !!0
=> true

4.2.4 定义方法

在控制台中,我们可以像定义 home 动作(代码清单 3.6)和 full_title 辅助方法(代码清单 4.2)一样定义方法。(在控制台中定义方法有点麻烦,我们一般会在文件中定义,这里只是为了演示。)例如,我们要定义一个名为 string_message 的方法,接受一个参数,返回值取决于参数是否为空:

>> def string_message(str = '')
>>   if str.empty?
>>     "It's an empty string!"
>>   else
>>     "The string is nonempty."
>>   end
>> end
=> :string_message
>> puts string_message("foobar")
The string is nonempty.
>> puts string_message("")
It's an empty string!
>> puts string_message
It's an empty string!

如最后一个命令所示,可以完全不指定参数(这种情况可以省略括号)。因为 def string_message(str = '') 中提供了参数的默认值,即空字符串。所以,str 参数是可选的,如果不指定,就使用默认值。

注意,Ruby 方法不用显式指定返回值,方法的返回值是最后一个语句的计算结果。上面这个函数的返回值是两个字符串中的一个,具体是哪一个取决于 str 参数是否为空。在 Ruby 方法中也可以显式指定返回值,下面这个方法和前面的等价:

>> def string_message(str = '')
>>   return "It's an empty string!" if str.empty?
>>   return "The string is nonempty."
>> end

(细心的读者可能会发现,其实没必要使用第二个 return,这一行是方法的最后一个表达式,不管有没有 return,字符串 "The string is nonempty." 都会作为返回值返回。不过两处都加上 return 看起来更好。)

还有一点很重要,方法并不关心参数的名字是什么。在前面定义的第一个方法中,可以把 str 换成任意有效的变量名,例如 the_function_argument,但是方法的作用不变:

>> def string_message(the_function_argument = '')
>>   if the_function_argument.empty?
>>     "It's an empty string!"
>>   else
>>     "The string is nonempty."
>>   end
>> end
=> nil
>> puts string_message("")
It's an empty string!
>> puts string_message("foobar")
The string is nonempty.

4.2.5 回顾标题的辅助方法

下面我们来理解一下代码清单 4.2 中的 full_title 辅助方法,[6]在其中加上注解之后如代码清单 4.9 所示:

代码清单 4.9:注解 full_title 方法

app/helpers/application_helper.rb

module ApplicationHelper

  # 根据所在的页面返回完整的标题                      # 在文档中显示的注释
  def full_title(page_title = '')                     # 定义方法,参数可选
    base_title = "Ruby on Rails Tutorial Sample App"  # 变量赋值
    if page_title.empty?                              # 布尔测试
      base_title                                      # 隐式返回值
    else
      page_title + " | " + base_title                 # 字符串拼接
    end
  end
end

方法定义、变量赋值、布尔测试、流程控制和字符串拼接[7]——组合在一起定义了一个可以在网站布局中使用的辅助方法。这里还有一个知识点——module ApplicationHelper:模块为我们提供了一种把相关方法组织在一起的方式,我们可以使用 include 把模块插入其他的类中。编写普通的 Ruby 程序时,你要自己定义一个模块,然后再显式将其引入类中,但是辅助方法所在的模块会由 Rails 为我们引入,结果是,full_title 方法自动可在所有视图中使用。