equal?、eql?、=== 和 == 有什么区别?

What's the difference between equal?, eql?, ===, and ==?

提问人:denniss 提问时间:8/23/2011 最后编辑:Freedom_Bendenniss 更新时间:6/2/2021 访问量:227527

问:

我试图理解这四种方法之间的区别。我知道默认情况下,当两个操作数引用完全相同的对象时,它会调用返回 true 的方法。==equal?

===默认情况下还调用哪些调用...好的,所以如果这三种方法都没有被覆盖,那么我猜,并做完全相同的事情?==equal?=====equal?

现在来了.这(默认情况下)有什么作用?它是否调用操作数的哈希/ID?eql?

为什么 Ruby 有这么多等号?它们应该在语义上有所不同吗?

Ruby 比较 运算符 相等

评论

0赞 PeterWong 8/23/2011
我刚刚开始了一个 irb,并得到了以下与您的结果相矛盾的结果......这 3 个都是正确的:、 和 。但这是错误的:(我的是 ruby 1.9.2-p180)"a" == "a""a" === "a""a".eql? "a""a".equal? "a"
7赞 Nemo157 8/23/2011
@Peter:这是因为字符串会覆盖所有相等运算符。尝试使用 then all , , , 将返回 vs 和 false for vs。a = Object.new; b = Object.new=====.equal?.eql?trueaaab

答:

824赞 jtbandes 8/23/2011 #1

我将在这里大量引用 Object 文档,因为我认为它有一些很好的解释。我鼓励您阅读它,以及这些方法的文档,因为它们在其他类(如 String)中被覆盖。

旁注:如果您想在不同的对象上亲自尝试这些,请使用如下方法:

class Object
  def all_equals(o)
    ops = [:==, :===, :eql?, :equal?]
    Hash[ops.map(&:to_s).zip(ops.map {|s| send(s, o) })]
  end
end

"a".all_equals "a" # => {"=="=>true, "==="=>true, "eql?"=>true, "equal?"=>false}

==— 通用的“平等”

在对象级别,仅当 和 是同一对象时才返回 true。通常,此方法在后代类中重写,以提供特定于类的含义。==objother

这是最常见的比较,因此也是您(作为类的作者)决定两个对象是否“相等”的最基本的地方。

===— 大小写相等

对于类 Object,实际上与调用相同,但通常由后代覆盖,以在 case 语句中提供有意义的语义。#==

这非常有用。具有有趣实现的事物示例:===

  • 范围
  • 正则表达式
  • Proc(在 Ruby 1.9 中)

因此,您可以执行以下操作:

case some_object
when /a regex/
  # The regex matches
when 2..4
  # some_object is in the range 2..4
when lambda {|x| some_crazy_custom_predicate }
  # the lambda returned true
end

在此处查看我的答案,以获取 + 如何使代码更简洁的简洁示例。当然,通过提供自己的实现,您可以获得自定义语义。caseRegex===case

eql?— 平等Hash

如果 和 引用相同的哈希键,则该方法返回 true。这用于测试成员是否相等。对于类 Object 的对象,eql?== 同义。子类通常通过为其重写的方法添加别名来延续这一传统,但也有例外。 例如,类型在 之间执行类型转换,但不跨 执行类型转换,因此:eql?objotherHasheql?==Numeric==eql?

1 == 1.0     #=> true
1.eql? 1.0   #=> false

因此,您可以自由地覆盖它以供自己使用,也可以覆盖和使用,以便这两种方法的行为方式相同。==alias :eql? :==

equal?— 身份比较

与 不同的是,该方法永远不应被子类覆盖:它用于确定对象标识(即,iff 与 是相同的对象)。==equal?a.equal?(b)ab

这实际上是指针比较。

评论

