Ruby 内置类的学习笔记

作者:袖梨 2022-06-25

Ruby 的字面构造器

Ruby 大部分的内置类可以使用 new 来实例化:

str = String.new
arr = Array.new
有些类不能使用 new 来实例化,比如 Integer 类。有些内置类可以使用字面构造器,就是不需要使用 new ,可以使用一种特别的形式来创建类的实例。

内置类的字面构造器

String:使用引号, —— "new string",'new string'
Symbol:冒号在前,——  :symbol,:"symbol with spaces"
Array:方括号,—— [1,2,3]
Hash:大括号,—— {"New York" => "NY", "Oregon" => "OR"}
Range:两个或三个点,—— 0..9 或者 0...10
Regexp:斜线,—— /([a-z]+)/
Proc(Lambda):横线,箭头,括号,大括号 —— ->(x,y){x * y}
糖衣语法

语法糖(Syntactic Sugar)。方法调用是这个形式:object.method(args),但 Ruby 提供了一些糖衣语法,可以让方法调用更漂亮:

x = 1 + 2
上面相当于是:

x  = 1.+(2)
通过定义方法来定义操作符

如果在类里定义了 a + 方法,你的类的对象就可以使用加法的糖衣语法。注意,操作符也是一种方法,这种方法只是作为操作符来用,让它更好看点。

使用 + 是一种惯例,Ruby 本身不知道 + 的意思是加法。你可以定义自己的不是加法的 + 方法:

obj = Object.new
def obj.+(other_obj)
  "想加点东西吗?"
end
puts obj + 100
puts 后面的 + 是调用 obj 上面的 + 方法,整数 100 是它的唯一参数。实际上 obj 上的 + 方法不是加法的意思,而只是简单的输出一个字符串。

在操作符糖衣之上的是快捷糖衣:x += 1 就是 x = x + 1。下面的银行账号类里有 + 与 - 方法:

class Account
  attr_accessor :balance

  def initialize(amount=0)
    self.balance = amount
  end

  def +(x)
    self.balance += x
  end

  def -(x)
    self.balance -= x
  end

  def to_s
    balance.to_s
  end
end

acc = Account.new(20)
acc -= 5
puts acc
上面输出的结果是 15。定义了 - 这个实例方法以后,我们就可以使用 -= 这个快捷方式了。

自定义一元操作符

+ 与 - 是一元操作符,在自己的类或对象里你可以指定 +obj 与 -obj 这些表达式的行为,可以定义方法 +@ 与 -@ 。

比如你想在一个叫 Banner 的类里面重新定义 + 与 - 的行为,让它们意思是把字符串变成大写字母或小写字母:

class Banner
  def initialize(text)
    @text = text
  end

  def to_s
    @text
  end

  def +@
    @text.upcase
  end

  def -@
    @text.downcase
  end
end
然后再实验一下:

banner = Banner.new("Eat at David's!")
puts banner # 输出:Eat at David's!
puts +banner # 输出:EAT AT DAVID'S!
puts -banner # 输出:eat at david's!
下午 5:30 ***

下午7:22 **

你也可以定义 ! 操作符,定义一个 ! 方法就行了,完成以后你会有一个一元操作符 ! ,另外还有一个 not :

class Banner
  def !
    reverse
  end
end
再实验一下:

puts !banner # 输出:!s'divaD ta taE
puts (not banner) # 输出:!s'divaD ta taE
! 方法

2016年9月11日 上午9:41 ***

Ruby 的方法可以使用 ! 号结尾,表示 “危险”。比如两个方法做的事情差不多,其中的一个会有点副作用,也可以说它有点危险,那么你可以让这两个方法叫一个名字,但是那个有副作用的或者有点危险的方法的名字的最后可以加上一个 ! 号结尾,比如(upcase 与 upcase!)。

这是 Ruby 的习俗,或者叫惯例,! 号本身在 Ruby 内部并没有特别的意思。但是按照惯例,有叹号的方法就表示它是一个危险的方法。什么是危险,其实是写这个方法的人定义的。比如在 Ruby 内置的类里面,带 ! 号的方法相比它的不带叹号的版本,可能会改变它的接收者。不过也不一直是这样,像:exit/exit!,sub/sub! 。

你也可以把危险想成是一种警告,或者注意! 大部分带叹号的方法都会有一个不带吧号的版本,它们是成对出现的。

破坏性

Ruby 核心带的一些带 ! 号结尾的方法有破坏性,就是它们会改变调用它的那个对象。比如 upcase 这个方法,它会返回一个新的大写字母的字母串,但是 upcase! 方法会直接改变原始的字符串。

做个实验:

