ZetCode

Ruby 中的对象

最后修改于 2023 年 10 月 18 日

在本 Ruby 教程中,我们将介绍 Ruby 语言中的对象概念。我们将在 OOP 章节中了解更多关于对象的信息。我写了这章关于对象的初步章节,因为许多 Ruby 功能可能会让新手感到困惑——特别是如果他们已经了解任何其他编程语言。

一切皆对象

Ruby 是一种面向对象的编程语言。这意味着在 Ruby 程序中,我们使用对象。从语言程序员的角度来看,Ruby 程序是令牌流。这些令牌是 Ruby 关键字、运算符、各种分隔符或字面量。从语义的角度来看,Ruby 程序由对象组成。这些对象在 Ruby 脚本的生命周期中被创建和修改。

有两种对象:内置对象和自定义对象。内置对象是所有程序员都可以使用的预定义对象。它们可以从 Ruby 语言的核心或各种库中使用。自定义对象由应用程序程序员为其应用程序域创建。

在我们可以使用它们之前,必须创建所有对象。我们经常使用术语对象实例化。它是对象创建的同义词。对象由数据和方法组成。数据是对象的静态部分。方法构成对象的动态部分。对象通过方法进行修改和相互通信。

simple.rb
#!/usr/bin/ruby

puts "Ruby language"

我们有一个简单的 Ruby 脚本。如果我们熟悉一些过程式语言,比如 Pascal 或 C,我们可能会看到一个名为 puts 的关键字或函数及其参数 "Ruby language",它是一个字符串。

Ruby 是一种纯面向对象的语言,情况有所不同。"Ruby language" 确实是一个字符串,这是一种常见的数据类型。但它也是一个对象。并且与所有对象一样,我们可以调用它的方法。这与其他语言略有不同。puts 是一种方法。方法是在对象中定义的函数。方法不能单独存在。事实上,puts 方法是 Kernel 模块的一部分。

simple2.rb
#!/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 对象。

object_number.rb
#!/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 关键字创建。自定义对象必须从特定的类创建。一个类是一个对象的模板。一个类可以用来创建许多对象。

object_create.rb
#!/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>

我们继续进行一些正式的对象创建。

formal.rb
#!/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 对象字面量

正如我们已经提到的,一些内置对象可以使用对象字面量来创建。以下示例显示了几个对象字面量。

object_literals.rb
#!/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 类层次结构的根。它的方法可用于所有类,除非被显式覆盖。

mother_object.rb
#!/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 对象也是如此。

inheritance.rb
#!/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

我们以一个例子结束本节,演示自定义用户对象的继承。

custom_inher.rb
#!/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

在示例中,我们创建了两个对象 BeingLivingLiving 对象继承自 Being。第一个是父对象,第二个是子对象。

class Being

    def to_s
        "This is Being"
    end

    def get_id
        9
    end
end

这是自定义 Ruby 对象的定义。该定义位于 classend 关键字之间。在定义内部,我们创建了两种方法。当 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。LivingBeing 的一种类型;例如,它继承自 Being 类。

puts l.is_a? Object
puts l.is_a? BasicObject

对于我们的 Living 自定义对象,我们没有明确指定与 ObjectBasicObject 对象的任何关系。但是这两行都返回 true。这是因为 Ruby 中的每个对象都会自动成为这两个对象的后代。这是由 Ruby 解释器在幕后完成的。

$ ./custom_inher.rb
This is Living
9
true
true
true

Ruby 顶级

Ruby 有一个特定的对象,被称为 Ruby 顶级。它是一个默认的执行环境,定义在任何其他上下文之外,例如类或模块定义。顶级的名称是 main。它是 Object 类型的一个实例。有一个与 main 关联的本地空间,所有局部变量都驻留在其中。

toplevel.rb
#!/usr/bin/ruby

n1 = 3
n2 = 5

puts local_variables

Kernel.puts self
puts self.class
n1 = 3
n2 = 5

在这里,我们定义了两个数值变量。这些变量是顶级的局部变量。

puts local_variables

在这里,我们生成一个所有局部变量的列表。local_variablesKernel 模块的一种方法,它被混入每个 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

这是该例子的输出。n1n2 是与顶级关联的局部变量。main 是为 Ruby 顶级执行环境给出的名称。最后,Object 是顶级的类型。

我们还有另一个与 Ruby 顶级相关的例子。

toplevel2.rb
#!/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 语言中对象的一些基础知识。