Ruby学习笔记之Ruby 对象

作者:袖梨 2022-06-25

大部分 Ruby 程序,它们的设计,逻辑,动作,都是围绕着对象进行的。写一个 Ruby 程序,主要的工作就是去创建对象,然后给它们能力,让它们可以去执行动作。

Ruby 是 OOP 语言,就是面向对象的语言,你执行的计算,数据处理,输入与输出这些动作,都是通过创建对象,然后让这个对象去执行指定的动作来完成的。对象(object)在现实世界里,就是一个东西。一个苹果是一个对象,一张桌子也是一个对象。

每个对象是一个特定的类的实例(instance),对象的行为大部分是在它们所属的那个类上面定义的方法(method)决定的。
当你想干点什么的时候,比如计算,输出,数据比较,你就是让一个对象去做。比如你想问 a 是否等于 b ,你要问的是 a 是不是认为它自己等于 b 。如果你想知道一个学生是不是上了某个老师的课,你可以问这个学生,“你是不是这个老师的学生”。

写 Ruby 程序,大部分工作就是创建对象,让每个对象都扮演个角色,每个对象可以执行跟这个角色相关的动作。

创建一个通用对象

类(class)是一种捆绑与标记行为的方法,它让创建多个拥有相似行为的对象更简单一些。在 Ruby 里面,每个对象都有可能去学习一些行为(method),这些行为可能不在它们的类里面。类的概念适用在对象概念之上,而不是反过来的,所以我们先了解一下对象。

创建一个通用的对象,它不表示某个具体的东西:

obj = Object.new
上面就是创建了一个对象,这个对象交给了变量 obj,你可以通过这个变量来处理新创建的这个对象。

>> obj = Object.new
=> #
所有的 Ruby 对象天生会具有某些能力,就是它会拥有一些可以执行的方法。我们也可以教会对象你想要让它做的事情。

定义对象的行为

假设你想让一个对象会说话,你得先让它说话,在你让它说话之前,你得先教会它说话。就是在对象上去定义方法(method),可以使用 def 这个关键词去定义方法。

def obj.talk
  puts '我是个对象'
  puts '你是吗?'
end
给对象发送信息

让 obj 这个对象可以说话,执行:

obj.talk
会返回:

我是个对象
你是吗?
对象 obj 现在能明白或者能响应发送给它的 talk 这个信息,如果对象上有一个跟这个信息对应的方法,对象就会执行这个方法。

点(.)是一个信息发送的操作符,点右边的信息会发送给点左边的对象。
信息的那个授受者一般就是一个使用变量表示的对象,也可以是字面构造的对象,比如使用引号的字符串。
发送的信息大部分情况下就是方法的名字,比如之前的那个 talk。对象会尝试运行跟信息对应的那个方法,如果方法不存在的话,就会触发错误处理(error-handling)。
带参数的方法

在调用方法的时候,你可以为方法提供一个或多个参数值。在定义方法的时候,参数就是在方法名字右边出现的在一个括号里的一个列表的变量,参数可以是必须的,也可以是可选的。调用方法的时候,你可以为这些变量提供值。

在定义方法的时候,列出的那些变量,它们是方法的 formal parameters(形式参数)。在调用方法的时候,你为这些变量提供的具体的值是方法的 arguments (实际参数)。一般我们就可以使用 arguments 来表示方法的参数,包含形式参数与实际参数。中文就可以直接叫参数。

比如一个摄氏温度转华氏温度的方法:

def obj.c2f(c)
  c * 9.0 / 5 + 32
end
上面方法支持一个参数 c ,调用这个方法的时候可以为这个参数提供值:

puts obj.c2f(100)
结果是:

212.0
定义与调用方法的时候括号是可选的东西。

def obj.c2f c
obj.c2f 100
方法的返回值

Ruby 的表达式都会有一个值,下面是一些表达式,还有它们的值。

>> 2 + 2
=> 4
>> 'hello'
=> "hello"
>> 'hello' + ' there'
=> "hello there"
>> c = 100
=> 100
>> c * 9/5 + 32
=> 212
>> obj.c2f(100)
=> 212.0
503CF7F1-4C64-4ABC-98C5-2C9E5886BBE2

每个方法的调用是一个表达式。调用一个方法的时候,方法会算出一些东西,这个算出来的结果就是方法的返回的值(return value)。

在定义方法和时候,也可以使用 return 这个关键词去返回值:

def obj.c2f(c)
  return c * 9.0 / 5 + 32