>> str = 'hello'
=> "hello"
>> str.upcase
=> "HELLO"
>> str
=> "hello"
>> str.upcase!
=> "HELLO"
>> str
=> "HELLO"
Ruby 核心里的有很多这样的方法,比如 sort/sort!,strip/strip!,reverse/reverse! 。不带叹号的方法返回新的对象,带叹号的方法直接改变原始对象。你得注意调用的方法是不是会改变它的接收者。返回新的对象的方法比修改原始对象的方法更消耗资源。

另一面,你也要注意一下,修改原始对象可能会影响到程序里的其它的地方,比如可能还有些地方要使用原始的没有被修改的对象。

破坏与危险

破坏与危险并不是一回事,有些带破坏性的方法可能不带 ! 号。Ruby 本身并不在乎方法里是不是有 ! 号,只要是方法它都会被执行。这个 ! 号是方法的作者与用户之间的一种沟通的方式。这是一种惯例,有经验的人一看就知道它的意思。

想使用带 ! 号的方法,方法一定得有一个跟它对应的不带 ! 号的版本。这两个方法做的事情基本一样,就是带 ! 号的方法可能有点副作用,比如返回一个不同的值,或者有一些其它的跟不带 ! 号的方法不同的行为。

不要你觉得方法在某些方面有点危险就在方法的名字里使用 ! 号。因为任何的方法它自己本身都不是危险的。只有在一些相对的情况下,一个方法有两个名字,一个带叹号,一个不带叹号,带叹号的方法的意思是这个方法可能会有一些副作用。

比如一个保存文件的方法,别因为它能把文件保存到磁盘上,你就叫它 save! ,就叫它 save 就行了。除非你需要另一个方法,保存文件的时候不备份原始文件(假设 save 方法能备份原始文件),你可以叫这个方法 save! 。

注意并不是所有的有破坏性的方法都是用 ! 号结尾的。没人强制在方法的名字里使用 ! 号,这只是一种惯例。

to_*

上午11:54 ***

Ruby 里面有一些用 to_ 开头的方法,它们可以转换对象。比如 to_s(转换成字符串),to_sym(转换成符号),to_a(转换数组),to_i(转换整数),to_f(转换浮点小数)。

to_s:转换字符串

做个实验:

>> "hello".to_s
=> "hello"
再试一下:

>> ["one", "two", "three", 4, 5, 6].to_s
=> "["one", "two", "three", 4, 5, 6]"
再试试:

>> Object.new.to_s
=> "#"
再做个实验:

>> obj = Object.new
=> #
>> puts obj
#
=> nil
>> def obj.to_s
>> "I'm an object!"
>> end
=> :to_s
>> puts obj
I'm an object!
=> nil
字符串插值也会用到 to_s:

>> "My Object says: #{obj}"
=> "My Object says: I'm an object!"
看一下 inspect 方法:

>> Object.new.inspect
=> "#"
irb 在输出的值上面会自动调用 inspect 方法:

>> Object.new
=> #
>> 'abc'
=> "abc"
>> [1,2,3]
=> [1, 2, 3]
>> /a regular expression/
=> /a regular expression/
自己可以在类里定义 inspect:

class Person
  def initialize(name)
    @name = name
  end

  def inspect
    @name
  end
end

david = Person.new('David')
puts david.inspect
输出的结果是 David 。

再用一下 display:

>> 'hello'.display
hello=> nil
to_a:转换数组

[1,2,3,4,5]
方括号里的东西是一个列表,而不是一个数组,加上方括号以后它才会是一个数组。

>> array = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
>> [*array]
=> [1, 2, 3, 4, 5]
如果不用 * 号:

>> [array]
=> [[1, 2, 3, 4, 5]]
参数:

def combine_names(first_name, last_name)
  first_name + " " + last_name
end

names = ["David", "Black"]
puts combine_names(*names)
输出的是:David Black 。如果不用 * 号,你的参数的值就会是一个数组。

to_i 与 to_f

Ruby 不会自动把字符串转换成数字,或者把数字转换成字符串。

试一下:

>> 1 + "2"
会提示:

TypeError: String can't be coerced into Fixnum
 from (irb):25:in `+'
 from (irb):25
 from /usr/local/bin/irb:11:in `

'
因为 Ruby 不知道把数字跟字符串加到一块儿是什么意思。

再做个实验:

print '输入一个数字:'
n = gets.chomp
puts n * 100
结果就是你输入的东西会输出 100 次。把输入的东西当成数字来处理,你得:

n = gets.to_i
再试一下:

>> 'hello'.to_i
=> 0
保留字符串里的数字部分:

>> '123hello'.to_i
=> 123
被保留的数字部分要在前面,在后面是不行的:

>> 'hello123'.to_i
=> 0
to_f 方法的行为跟 to_i 类似。

