1.6 其他特性
下面是本书中示例代码会用到的其他特性。
1.6.1 局部变量和赋值
就像我们已经看到的那样,Ruby 仅允许通过赋值声明局部变量:
>> greeting = 'hello' => "hello" >> greeting => "hello"
我们还可以通过数组一次给多个变量并行赋值:
>> width, height, depth = [1000, 2250, 250] => [1000, 2250, 250] >> height => 2250
1.6.2 字符串插值
字符串可以使用单引号也可以使用双引号表示。对双引号中的字符串,Ruby 会自动用表达式的结果替换 #{ 表达式 },以执行字符串插值操作。
>> "hello #{'dlrow'.reverse}" => "hello world"
如果被插入的表达式返回的不是一个字符串类型的对象,那么这个对象就会自动收到一个to_s 消息以返回能顶替其位置的字符串。我们可以借此控制被替换对象的展示方式:
>> o = Object.new => #<Object> >> def o.to_s 'a new object' end => nil >> "here is #{o}" => "here is a new object"
1.6.3 检查对象
每当 IRB 需要显示一个对象,类似下面的一些事情就会发生:向这个对象发送 inspect 消息,然后这个对象返回自身的字符串表示。Ruby 当中所有对象默认都有对 #inspect 的合理实现,但是通过提供自己的定义,我们就可以控制如何在控制台显示对象:
>> o = Object.new => #<Object> >> def o.inspect '[my object]' end => nil >> o => [my object]
1.6.4 打印字符串
方法 #puts 对每个 Ruby 对象(包括 main)都可用,可以用来向标准输出打印字符串:
>> x = 128 => 128 >> while x < 1000 puts "x is #{x}" x = x * 2 end x is 128 x is 256 x is 512 => nil
1.6.5 可变参数方法(variadic method)
定义方法时可以使用 * 运算符,以支持数目可变的参数:
>> def join_with_commas(*words) words.join(', ') end => nil >> join_with_commas('one', 'two', 'three') => "one, two, three"
一个方法定义只能有一个可变参数,而常规参数放到可变参数的前后都可以:
>> def join_with_commas(before, *words, after) before + words.join(', ') + after end => nil >> join_with_commas('Testing: ', 'one', 'two', 'three', '.') => "Testing: one, two, three."
在发送消息的时候,* 运算符还可以把每一个数组元素当作单个参数处理:
>> arguments = ['Testing: ', 'one', 'two', 'three', '.'] => ["Testing: ", "one", "two", "three", "."] >> join_with_commas(*arguments) => "Testing: one, two, three."
* 也可以使用并行赋值方式:
>> before, *words, after = ['Testing: ', 'one', 'two', 'three', '.'] => ["Testing: ", "one", "two", "three", "."] >> before => "Testing: " >> words => ["one", "two", "three"] >> after => "."
1.6.6 代码块
代码块(block)是由 do/end 或者大括号围住的一段 Ruby 代码。方法可以带一个隐式代码块参数,并使用 yield 关键字表示对代码块中那段代码的调用:
>> def do_three_times yield yield yield end => nil >> do_three_times { puts 'hello' } hello hello hello => nil
代码块可以带参数:
>> def do_three_times yield('first') yield('second') yield('third') end => nil >> do_three_times { |n| puts "#{n}: hello" } first: hello second: hello third: hello => nil
yield 返回执行代码块的结果:
>> def number_names [yield('one'), yield('two'), yield('three')].join(', ') end => nil >> number_names { |name| name.upcase.reverse } => "ENO, OWT, EERHT"
1.6.7 枚举类型
Ruby 有 一个叫作 Enumerable 的内置模块,被数组(Array)、散列表(Hash)、范围(Range)以及其他表示值的集合的类包含。Enumerable 提供的方法可以帮助我们对集合进行遍历、搜索和排序,其中的很多方法在调用时都可以带上一个代码块。通常,代码块中的代码会根据集合中的一些值或全部值来运行,以此承担方法的一部分工作。例如:
>> (1..10).count { |number| number.even? } => 5 >> (1..10).select { |number| number.even? } => [2, 4, 6, 8, 10] >> (1..10).any? { |number| number < 8 } => true >> (1..10).all? { |number| number < 8 } => false >> (1..5).each do |number| if number.even? puts "#{number} is even" else puts "#{number} is odd" end end 1 is odd 2 is even 3 is odd 4 is even 5 is odd => 1..5 >> (1..10).map { |number| number * 3 } => [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
通常,一个代码块带有一个参数,并向此参数发送一个无参的消息,所以 Ruby 提供了一种缩写方式 &:message,这比写代码块 { |object| object.message } 更为简洁:
>> (1..10).select(&:even?) => [2, 4, 6, 8, 10] >> ['one', 'two', 'three'].map(&:upcase) => ["ONE", "TWO", "THREE"]
有的代码块可以为集合中的每个值生成一个数组,Enumerable 的方法 #flat_map 能把这些生成的结果数组连接起来:
>> ['one', 'two', 'three'].map(&:chars) => [["o", "n", "e"], ["t", "w", "o"], ["t", "h", "r", "e", "e"]] >> ['one', 'two', 'three'].flat_map(&:chars) => ["o", "n", "e", "t", "w", "o", "t", "h", "r", "e", "e"]
还有一个有用的方法 #inject。有些代码块会处理集合中的每个值,#inject 能对这个代码块求值并累积成一个最终结果:
>> (1..10).inject(0) { |result, number| result + number } => 55 >> (1..10).inject(1) { |result, number| result * number } => 3628800 >> ['one', 'two', 'three'].inject('Words:') { |result, word| "#{result} #{word}" } => "Words: one two three"
1.6.8 结构体
结构体(Struct)是 Ruby 中一个特殊的类,它的工作是生成其他类。根据传进 Struct.new 的每个属性名,Struct 产成的类会包含相应的获取方法和设置方法。要使用由结构体生成的类,常见方式是对其进行子类化;我们可以给子类起个名字,然后在里边定义其他任意的方法。例如,为了创建一个拥有属性 x 和 y,名字是 Point 的类,可以写成:
class Point < Struct.new(:x, :y) def +(other_point) Point.new(x + other_point.x, y + other_point.y) end def inspect "#<Point (#{x}, #{y})>" end end
现在我们可以创建 Point 的一些实例,然后在 IRB 中进行检查,并给它们发送消息:
>> a = Point.new(2, 3) => #<Point (2, 3)> >> b = Point.new(10, 20) => #<Point (10, 20)> >> a + b => #<Point (12, 23)>
和我们定义的所有方法一样,Point 实例会响应消息 x 和 x=,以便获取和设置属性 x 的值。
y 和 y= 与 x和x= 的情况类似:
>> a.x => 2 >> a.x = 35 => 35 >> a + b => #<Point (45, 23)>
由 Struct.new 生成的类还有其他实用功能,像判断是否相等的方法 #== 的实现,就可以比较两个结构体的属性是否相等:
>> Point.new(4, 5) == Point.new(4, 5) => true >> Point.new(4, 5) == Point.new(6, 7) => false
1.6.9 给内置对象扩展方法(Monkey Patching)
我们随时都可以给类或模块增加方法。这是一个强大的特性,通常叫作 Monkey Patching,可以让我们扩展已有类的行为:
>> class Point def -(other_point) Point.new(x - other_point.x, y - other_point.y) end end => nil >> Point.new(10, 15) - Point.new(1, 1) => #<Point (9, 14)>
我们甚至可以扩展 Ruby 内置的类:
>> class String def shout upcase + '!!!' end end => nil >> 'hello world'.shout => "HELLO WORLD!!!"
1.6.10 定义常量
Ruby 支持一种叫作常量的特殊变量。一般而言,常量一旦创建,就不能再被重新赋值。(Ruby 并不会阻止一个常量被重新赋值,但它会产生警告,以便我们知道自己做错了事。)任何以大写字母开头的变量都是常量。可以在顶层或者在一个类或模块中定义新的常量:
>> NUMBERS = [4, 8, 15, 16, 23, 42] => [4, 8, 15, 16, 23, 42] >> class Greetings ENGLISH = 'hello' FRENCH = 'bonjour' GERMAN = 'guten Tag' end => "guten Tag" >> NUMBERS.last => 42 >> Greetings::FRENCH => "bonjour"
类和模块的名字总是以大写字母开头,所以类和模块的名字也是常量。
1.6.11 删除常量
在使用 IRB 进行探索时,如果我们想重新定义某个类或模块,而不是要扩展它们,实用的做法是让 Ruby 完全忽略该常量。一个顶层常量可以通过给 Object 发送消息 remove_const来删除,同时还要把常量名作为符号(symbol)对象传进去:
>> NUMBERS.last => 42 >> Object.send(:remove_const, :NUMBERS) => [4, 8, 15, 16, 23, 42] >> NUMBERS.last NameError: uninitialized constant NUMBERS >> Greetings::GERMAN => "guten Tag" >> Object.send(:remove_const, :Greetings) => Greetings >> Greetings::GERMAN NameError: uninitialized constant Greetings
只能使用 Object.send(:remove_const, : 常量名 ) 而非 Object.remove_const(:常量名 ),这是因为 remove_const 是一个私有(private)方法,只能通过从 Object 类的自身内部发送消息来调用;使用 Object.send 时,我们可以暂时跳过这个限制。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论