35赞 sawa 10/12/2012
我从你的回答中了解到,严格程度是:平等?< eql?< == < ===.通常,您使用 ==。对于某些松散的目的,您可以使用 ===。对于严格情况,您使用 eql?,对于完全标识,您使用 equal?。
24赞 jtbandes 10/13/2012
严格性的概念在文档中没有强制执行,甚至没有提出,碰巧以比 更严格的方式处理它。这真的取决于课程的作者。 在语句之外很少使用。Numeric=====case
4赞 apeiros 11/4/2012
== 在更大/更小方面也是相等的。也就是说,如果包含 Comparable,它将以 <=> 返回 0 来定义。这就是 1 == 1.0 返回 true 的原因。
5赞 Kelvin 5/18/2013
@sawa我通常认为是“匹配”的意思(大致)。例如,“正则表达式是否与字符串匹配”或“范围是否匹配(包括)数字”。===
7赞 Mark Amery 12/5/2014
有趣的事实:官方文档现在链接到这个答案(见 ruby-doc.org/core-2.1.5/...
8赞 Kishore Mohan 4/22/2014 #2

=== #---大小写相等

== #--- 通用平等

两者的工作方式相似,但“===”甚至做大小写语句

"test" == "test"  #=> true
"test" === "test" #=> true

这里的区别

String === "test"   #=> true
String == "test"  #=> false

评论

3赞 mwfearnley 9/16/2015
它们的工作方式并不相似,尽管当那时 .但功能更强大。 不对称,并且意味着与 截然不同的东西,更不用说了。a==ba===ba===b===a===bb===aa==b
-9赞 Tom Phan 4/26/2014 #3

我为上述所有内容写了一个简单的测试。

def eq(a, b)
  puts "#{[a, '==',  b]} : #{a == b}"
  puts "#{[a, '===', b]} : #{a === b}"
  puts "#{[a, '.eql?', b]} : #{a.eql?(b)}"
  puts "#{[a, '.equal?', b]} : #{a.equal?(b)}"
end

eq("all", "all")
eq(:all, :all)
eq(Object.new, Object.new)
eq(3, 3)
eq(1, 1.0)
54赞 Andreas Rayo Kniep 4/6/2015 #4

我喜欢 jtbandes 答案,但由于它很长,我将添加我自己的紧凑答案:

=====eql?, equal?
是 4 个比较器,即。在 Ruby 中比较 2 个对象的 4 种方法。
因为在 Ruby 中,所有的比较器(以及大多数运算符)实际上都是方法调用,你可以自己更改、覆盖和定义这些比较方法的语义。然而,重要的是要理解,当 Ruby 的内部语言结构使用哪个比较器时:

==(值比较)
Ruby 在任何地方都使用 :== 来比较 2 个对象的值,例如。哈希值:

{a: 'z'}  ==  {a: 'Z'}    # => false
{a: 1}    ==  {a: 1.0}    # => true

===(大小写比较)
Ruby 在 case/when 构造中使用 :===。以下代码片段在逻辑上是相同的:

case foo
  when bar;  p 'do something'
end

if bar === foo
  p 'do something'
end

eql? (哈希键比较)
Ruby 使用 :eql?(结合方法哈希)来比较哈希键。在大多数类中:eql?与 :== 相同。
了解 :eql?仅当您想要创建自己的特殊类时才重要:

class Equ
  attr_accessor :val
  alias_method  :initialize, :val=
  def hash()           self.val % 2             end
  def eql?(other)      self.hash == other.hash  end
end

h = {Equ.new(3) => 3,  Equ.new(8) => 8,  Equ.new(15) => 15}    #3 entries, but 2 are :eql?
h.size            # => 2
h[Equ.new(27)]    # => 15

注意:常用的 Ruby 类集也依赖于 Hash-key-comparison。

equal?(对象标识比较)
Ruby 使用 :equal?检查两个对象是否相同。此方法(类 BasicObject)不应被覆盖。

obj = obj2 = 'a'
obj.equal? obj2       # => true
obj.equal? obj.dup    # => false

评论

37赞 odigity 10/4/2015
这是一个很好的答案,但它几乎和 jtbandes 的一样长。:)
3赞 Cary Swoveland 10/18/2018
@odigity,大约70%的长度。我能想到很多事情来花这30%的钱。
0赞 Andrey Tarantsov 7/3/2019
我认为这个例子非常具有误导性。 是一种相等性比较,与哈希的计算方式一致,即 保证.它不是简单地比较哈希码。eql?eql?a.eql?(b)a.hash == b.hash
0赞 Alexis Wilke 10/12/2019
案例对比真的等同于和不等同吗?我希望后者是正确的,这很重要,因为编译器调用左侧:===''bar === foofoo === bar
0赞 Andreas Rayo Kniep 10/22/2019
据我所知,它是:Ruby 在左侧使用 case 值,在右侧使用 case 变量。这可能与避免 NPE(空指针异常)有关。bar === foo
10赞 kalibbala 4/12/2015 #5