严格转换

用 Integer 与 Float。

>> '123abc'.to_i
=> 123
>> Integer('123abc')
ArgumentError: invalid value for Integer(): "123abc"
 from (irb):34:in `Integer'
 from (irb):34
 from /usr/local/bin/irb:11:in `

'
>> Float('3')
=> 3.0
>> Float('-3')
=> -3.0
>> Float('-3xyz')
ArgumentError: invalid value for Float(): "-3xyz"
 from (irb):37:in `Float'
 from (irb):37
 from /usr/local/bin/irb:11:in `
'
角色扮演 to_*

>> 'hello ' + 'there'
=> "hello there"
>> 'hello' + 10
TypeError: no implicit conversion of Fixnum into String
 from (irb):40:in `+'
 from (irb):40
 from /usr/local/bin/irb:11:in `

'
class Person
  attr_accessor :name
  def to_str
    name
  end
end
如果创建一个 Person 对象,让它去加一个字符串,to_str

>> david = Person.new
=> #
>> david.name = 'David'
=> "David"
>> puts 'david is named ' + david + '.'
david is named David.
=> nil
to_ary

class Person
  attr_accessor :name, :age, "email"
  def to_ary
    [name, age, email]
  end
end
试下:

>> david = Person.new
=> #
>> david.name = "David"
=> "David"
>> david.age = 55
=> 55
>> david.email = "david@wherever"
=> "david@wherever"
>> array = []
=> []
>> array.concat(david)
=> ["David", 55, "david@wherever"]
>> p array
["David", 55, "david@wherever"]
=> ["David", 55, "david@wherever"]
布尔状态,布尔对象,nil

Ruby 的每个表达式都会计算出一个对象,每个对象里面都有一个布尔值,true 或 false 。true 与 false 本身也是对象。在很多情况下,在你想得到真或假值的地方,比如 if 声明,或比较两个数字,你不用直接处理这些特别的对象,在这种情况下,你可以把真假想成是状态,而不是对象。

作为状态的 true 与 false

Ruby 里的表达式要么是 true 要么就是 false。使用 if 语句你可以去验证表达式到底是 true 还是 false 。

做些实验:

if (class MyClass; end)
  puts '空白的对象是 true'
else
  puts '空白的对象是 false'
end

if (class MyClass; 1; end)
  puts '类定义里有个数字 1 是 true'
else
  puts '类定义里有个数字 1 是 false'
end

if (def m; return false; end)
  puts '方法定义是 true'
else
  puts '方法定义是 false'
end

if 'string'
  puts '字符串是 true'
else
  puts '字符串是 false'
end

if 100 > 50
  puts '100 大于 50'
else
  puts '100 不大于 50'
end
结果会是:

空白的对象是 false
类定义里有个数字 1 是 true
方法定义是 true
字符串是 true
100 大于 50
再来试一下,看看表达式到底返回的是什么东西:

>> class MyClass; end
=> nil
>> class MyClass; 1; end
=> 1
>> def m; return flase; end
=> :m
>> 'string'
=> "string"
>> 100 > 50
=> true
100 > 50 返回的是 true 这个对象。

作为对象的 true 与 false

true 与 false 是特别的对象,它们是 TrueClass 与 FalseClass 类的唯一的实例。

做个实验:

>> puts true.class
TrueClass
=> nil
>> puts false.class
FalseClass
=> nil
true 与 false 是关键词,所以你不能把它们用在变量或方法的名字里。

>> a = true
=> true
>> a = 1 unless a
=> nil
>> a
=> true
>> b = a
=> true
有时候你会看到把 true 或 false 作为方法的参数使用。比如你想让一个类显示它所有的实例方法,但不想显示在祖先类里的实例方法,可以这样做:

>> String.instance_methods(false)
nil

nil 是 NilClass 的唯一实例。nil 的布尔值是 false。nil 表示的是没有任何东西。

做个实验:

puts @x
puts 一个不存在的实例变量,输出的是 nil 。nil 也是不存在的元素的默认值,比如你创建了一个数组,里面有三个项目,当你访问数组里的第九个项目的时候,得到的值就是 nil 。试一下:

>> ['one', 'two', 'three'][9]
=> nil
nil 表示的就是没有还有不存在。不过 nil 本身是存在的,它也可以像其它对象一样响应方法地调用:

>> nil.to_s
=> ""
>> nil.to_i
=> 0
>> nil.object_id
=> 8
nil 与 false 是 Ruby 里面唯一的两个布尔值是 false 的对象。

下午2:53 ***

比较两个对象

下午3:28 ***

Ruby 的对象可以比较它们自己是否跟其它的对象相等。

相等测试

