返回介绍

1.6 其他特性

发布于 2023-05-18 19:12:04 字数 7557 浏览 0 评论 0 收藏 0

下面是本书中示例代码会用到的其他特性。

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文