4.2 字符串和方法
最后更新于:2022-04-01 22:28:30
# 4.2 字符串和方法
我们学习 Ruby 主要使用的工具是 Rails 控制台,它是用来和 Rails 应用交互的命令行工具,在 [2.3.3 节](chapter2.html#a-user-has-many-microposts)介绍过。控制台基于 Ruby 的交互程序(`irb`)开发,因此能使用 Ruby 语言的全部功能。([4.4.4 节](#a-controller-class)会介绍,控制台还可以访问 Rails 环境。)
如果使用云端 IDE,我建议使用一些 irb 配置参数。使用简单的 `nano` 文本编辑器,把[代码清单 4.8](#listing-irbrc) 中的内容写入家目录里的 `.irbrc` 文件:[[2](#fn-2)]
```
$ nano ~/.irbrc
```
[代码清单 4.8](#listing-irbrc) 中的内容作用是简化 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 节](chapter7.html#debug-and-rails-environments)会详细介绍。
控制台是学习的好工具,你可以尽情地探索它的用法。别担心,你(几乎)不会破坏任何东西。如果在控制台中遇到了问题,可以按 Ctrl-C 键结束当前执行的操作,或者按 Ctrl-D 键直接退出。在阅读本章后续内容的过程中,你会发现查阅 [Ruby API](http://ruby-doc.org/) 很有帮助。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](#fn-3)]
另一种创建字符串的方式是通过特殊的句法 `#{}` 进行插值操作:[[4](#fn-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"` 先把字符串打印到屏幕上,然后返回[空值字面量](http://www.answers.com/nil)——`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 节](#class-inheritance)介绍对象技术层面上的意义,不过一般很难通过阅读一本书就理解对象,你要多看一些例子才能建立对对象的感性认识。
对象的作用说起来很简单:响应消息。例如,一个字符串对象可以响应 `length` 这个消息,返回字符串中包含的字符数量:
```
>> "foobar".length # 把 length 消息传给字符串
=> 6
```
一般来说,传给对象的消息是“方法”,是在这个对象上定义的函数。[[5](#fn-5)]字符串还可以响应 `empty?` 方法:
```
>> "foobar".empty?
=> false
>> "".empty?
=> true
```
注意,`empty?` 方法末尾有个问号,这是 Ruby 的约定,说明方法的返回值是布尔值,即 `true` 或 `false`。布尔值在流程控制中特别有用:
```
>> s = "foobar"
>> if s.empty?
>> "The string is empty"
>> else
>> "The string is nonempty"
>> end
=> "The string is nonempty"
```
如果分支很多,可以使用 `elsif`(`else` + `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](chapter3.html#listing-static-pages-controller))和 `full_title` 辅助方法([代码清单 4.2](#listing-title-helper))一样定义方法。(在控制台中定义方法有点麻烦,我们一般会在文件中定义,这里只是为了演示。)例如,我们要定义一个名为 `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](#listing-title-helper) 中的 `full_title` 辅助方法,[[6](#fn-6)]在其中加上注解之后如[代码清单 4.9](#listing-annotated-title-helper) 所示:
##### 代码清单 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](#fn-7)]——组合在一起定义了一个可以在网站布局中使用的辅助方法。这里还有一个知识点——`module ApplicationHelper`:模块为我们提供了一种把相关方法组织在一起的方式,我们可以使用 `include` 把模块插入其他的类中。编写普通的 Ruby 程序时,你要自己定义一个模块,然后再显式将其引入类中,但是辅助方法所在的模块会由 Rails 为我们引入,结果是,`full_title` 方法[自动](http://catb.org/jargon/html/A/automagically.html)可在所有视图中使用。
';