ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 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)可在所有视图中使用。