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 的约定,说明方法的返回值是布尔值,即 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)和 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
方法自动可在所有视图中使用。