end
上面这个例子跟之前我们定义的方法是一样的,加上 return 更清楚一些,使用 return 可以返回多个值,比如 return a,b,c ,返回的值的列表会自动放到一个数组里面。不管用不用 return,每个方法的调用都会返回某些东西。甚至一个空白主体的方法,它都会返回一个 nil 。

创建一个表示票的对象

一张票(ticket)可以提供关于它自己的一些数据,比如这个剧的时间,位置,名字是什么,表演者是谁,哪个座位,多少钱等等。

01/02/03
Town Hall
Author's reading
Mark Twain
Second Balcony, row J, seat 12
$5.50
创建对象

创建一个对象可以很容易的得到上面这些信息。

ticket = Object.new
有了 ticket 这个对象以后,在它里面再去定义一些方法:

def ticket.date
  '01/02/03'
end

def ticket.venue
  'Town Hall'
end

def ticket.event
  'Author's reading'
end

def ticket.performer
  'Mark Twain'
end

def ticket.seat
  'Second Balcony, row J, seat 12'
end

def ticket.price
  5.50
end
现在 ticket 对象就知道了一些关于自己的事情。

查询对象

想得到 ticket 对象的时间,可以执行 ticket.date ,得到座位,执行 ticket.seat。

字符串插值

string interpolation,就是在一个字符串里插入值。用的形式是 #{表示值的东西} ,比如 #{ticket.event} 。

puts "This ticket is for: #{ticket.event}, at #{ticket.venue}." +
 "The performer is #{ticket.performer}." +
 "The seat is #{ticket.seat}, " +
 "and it costs $#{"%.2f." % ticket.price}"
方法里的布尔值

一张票是不是已经卖掉了,一种方法是可以给它添加一个可用的状态:

def ticket.availability_status
  "sold"
end
还有一种方法是问一下它是不是可用,它会返回 true 或者 false:

def ticket.available?
  false
end
true 与 false 在 Ruby 里面都是对象,使用它们可以在方法里表示 真 或者 假。注意 available?,这里有个问号,它会算出 true 或 false,这样在使用这种方法的时候有点像是问一个问题:

if ticket.available?
  puts "You're in luck!"
else
  puts "Sorry--that seat has been sold."
end
Ruby 里的每个表达式都会得到一个对象,这个对象里面都会有一个表示真假的值,这个值几乎在所有的 Ruby 对象里都是 true 。只有 false 跟 nil 对象里的表示真假的值是 false。

ABBC0D70-3A82-47DF-A2E0-50AE1CD5ABC6

>> if 'abc'
>>   puts '在 Ruby 里字符串是 true'
>> end
(irb):105: warning: string literal in condition
在 Ruby 里字符串是 true
=> nil
>> if 123
>>   puts '数字也是 true'
>> end
数字也是 true
=> nil
>> if 0
>>   puts '0 也是 true,注意 0 在某些语言里不是 true'
>> end
0 也是 true,注意 0 在某些语言里不是 true
=> nil
>> if 1 == 2
>>   puts '1 不等于 2,所以不会显示这个字符串'
>> end
=> nil
对象天生的行为

2016年9月5日 下午1:11 ****

一个对象存在以后它就具有了一些天生的行为,查看这些天生的行为,执行一下:

>> puts Object.new.methods.sort
!
!=
!~
<=>
==
===
=~
__id__
__send__
class
clone
define_singleton_method
display
dup
enum_for
eql?
equal?
extend
freeze
frozen?
hash
inspect
instance_eval
instance_exec
instance_of?
instance_variable_defined?
instance_variable_get
instance_variable_set
instance_variables
is_a?
itself
kind_of?
method
methods
nil?
object_id
private_methods
protected_methods
public_method
public_methods
public_send
remove_instance_variable
respond_to?
send
singleton_class
singleton_method
singleton_methods
taint
tainted?
tap
to_enum
to_s
trust
untaint
untrust
untrusted?
=> nil
对象的 ID

每个对象都有一个 ID,这个 ID 会在 object_id 里面,比如:

>> 'hello'.object_id
=> 70214603696960
再做个测试:

>> a = Object.new
=> #
>> b = a
=> #
>> a.object_id
=> 70214603668580
>> b.object_id
=> 70214603668580
再做个测试:

>> string_1 = 'hello'
=> "hello"
>> string_2 = 'hello'
=> "hello"
>> string_1.object_id
=> 70214603633100
>> string_2.object_id
=> 70214603619360
string_1 与 string_2 表示的字符串都是 hello,但是它俩的 object_id 是不一样的。

查询对象是否能做什么

先试一下:

>> obj = Object.new
=> #
>> obj.talk
NoMethodError: undefined method `talk' for #
 from (irb):128
 from /usr/local/bin/irb:11:in `