做些实验:

>> a = Object.new
=> #
>> b = Object.new
=> #
>> a == a
=> true
>> a == b
=> false
>> a != b
=> true
>> a.eql?(a)
=> true
>> a.eql?(b)
=> false
>> a.equal?(a)
=> true
>> a.equal?(b)
=> false
在上面的实验里,我们用了三种相等测试的方法,==,eql?,equal? ,它们给出的结果是一样的。a 跟 a 是相等的,a 跟 b 不相等。

在自己的类里你可以重新定义 == 与 eql? 方法。一般会留着 equal? 方法,你可以用它比较两个对象表示的是不是同一个对象。比如在 String 类里就重新定义了 == 与 eql? 方法:

>> string1 = 'text'
=> "text"
>> string2 = 'text'
=> "text"
>> string1 == string2
=> true
>> string1.eql?(string2)
=> true
>> string1.equal?(string2)
=> false
Comparable 模块

最常见的重定义的相等测试方法是 == 。它是相等测试方法大家族里的一员,也是比较方法家族的成员,==,!==,>,<,>=,<= 。

如果你需要让 MyClass 这个类的对象拥有所有的这些比较方法,你只需要:

把 Comparable 模块混合到 MyClass 里面。
在 MyClass 里使用 <=> 名字定义一个比较方法。
<=> 比较方法有时候也叫飞船操作符或者飞船方法。在这个方法里你可以定义小于,等于,大于到底是什么意思。完成以后,Ruby 就会给你提供对应的比较方法。

例如,你有一个投标类,名字是 Bid,可以跟踪进来的投标,你希望有一些比较的功能可以比较两个 Bid 对象,基于 estimate 属性。比较的时候可以使用 > ,< 这些简单的操作符。

class Bid
  include Comparable
  attr_accessor :estimate
  def <=>(other_bid)
    if self.estimate < other_bid.estimate
      -1
    elsif self.estimate > other_bid.estimate
      1
    else
      0
    end
  end
end
简单点,可以这样;

def <=>(other_bid)
  self.estimate <=> other_bid.estimate
end
实验一下:

>> bid1 = Bid.new
=> #
>> bid2 = Bid.new
=> #
>> bid1.estimate = 100
=> 100
>> bid2.estimate = 105
=> 105
>> bid1 < bid2
=> true
检查对象的能力

inspection 与 reflection,就是让 Ruby 对象在它的生命周期里关于它自己的一些事儿。

列出对象的方法

你想知道一个对象能懂什么信息,也就是能在它上面调用什么方法,试一下:

>> '我是个字符串对象'.methods
你会看到大量的方法,排下顺序可以:

>> '我是个字符串对象'.methods.sort
methods 方法也可以在模块对象与类对象上用:

>> String.methods.sort
列表里的方法是 String 这个类对象的方法。

在一个字符串对象上定义一个独立方法,再查看这个对象的方法列表,你会找到在这个对象上定义的这个独立方法:

>> str = 'A plain old string'
=> "A plain old string"
>> def str.shout
>> self.upcase + "!!!"
>> end
=> :shout
>> str.shout
=> "A PLAIN OLD STRING!!!"
>> str.methods.sort
你也可以单独显示对象的独立方法:

>> str.singleton_methods
=> [:shout]
在一个类里混合了模块,在模块里的方法,类的实例对象也可以调用,再做个实验:

>> str = 'Another plain old string.'
=> "Another plain old string."
>> module StringExtras
>> def shout
>> self.upcase + '!!!'
>> end
>> end
=> :shout
>> class String
>> include StringExtras
>> end
=> String
>> str.methods.include?(:shout)
=> true
先创建了一个字符串对象,又创建了一个模块,又把这个模块混合到了 String 类里面,再查看最开始创建的字符串对象是否包含在模块里定义的 :shout 方法的时候,会返回 true 。

查询类与模块对象

在 irb 里执行 String.methods.sort 的时候,你会找到一个 instance_methods 方法,它会告诉我们 String 类的实例能使用的实例方法:

>> String.instance_methods.sort
也可以问一下模块:

>> Enumerable.instance_methods.sort
过滤与选择的方法列表

有时候你想知道某个特定类上面定义的实例方法,不想包含这个类的祖先上面定义的方法,可以这样做:

String.instance_methods(false).sort
你看到的就只是在 String 类上定义的实例方法。还有几种列出方法的方法:

obj.private_methods
obj.public_methods
obj.protected_methods
obj.singleton_methods
类与模块上的实例方法:

MyClass.private_instance_methods
MyClass.protected_instance_methods
MyClass.public_instance_methods
public_instance_methods 跟 instance_methods 是一个意思。

相关文章

精彩推荐