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 语言中对象的一些基础知识。