ChatGPT解决这个技术问题 Extra ChatGPT

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

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

默认情况下 === 也调用 == 调用 equal?...好吧,如果所有这三个方法都没有被覆盖,那么我猜 =====equal? 做同样的事情?

现在是 eql?。这是做什么的(默认情况下)?它会调用操作数的哈希/ID吗?

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

我刚刚开始了一个 irb,并得到了以下与你的相矛盾的结果......所有这 3 个都是正确的:"a" == "a""a" === "a""a".eql? "a"。但这是错误的:"a".equal? "a"(我的是 ruby 1.9.2-p180)
@Peter:那是因为字符串覆盖了所有的相等运算符。尝试使用 a = Object.new; b = Object.new,然后所有 =====.equal?.eql? 将针对 aa 返回 true,对于 ab 将返回 false。

C
Community

我将在这里大量引用 the Object documentation,因为我认为它有一些很好的解释。我鼓励您阅读它以及这些方法的文档,因为它们在其他类中被覆盖,例如 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}

== — 通用的“平等”

在对象级别,== 只有当 obj 和 other 是同一个对象时才返回 true。通常,此方法在后代类中被覆盖以提供特定于类的含义。

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

=== — 大小写相等

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

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

范围

正则表达式

过程(在 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

有关 case+Regex 如何使代码更简洁的简洁示例,请参阅 my answer here。当然,通过提供您自己的 === 实现,您可以获得自定义 case 语义。

情商? — 哈希相等

情商?如果 obj 和 other 引用相同的哈希键,则方法返回 true。 Hash 使用它来测试成员是否相等。对于 Object 类的对象,eql?是 == 的同义词。子类通常通过别名 eql?到他们重写的 == 方法,但也有例外。例如,数值类型跨 == 执行类型转换,但不跨 eql?,所以: 1 == 1.0 #=> true 1.eql? 1.0 #=> 假

因此,您可以随意覆盖它以供自己使用,或者您可以覆盖 == 并使用 alias :eql? :==,这样这两种方法的行为方式相同。

平等的? ——身份比较

不像==,等于?方法永远不应该被子类覆盖:它用于确定对象身份(即,a.equal?(b) iff a 与 b 是同一个对象)。

这实际上是指针比较。


正如我从您的回答中了解到的那样,严格性是:平等? < 等价物? < == < ===。通常,您使用 ==。对于一些松散的目的,您使用 ===。对于严格的情况,你使用 eql?,而对于完整的身份,你使用 equal?。
文档中没有强制甚至没有建议严格的概念,只是碰巧 Numeric 以比 == 更严格的方式处理它。这真的取决于课程的作者。 === 很少在 case 语句之外使用。
== 在更大/更小方面也是平等的。即,如果包含 Comparable,它将根据 <=> 返回 0 来定义。这就是 1 == 1.0 返回 true 的原因。
@sawa 我通常认为 === 的意思是“匹配”(大致)。如,“正则表达式是否匹配字符串”或“范围是否匹配(包括)数字”。
有趣的事实:官方文档现在链接到此答案(请参阅 ruby-doc.org/core-2.1.5/…)。
A
Andreas Rayo Kniep

我喜欢 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 类 Set 也依赖于 Hash-key-comparison。

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

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

这是一个很好的答案,但它几乎和 jtbandes 的一样长。 :)
@odigity,大约是 70%。我可以想出很多东西来花掉这 30%。
我认为 eql? 的示例非常具有误导性。 eql? 是一个相等比较,它与哈希的计算方式一致,即 a.eql?(b) 保证 a.hash == b.hash。它只是简单地比较哈希码。
大小写比较真的等同于 bar === foo 而不是 foo === bar?我希望后者是正确的,这很重要,因为编译器调用左侧:===`'
据我所知,它是 bar === foo:Ruby 使用左侧的 case 值和右侧的 case 变量。这可能与避免 NPE(空指针异常)有关。
e
erbridge

等式运算符:== 和 !=

== 运算符,也称为相等或双重相等,如果两个对象相等则返回 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

情商?

在 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

hash 方法在 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 类的实例,Fixnum 类是 Integer 类的子类。 ===, is_a?和instance_of?如果对象是给定类或任何子类的实例,则方法返回 true。 instance_of 方法更严格,仅当对象是该确切类的实例而不是子类时才返回 true。

is_a?和种类?方法在 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/ === "practice zazen today" # 输出:=> true # 与 "practice zazen today" 相同=~ /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

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

=~ (equal-tilde) 和 !~ (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

在所有实现中,当字符串或符号与 Regexp 模式匹配时,它会返回一个整数,该整数是匹配的位置(索引)。如果没有匹配,则返回 nil。请记住,在 Ruby 中,任何整数值都是“真”而 nil 是“假”,因此 =~ 运算符可用于 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。

如需更多信息,请访问 this blog post


我发现这是一个比当前接受的答案更好的答案,因为它提供了很好的示例,并且对于不同类型的平等的含义以及它们存在的原因/使用它们的位置不那么模棱两可。
非常详细的答案,但在我的 irb (ruby v 2.2.1) :zen === "zen" 上返回 false
@MikeR 谢谢你让我知道。我已经更正了答案。
我想你的意思是type_of? “请注意最后一个示例返回 false,因为像 2 这样的整数是 Fixnum 类的实例,它是 Integer 类的子类。===、is_a? 和 instance_of? (TYPE_OF?)”?
我喜欢这个答案。谢谢
M
Mark

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

希望它可以帮助别人。


a
akuhn

我想扩展 === 运算符。

=== 不是等式运算符!

不是。

让我们真正理解这一点。

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

那么 === 做了什么?

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

=== 匹配正则表达式

=== 检查范围成员资格

=== 检查是否是类的实例

=== 调用 lambda 表达式

=== 有时会检查相等性,但大多数情况下不会

那么这种疯狂有什么意义呢?

Enumerable#grep 在内部使用 ===

语句在内部使用 === 的情况

有趣的事实,rescue 在内部使用 ===

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

一些例子

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 === value 以及 grep 方法。

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]

K
Kishore Mohan

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

== #--- 泛型相等

两者的工作方式相似,但“===”甚至可以做案例陈述

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

这里的区别

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

它们工作相似,即使当 a==ba===b 时往往是正确的。但是 a===b 更强大。 === 不是对称的,a===b 的含义与 b===a 完全不同,更不用说 a==b
p
pratik

.eql? - 如果接收者和参数具有相同的类型和相等的值,则此运算符返回 true。

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

=== - 它会在 case 语句中测试相等性。

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

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

例如 - (1...10) == 1 是假的

更多示例 click here


T
Tom Phan

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

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)