'
新建一个对象,试着给它发送 talk 这个信息,Ruby 会提示 talk 这个方法还没定义。这样再试一下:

obj = Object.new

if obj.respond_to?("talk")
  obj.talk
else
  puts "Sorry, the object doesn't understand the 'talk' message."
end
创建一个对象,使用对象的 respond_to? ,问一下它能不能 talk 。respond_to? 是对象天生有的能力,它可以查询出对象是不是可以对某些信息做出响应。

使用 send 方法发送信息

之前创建的那个票对象,假设你想根据用户输入的东西,来返回对应的关于这个对象的信息,代码像这样:

print "Information desired: "
request = gets.chomp
然后你可以做判断:

if request == "venue"
  puts ticket.venue
elsif request == "performer"
  puts ticket.performer
...
上面的方法有点长,你也可以这样来做:

if ticket.respond_to?(request)
  puts ticket.send(request)
else
  puts "no such information"
end
上面用 respond_to? 问一下输入的东西有没有,也就是对象是不是可以对这个信息做出响应,如果是的话,就用 send 这个方法去发送这个信息,也就是让对象去执行对应的方法。

方法的参数

Ruby 里的方法可以包含零个或多个参数。参数的数量也可以是可变的。

必须与可选

先做个实验:

obj = Object.new

def obj.one_arg(x)
  puts "I require one and only one argument!"
end

obj.one_arg(1,2,3)
结果会是:

ArgumentError: wrong number of arguments (3 for 1)
意思就是参数的数量错了,应该只有一个参数,你确给出了三个。

在方法里添加多个参数,可以使用 * 号,像这样:

def obj.multi_args(*x)
  puts "I can take zero or more arguments!"
end
*x 这个参数的意思是,你调用这个方法的时候可以添加任意数量的参数。这个参数的值会是一个数组。

再看一个混合使用参数的例子:

def two_or_more(a,b,*c)
  puts "I require two or more arguments!"
  puts "And sure enough, I got: "
  p a, b, c
end
如果像这样调用上面这个方法:two_or_more(1,2,3,4,5) ,得到的东西应该像这样:

I require two or more arguments!
And sure enough, I got:
1
2
[3, 4, 5]
方面里的 p 是输出东西用的,要输出的是传递给方法里的 a,b,c 这几个参数的值。调用方法的时候传递的参数是1,2,3,4,5,这样 a 参数的值应该是 1 ,b 参数的值是 2 ,剩下的是 c 参数的值,它会是一个数组,里面包含了 3,4,5  。

参数的默认值

定义方法的时候可以为参数设置默认的值,如果调用方法的时候没有设置参数的值,就会使用它的默认的那个值。

def default_args(a,b,c=1)
  puts "Values of variables: ",a,b,c
end
如果像这样调用上面这个方法:

default_args(3,2)
得到的结果是:

Values of variables:
3
2
1
调用方法的时候我们没给 c 参数传递值,这样就会使用它的默认的值,也就是 1 。

参数的顺序

先看个例子:

def mixed_args(a,b,*c,d)
  puts "Arguments:"
  p a,b,c,d
end

mixed_args(1,2,3,4,5)
调用方法返回的结果像这样:

Arguments:
1
2
[3, 4]
5
*c 是一个海绵参数,它的优先级比较低,所以如果这样调用方法:

mixed_args(1,2,3)
返回的结果会是:

1
2
[]
3
下面是更详细的参数例子:

必须
def m(abc)
  m(1,2,3)
    a = 1, b = 2, c = 3

可选
def m(*a)
  m(1,2,3)
    a = [1,2,3]

默认
def m(a=1)
  m
    a = 1
  m(2)
    a = 2

必须/可选
def m(a, *b)
  m(1)
    a = 1, b = []

必须/默认
def m(a,b=1)
  m(2)
    a = 2, b = 1
  m(2,3)
    a = 2, b = 3

默认/可选
def m(a=1, *b)
  m
    a = 1, b = []
  m(2)
    a = 2, b = []

必须/默认/可选
def m(a,b=2,*c)
  m(1)
    a = 1, b = 2, c = []
  m(1,3)
    a = 1, b = 3, c = []
  m(1,3,5,7)
    a = 1, b = 3, c = [5,7]

必须/默认/可选/必须
def m(a,b=2,*c,d)
  m(1,3)
    a = 1, b = 2, c = [], d = 3
  m(1,3,5)
    a = 1, b = 3, c = [], d = 5
  m(1,3,5,7)
    a = 1, b = 3, c = [5], d = 7
  m(1,3,5,7,9)
    a = 1, b = 3, c = [5, 7], d = 9

