Ruby 中的对象
最后修改于 2023 年 10 月 18 日
在本 Ruby 教程中,我们将介绍 Ruby 语言中的对象概念。我们将在 OOP 章节中了解更多关于对象的信息。我写了这章关于对象的初步章节,因为许多 Ruby 功能可能会让新手感到困惑——特别是如果他们已经了解任何其他编程语言。
一切皆对象
Ruby 是一种面向对象的编程语言。这意味着在 Ruby 程序中,我们使用对象。从语言程序员的角度来看,Ruby 程序是令牌流。这些令牌是 Ruby 关键字、运算符、各种分隔符或字面量。从语义的角度来看,Ruby 程序由对象组成。这些对象在 Ruby 脚本的生命周期中被创建和修改。
有两种对象:内置对象和自定义对象。内置对象是所有程序员都可以使用的预定义对象。它们可以从 Ruby 语言的核心或各种库中使用。自定义对象由应用程序程序员为其应用程序域创建。
在我们可以使用它们之前,必须创建所有对象。我们经常使用术语对象实例化。它是对象创建的同义词。对象由数据和方法组成。数据是对象的静态部分。方法构成对象的动态部分。对象通过方法进行修改和相互通信。
#!/usr/bin/ruby puts "Ruby language"
我们有一个简单的 Ruby 脚本。如果我们熟悉一些过程式语言,比如 Pascal 或 C,我们可能会看到一个名为 puts
的关键字或函数及其参数 "Ruby language",它是一个字符串。
Ruby 是一种纯面向对象的语言,情况有所不同。"Ruby language" 确实是一个字符串,这是一种常见的数据类型。但它也是一个对象。并且与所有对象一样,我们可以调用它的方法。这与其他语言略有不同。puts
是一种方法。方法是在对象中定义的函数。方法不能单独存在。事实上,puts
方法是 Kernel
模块的一部分。
#!/usr/bin/ruby Kernel.puts "Ruby language" Kernel.puts "Ruby language".size
在上面的脚本中,我们有两行代码。
Kernel.puts "Ruby language"
在第一个例子中,我们调用了 puts
方法,没有使用 Kernel
部分,这可以省略。这节省了时间和一些打字。实际上它是对 Kernel.puts
正式调用的简写。在 C# 中,我们有 Console.WriteLine
,在 Java 中有 System.println
。 想法是一样的。方法必须与某个对象关联,或者,在类方法的情况下,与一个类关联。
Kernel.puts "Ruby language".size
在此代码行中,我们将 "Ruby language" 字符串的大小打印到控制台。对于用其他语言编程的程序员来说,这可能会令人困惑。在其他语言中,字符串是一种基本数据类型,不能被修改并且缺少它自己的方法。在 Ruby 中,字符串是一个完整的对象,并且有它自己的方法。size
方法是其中之一。它以字符为单位返回字符串的大小。
$ ./simple2.rb Ruby language 13
Ruby Integer 对象
在下面的例子中,我们来看看一个整数。与字符串类似,整数值也是一个 Ruby 对象。
#!/usr/bin/ruby puts 6.object_id puts 6.even? puts 6.zero? puts 6.class
在这个例子中,我们有一个整数 6。我们在这个数字上调用了一些方法。
puts 6.object_id
6 是一个对象。object_id
是一种方法。该方法返回与对象关联的 id。每个对象都有一个 id。如果我们对一个对象调用一个方法,我们必须总是在两者之间放置一个点字符。
puts 6.even? puts 6.zero?
在这里,我们对 6 对象调用了两种方法。如果数字是偶数,even?
返回 true。如果数字等于零,则 zero?
方法返回 true。请注意,这两种方法都以问号结尾。这是 Ruby 的一种约定。返回布尔值的方法以问号结尾。
puts 6.class
class
方法告诉我们我们正在处理什么样的对象。在我们的例子中,6 是一个 Fixnum
$ ./object_number.rb 13 true false Integer
Ruby 对象创建
我们已经提到过,在我们可以使用 Ruby 对象之前,必须先创建它们。对象可以隐式或显式地创建。隐式对象创建是通过字面量表示法进行的。显式对象创建通过使用 new
关键字来实现。自定义对象始终使用 new
关键字创建。自定义对象必须从特定的类创建。一个类是一个对象的模板。一个类可以用来创建许多对象。
#!/usr/bin/ruby class Being end puts 67 puts "ZetCode" s = String.new "ZetCode" puts s b = Being.new puts b
该代码示例演示了在 Ruby 中创建对象。
class Being end
这是我们自定义对象 Being
的模板。模板是使用 class
关键字创建的。自定义对象的模板通常放置在源文件的顶部或单独的 Ruby 文件中。
puts 67 puts "ZetCode"
在这两行中,我们使用了两个对象。一个类型为 Fixnum
的 67 对象和一个类型为 String
的 "ZetCode" 字符串。67 和 "String" 就是我们所说的字面量。字面量是一种特定类型的特定值的文本表示形式。这两个对象是在幕后由 Ruby 解释器创建的。Ruby 中的一些对象是通过在源代码中指定它们的字面量来创建的。
s = String.new "ZetCode" puts s
这是创建 String
对象的正式方式。它与之前的隐式创建(使用字符串字面量)相同。
b = Being.new puts b
在这里,我们创建了自定义对象的一个实例。puts
方法为我们提供了该对象的简短描述。
$ ./object_create.rb 67 ZetCode ZetCode #<Being:0x9944d9c>
我们继续进行一些正式的对象创建。
#!/usr/bin/ruby s1 = String.new "Ruby" puts s1.size puts s1.downcase a1 = Array.new a1.push 1, 2, 3 puts a1.include? 3 puts a1.empty? r1 = Range.new 1, 6 puts r1.class puts r1.include? 4
在这个例子中,我们创建了三个内置对象,并调用了它们的一些方法。
s1 = String.new "Ruby" puts s1.size puts s1.downcase
创建了一个 String
对象。我们调用了该对象的两种方法。size
方法返回字符串的大小。downcase
方法将字符串的字符转换为小写。
a1 = Array.new a1.push 1, 2, 3 puts a1.include? 3 puts a1.empty?
在这里,我们创建了一个 Array
对象并向其中添加了三个数字。稍后,我们调用了两个数组方法。include?
方法检查特定值(在我们的例子中是 3)是否是数组的一部分。empty?
方法返回一个布尔值,指示数组是否为空。
r1 = Range.new 1, 6 puts r1.class puts r1.include? 4
创建了 Range
类的一个实例。它包含从 1 到 6 的数字。class
方法返回对象的名称。include?
方法检查数字 4 是否是范围的一部分。在我们的例子中是。
$ ./formal.rb 4 ruby true false Range true
运行此示例会给出此输出。
Ruby 对象字面量
正如我们已经提到的,一些内置对象可以使用对象字面量来创建。以下示例显示了几个对象字面量。
#!/usr/bin/ruby 4.times { puts "Ruby" } puts "Ruby".size puts "Ruby".downcase puts [1, 2, 3].include? 3 puts [1, 2, 3].empty? puts :name.class puts :name.frozen? puts (1..6).class puts (1..6).include? 4
在上面的例子中,我们使用字面量表示法来创建 fixnum、字符串、数组、符号和范围。
4.times { puts "Ruby" }
我们可以立即在一个整数字面量上调用一个方法。此行将 "Ruby" 字符串打印到终端四次。
puts "Ruby".size puts "Ruby".downcase
我们在使用字符串字面量创建的 String
对象上调用两种方法。
puts [1, 2, 3].include? 3 puts [1, 2, 3].empty?
在这里,我们使用数组字面量表示法创建了两个 Array
对象。我们使用 include?
方法检查特定数字是否是数组的一部分。empty?
方法检查数组对象是否为空。
puts :name.class puts :name.frozen?
调用了 Symbol 对象的两种方法。符号是使用符号字面量创建的,它以冒号开头。
puts (1..6).class puts (1..6).include? 4
使用范围字面量创建了两个 Range
对象。我们在这些对象上调用两种方法。class
方法返回类的名称,而 include?
方法检查给定的数字是否是范围的一部分。
$ ./object_literals.rb Ruby Ruby Ruby Ruby 4 ruby true false Symbol true Range true
示例输出。
Ruby 对象层次结构
在许多面向对象的语言中,对象形成一个层次结构。Ruby 也有一个对象层次结构。它是一个类似树的层次结构,我们有父对象和子对象。对象从它们的父对象继承数据和行为。在层次结构的顶部是根对象。它被称为 Object
。Ruby 中的每个对象至少有一个父级。换句话说,每个对象都从基本的 Object
对象继承。
根据 Ruby 官方文档,Object
是 Ruby 类层次结构的根。它的方法可用于所有类,除非被显式覆盖。
#!/usr/bin/ruby puts 4.is_a? Object puts "Ruby".is_a? Object puts [2, 3].is_a? Object puts :name.is_a? Object puts (1..2).is_a? Object
在上面的代码示例中,我们演示了所有对象都继承自根 Object
puts 4.is_a? Object
我们使用 is_a?
方法来检查一个数字是否是特定的类型:换句话说,它是否继承自给定的对象类型。
$ ./mother_object.rb true true true true true
所有方法都返回 true,这意味着所有对象都继承自母对象。
继承层次结构可能非常复杂,即使对于非常基本的 Ruby 对象也是如此。
#!/usr/bin/ruby puts 6.class puts 6.is_a? BasicObject puts 6.is_a? Object puts 6.is_a? Numeric puts 6.is_a? Integer puts 6.is_a? String
在这个例子中,我们揭示了小数值的继承层次结构。
puts 6.class
我们找出数字值 6 是什么类型的对象:Integer
。
puts 6.is_a? BasicObject puts 6.is_a? Object puts 6.is_a? Numeric puts 6.is_a? Integer
以上所有行都返回 true。
puts 6.is_a? String
String
不是 6 值的父级。
$ ./inheritance.rb Integer true true true true false
我们以一个例子结束本节,演示自定义用户对象的继承。
#!/usr/bin/ruby class Being def to_s "This is Being" end def get_id 9 end end class Living < Being def to_s "This is Living" end end l = Living.new puts l puts l.get_id puts l.is_a? Being puts l.is_a? Object puts l.is_a? BasicObject
在示例中,我们创建了两个对象 Being
和 Living
。Living
对象继承自 Being
。第一个是父对象,第二个是子对象。
class Being def to_s "This is Being" end def get_id 9 end end
这是自定义 Ruby 对象的定义。该定义位于 class
和 end
关键字之间。在定义内部,我们创建了两种方法。当 puts
方法将一个对象作为参数时,它会调用其 to_s
方法。它通常给出一个对象的字符串表示/描述。
class Living < Being def to_s "This is Living" end end
我们创建了 Living
对象的定义。该对象继承自 Being
对象。运算符 < 用于创建继承关系。to_s
方法被覆盖。
l = Living.new
从上面的 Living
对象模板,我们创建了 Living
对象的一个实例。自定义对象的实例是用 new
关键字创建的。
puts l
puts
方法调用 Living
对象的 to_s
方法。如果 Living
类中没有定义 to_s
方法,则将调用 Being
类的 to_s
方法。
puts l.get_id
Living
对象没有定义 get_id
方法。在这种情况下,会检查父类是否具有此类方法。在我们的例子中,Being
方法具有此类方法,并且它被调用。
puts l.is_a? Being
该行返回 true。Living
是 Being
的一种类型;例如,它继承自 Being
类。
puts l.is_a? Object puts l.is_a? BasicObject
对于我们的 Living
自定义对象,我们没有明确指定与 Object
或 BasicObject
对象的任何关系。但是这两行都返回 true。这是因为 Ruby 中的每个对象都会自动成为这两个对象的后代。这是由 Ruby 解释器在幕后完成的。
$ ./custom_inher.rb This is Living 9 true true true
Ruby 顶级
Ruby 有一个特定的对象,被称为 Ruby 顶级。它是一个默认的执行环境,定义在任何其他上下文之外,例如类或模块定义。顶级的名称是 main
。它是 Object
类型的一个实例。有一个与 main 关联的本地空间,所有局部变量都驻留在其中。
#!/usr/bin/ruby n1 = 3 n2 = 5 puts local_variables Kernel.puts self puts self.class
n1 = 3 n2 = 5
在这里,我们定义了两个数值变量。这些变量是顶级的局部变量。
puts local_variables
在这里,我们生成一个所有局部变量的列表。local_variables
是 Kernel
模块的一种方法,它被混入每个 Object
中,包括顶级对象。
Kernel.puts self
self
是一个 Ruby 伪变量。它返回当前的对象接收器。该行将 "main" 打印到控制台。它是 Ruby 顶级的名称。可以省略 Kernel.puts
代码的 Kernel
部分。通过完全指定名称,我们表明 puts
方法属于 Kernel
模块。
puts self.class
该行打印顶级对象的 class
。我们得到了顶级对象的对象类型。它是 Object
,它是 Ruby 类层次结构的根。
$ ./toplevel.rb n1 n2 main Object
这是该例子的输出。n1
和 n2
是与顶级关联的局部变量。main 是为 Ruby 顶级执行环境给出的名称。最后,Object 是顶级的类型。
我们还有另一个与 Ruby 顶级相关的例子。
#!/usr/bin/ruby @name = "Jane" @age = 17 def info "#{@name} is #{@age} years old" end puts self.instance_variables puts self.private_methods.include? :info puts info
我们展示了属于顶级环境的实例变量和方法。
@name = "Jane" @age = 17
我们定义了两个实例变量。实例变量在 Ruby 中以 @
字符开头。实例变量属于特定的对象实例。在这种情况下,它们属于 Ruby 顶级。
def info "#{@name} is #{@age} years old" end
这是一个方法定义。每个方法都必须属于某个对象。此方法属于顶级对象。所有顶级方法都是私有的。访问私有方法受到限制。
puts self.instance_variables
instance_variables
方法打印 self 的所有实例变量,在当前上下文中,self 指向 Ruby 顶级。
puts self.private_methods.include? :info
所有顶级方法都是自动私有的。private_methods
返回对象的所有私有方法。由于有很多方法,我们调用 include?
方法来检查 info 方法是否在其中。请注意,我们通过其符号名称来引用 info 方法。
$ ./toplevel2.rb @name @age true Jane is 17 years old
本章涵盖了 Ruby 语言中对象的一些基础知识。