Ruby 公开了几种不同的处理相等的方法:

a.equal?(b) # object identity - a and b refer to the same object

a.eql?(b) # object equivalence - a and b have the same value

a == b # object equivalence - a and b have the same value with type conversion.

点击下面的链接继续阅读,它给了我一个清晰的总结。

https://www.relishapp.com/rspec/rspec-expectations/v/2-0/docs/matchers/equality-matchers

希望它能帮助到其他人。

41赞 BrunoF 5/30/2016 #6

相等运算符:== 和 !=

== 运算符,也称为相等或双相等,如果两个对象相等,则返回 true,如果两个对象不相等,则返回 false。

"koan" == "koan" # Output: => true

!= 运算符,也称为不等式,与 == 相反。如果两个对象不相等,则返回 true,如果它们相等,则返回 false。

"koan" != "discursive thought" # Output: => true

请注意,具有相同元素且顺序不同的两个数组不相等,同一字母的大写和小写版本不相等,依此类推。

在比较不同类型的数字(例如整数和浮点数)时,如果它们的数值相同,则 == 将返回 true。

2 == 2.0 # Output: => true

平等?

与测试两个操作数是否相等的 == 运算符不同,equal 方法检查两个操作数是否引用同一对象。这是 Ruby 中最严格的平等形式。

例: a = “禅” b = “禅”

a.object_id  # Output: => 20139460
b.object_id  # Output :=> 19972120

a.equal? b  # Output: => false

在上面的示例中,我们有两个具有相同值的字符串。但是,它们是两个不同的对象,具有不同的对象 ID。因此,平等?方法将返回 false。

让我们再试一次,只是这次 b 将引用 a。请注意,两个变量的对象 ID 相同,因为它们指向同一个对象。

a = "zen"
b = a

a.object_id  # Output: => 18637360
b.object_id  # Output: => 18637360

a.equal? b  # Output: => true

EQL?

在 Hash 类中,eql?方法 它用于测试密钥是否相等。需要一些背景来解释这一点。在一般的计算上下文中,哈希函数采用任何大小的字符串(或文件)并生成固定大小的字符串或整数,称为哈希码,通常称为哈希。一些常用的哈希码类型是 MD5、SHA-1 和 CRC。它们用于加密算法、数据库索引、文件完整性检查等。某些编程语言(如 Ruby)提供称为哈希表的集合类型。哈希表是类似字典的集合,它成对存储数据,由唯一键及其相应的值组成。在后台,这些密钥存储为哈希码。哈希表通常简称为哈希。请注意单词 hash 如何指代哈希码或哈希表。在 Ruby 编程的上下文中,hash 这个词几乎总是指类似字典的集合。

Ruby 提供了一个称为 hash 的内置方法,用于生成哈希码。在下面的示例中,它接受一个字符串并返回一个哈希码。请注意,具有相同值的字符串始终具有相同的哈希码,即使它们是不同的对象(具有不同的对象 ID)。

"meditation".hash  # Output: => 1396080688894079547
"meditation".hash  # Output: => 1396080688894079547
"meditation".hash  # Output: => 1396080688894079547

哈希方法在 Kernel 模块中实现,该模块包含在 Object 类中,该类是所有 Ruby 对象的默认根。某些类(如 Symbol 和 Integer)使用默认实现,而其他类(如 String 和 Hash)则提供自己的实现。

Symbol.instance_method(:hash).owner  # Output: => Kernel
Integer.instance_method(:hash).owner # Output: => Kernel

String.instance_method(:hash).owner  # Output: => String
Hash.instance_method(:hash).owner  # Output: => Hash