下午2:15 ****

本地变量与变量分配

下午2:15 ****

本地变量可以使用小写字母或下划线开头,也可以包含数字,下面都是合格的本地变量名:

x
_x
name
first_name
plan9
user_ID
_
本地变量的意思就是它的作用范围会受到限制,每个本地变量只会在程序的某一块儿地方起作用。比如在程序的不同的地方可能会包含同样名字的两个本地变量。

常见的作用范围就是在一个方法的定义里面,看个例子:

def say_goodby
 x = 'goodbye'
 puts x
end

def start_here
 x = 'hello'
 puts x
 say_goodby
 puts 'Let us check whether x remained the same:'
 puts x
end

start_here
执行的结果应该是:

hello
goodbye
let us check whether x remained the same:
hello
第一行输出的是 start_here 里定义的 x 变量的值,也就是 hello ,第二行内容是在 start_here 里面执行了 say_goodby,输出了这个方法里定义的 x 的值,最后在 start_here 里输出的 x 的值,仍然是在这个方法内部定义的那个 x 的值。

变量,对象,引用

先试一下:

str = 'hello'
现在 str 这个变量表示的就是字符串 hello 。

再试一下:

str = 'hello'
abc = str
puts abc
str 表示 hello,把 str 的值分配给了 abc ,所以输出 abc 的时候,结果就会是 hello 。

再试一下:

str = "hello"
abc = str
str.replace("goodbye")
puts str
puts abc
def say_goodbye
  str = "hello"
  abc = str
  str.replace("goodbye")
  puts str
  puts abc
end

say_goodbye
输出的结果是:

goodbye
goodbye
第一个 goodbye 是 str 的,第二个是 abc 的,但是我们只替换了 str ,为啥 abc 的值也被替换了?

引用

在 Ruby 里,大部分情况下变量本身不存对象的值,上面的 str 其实不包含 hello 这个字符串,它只包含了一个对这个字符串对象的引用。

在一个分配里,左边是变量,右边是一个对象,变量会收到一个对这个对象的引用。如果分配的是一个变量到另一个变量,比如 abc = str,左边的变量会复制一份右边的变量的引用,也就是这两个变量引用的会是同一个对象。

str.replace("goodbye")
把 str 引用的那个字符串替换成了 goodbye ,变量 abc 引用了同样的字符串对象,所以在 str 上做的替换动作也会影响到 abc ,也就是字符串的内容变成了 goodbye 。

不被引用的东西

在 Ruby 里,有些对象会作为立即值存储在变量里。比如整数,符号(symbol,看起来像 :this,字符串的前面带个冒号),还有 true,false,nil 。你把这些值分配给一个变量的时候,变量会保存这个值本身,而不是一个到它们的引用。Ruby 会自动判断是否使用引用。

任何表示立即值的对象,永远都是唯一的一个对象,不管它分配给了多少个变量。比如只有一个 100 对象 ,只有一个 false 对象。

Ruby 的对象必须有一个或多个引用指向它,如果没有引用,会释放掉对象占用的内存。

如果你有两个或两个以上的变量指向了同一个对象,你可以使用这些变量里的其中的任何一个来发送信息给它们表示的对象。

分配与重新分配变量中的引用

对变量的每次分配,都会把变量之前表示的东西清除掉。来试一下:

str = "hello"
abc = str
str = "goodbye"
puts str
puts abc
这回输出的是:

goodbey
hello
引用与方法参数

来段代码:

def change_string(str)
  str.replace("新内容")
end
再创建一个字符串,把它发送给 change_string :

s = "原始内容"
change_string(s)
再检查一下:

puts s
结果会是:

新内容
如果你不想改变那个字符串,可以这样试一下:

s = "Original string content!"
change_string(s.dup)
puts s
dup 方法会复制一个新的对象。也可以这样做:

s = "原始内容"
s.freeze
change_string(s)
puts s
本地变量和跟它们很像的东西

Ruby 看到这些东西:s,ticket,puts,user_name,会把它们解释成下面这三样东西其中的一种:

本地变量:local variable
关键词:keyword
方法调用:method call
关键词是 Ruby 的保留词,你不能把它们作为变量的名字。比如 def,if 这些都是关键词。跟本地变量差不多,方法调用也是纯文字,如果方法有参数,加上括号以后很容易知道它们不是一个变量。如果调用的方法没有括号,Ruby 会整明白它到底是个啥。

Ruby 是这样判断标识符的:

如果标识符是关键词,它就是一个关键词。
如果有个等号在标识符的右边出现,它就是一个本地变量。
其它的情况标识符就会是一个方法的调用。

相关文章

精彩推荐