在 Ruby 中,当我们将某些东西存储在哈希(集合)中时,作为键(例如字符串或符号)提供的对象被转换并存储为哈希码。稍后,当从哈希(集合)中检索元素时,我们提供了一个对象作为键,该对象被转换为哈希码并与现有键进行比较。如果存在匹配项,则返回相应项的值。比较是使用 eql?引擎盖下的方法。

"zen".eql? "zen"    # Output: => true
# is the same as
"zen".hash == "zen".hash # Output: => true

在大多数情况下,eql?方法的行为类似于 == 方法。但是,也有一些例外。例如,eql?将整数与浮点数进行比较时不执行隐式类型转换。

2 == 2.0    # Output: => true
2.eql? 2.0    # Output: => false
2.hash == 2.0.hash  # Output: => false

大小写相等运算符:===

Ruby 的许多内置类,如 String、Range 和 Regexp,都提供了自己的 === 运算符实现,也称为大小写相等、三等或三等。由于它在每个类中的实现方式不同,因此它的行为会根据调用它的对象类型而有所不同。通常,如果右侧的对象“属于”或“是”左侧对象的成员,则返回 true。例如,它可用于测试对象是否是类(或其子类之一)的实例。

String === "zen"  # Output: => true
Range === (1..2)   # Output: => true
Array === [1,2,3]   # Output: => true
Integer === 2   # Output: => true

使用其他可能最适合该工作的方法也可以获得相同的结果。通常最好在不牺牲效率和简洁性的情况下,通过尽可能明确地编写易于阅读的代码。

2.is_a? Integer   # Output: => true
2.kind_of? Integer  # Output: => true
2.instance_of? Integer # Output: => false

请注意,最后一个示例返回 false,因为像 2 这样的整数是 Fixnum 类的实例,该类是 Integer 类的子类。 ===、is_a?instance_of?如果对象是给定类或任何子类的实例,则方法返回 true。instance_of 方法更严格,仅当对象是该确切类的实例而不是子类时才返回 true。

is_a?kind_of?方法在 Kernel 模块中实现,该模块由 Object 类混合。两者都是同一方法的别名。让我们验证一下:

Kernel.instance_method(:kind_of?) == Kernel.instance_method(:is_a?) # 输出: => true

=== 的范围实现

在范围对象上调用 === 运算符时,如果右侧的值落在左侧的范围内,则返回 true。

(1..4) === 3  # Output: => true
(1..4) === 2.345 # Output: => true
(1..4) === 6  # Output: => false

("a".."d") === "c" # Output: => true
("a".."d") === "e" # Output: => false

请记住,=== 运算符调用左侧对象的 === 方法。所以 (1..4) === 3 等价于 (1..4).=== 3。换句话说,左侧操作数的类将定义将调用 === 方法的哪个实现,因此操作数位置不可互换。

=== 的正则表达式实现

如果右侧的字符串与左侧的正则表达式匹配,则返回 true。 /zen/ === “今天练习坐禅” # 输出: => true # 与 “今天练习坐禅”=~ /zen/

在 case/when 语句中隐式使用 === 运算符

此运算符也用于 case/when 语句的后台。这是它最常见的用途。

minutes = 15

case minutes
  when 10..20
    puts "match"
  else
    puts "no match"
end

# Output: match

在上面的示例中,如果 Ruby 隐式使用了双等运算符 (==),则范围 10..20 不会被视为等于整数,例如 15。它们之所以匹配,是因为在所有 case/when 语句中都隐式使用了三等运算符 (===)。上面示例中的代码等效于:

if (10..20) === minutes
  puts "match"
else
  puts "no match"
end

模式匹配运算符:=~ 和 !~

=~(等波浪号)和 !~(bang-tilde)运算符用于将字符串和符号与正则表达式模式进行匹配。

String 和 Symbol 类中 =~ 方法的实现需要正则表达式(Regexp 类的实例)作为参数。

"practice zazen" =~ /zen/   # Output: => 11
"practice zazen" =~ /discursive thought/ # Output: => nil

:zazen =~ /zen/    # Output: => 2
:zazen =~ /discursive thought/  # Output: => nil

Regexp 类中的实现需要字符串或符号作为参数。

/zen/ =~ "practice zazen"  # Output: => 11
/zen/ =~ "discursive thought" # Output: => nil

在所有实现中,当字符串或符号与正则表达式模式匹配时,它会返回一个整数,该整数是匹配项的位置(索引)。如果没有匹配项,则返回 nil。请记住,在 Ruby 中,任何整数值都是“truthy”,nil 是“falsy”,因此 =~ 运算符可以在 if 语句和三元运算符中使用。

puts "yes" if "zazen" =~ /zen/ # Output: => yes
"zazen" =~ /zen/?"yes":"no" # Output: => yes

模式匹配运算符对于编写较短的 if 语句也很有用。例:

if meditation_type == "zazen" || meditation_type == "shikantaza" || meditation_type == "kinhin"
  true
end
Can be rewritten as:
if meditation_type =~ /^(zazen|shikantaza|kinhin)$/
  true
end

!~ 运算符与 =~ 相反,如果没有匹配项,则返回 true,如果有匹配项,则返回 false。

有关详细信息,请参阅此博客文章

评论

7赞 Qqwy 6/24/2016
我发现这是一个比目前接受的答案更好的答案,因为它提供了很好的例子,并且对不同类型的平等意味着什么以及它们为什么存在/在哪里使用它们不那么模棱两可。
1赞 Mike R 7/3/2016
非常详细的答案,但在我的 irb (ruby v 2.2.1) 上返回 false:zen === "zen"
0赞 BrunoF 7/3/2016
@MikeR 谢谢你让我知道。我已经更正了答案。
0赞 user1883793 10/27/2016
我想你是说type_of?“请注意,最后一个示例返回 false,因为像 2 这样的整数是 Fixnum 类的实例,该类是 Integer 类的子类。 ===,is_a?instance_of?(TYPE_OF?)“?
1赞 Abdullah Fadhel 9/19/2017
我喜欢这个答案。谢谢
10赞 akuhn 3/12/2017 #7

我想对运营商进行扩展。===

===不是相等运算符!

不。

让我们真正了解这一点。

您可能熟悉 Javascript 和 PHP 中的相等运算符,但这不是 Ruby 中的相等运算符,并且具有根本不同的语义。===

那怎么办呢?===

===是模式匹配运算符!

  • ===匹配正则表达式
  • ===检查范围成员资格
  • ===检查是否为类的实例
  • ===调用 lambda 表达式
  • ===有时检查是否相等,但大多数情况下不会

那么,这种疯狂是如何理解的呢?

  • Enumerable#grep内部使用===
  • case when内部使用的语句===
  • 有趣的事实,内部使用rescue===

这就是为什么您可以在语句中使用正则表达式、类和范围,甚至 lambda 表达式的原因。case when

一些例子

case value
when /regexp/
  # value matches this regexp
when 4..10
  # value is in range
when MyClass
  # value is an instance of class
when ->(value) { ... }
  # lambda expression returns true
when a, b, c, d
  # value matches one of a through d with `===`
when *array
  # value matches an element in array with `===`
when x
  # values is equal to x unless x is one of the above
end

所有这些示例也适用于方法。pattern === valuegrep

arr = ['the', 'quick', 'brown', 'fox', 1, 1, 2, 3, 5, 8, 13]
arr.grep(/[qx]/)                                                                                                                            
# => ["quick", "fox"]
arr.grep(4..10)
# => [5, 8]
arr.grep(String)
# => ["the", "quick", "brown", "fox"]
arr.grep(1)
# => [1, 1]
0赞 Pratik Gaikwad 6/2/2021 #8
  1. .eql?- 如果 receiver 和参数具有相同的类型和相等的值,则此运算符返回 true。

例如 - 10.eql?(10.0) 是错误的。

  1. === - 它将测试 case 语句中的相等性。

例如 - (1...10) === 1 为 true

  1. == - 此运算符检查两个给定的操作数是否相等。如果等于,则返回 TRUE,否则返回 FALSE。

例如 - (1...10) == 1 为 false

更多例子请点击这里