我试图了解这四种方法之间的区别。我知道默认情况下 ==
调用方法 equal?
当两个操作数都引用完全相同的对象时返回 true。
默认情况下 ===
也调用 ==
调用 equal?
...好吧,如果所有这三个方法都没有被覆盖,那么我猜 ===
、==
和 equal?
做同样的事情?
现在是 eql?
。这是做什么的(默认情况下)?它会调用操作数的哈希/ID吗?
为什么 Ruby 有这么多等号?他们应该在语义上有所不同吗?
"a" == "a"
、"a" === "a"
和 "a".eql? "a"
。但这是错误的:"a".equal? "a"
(我的是 ruby 1.9.2-p180)
a = Object.new; b = Object.new
,然后所有 ==
、===
、.equal?
、.eql?
将针对 a
与 a
返回 true
,对于 a
与 b
将返回 false。
我将在这里大量引用 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 是同一个对象)。
这实际上是指针比较。
我喜欢 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
eql?
的示例非常具有误导性。 eql?
是一个相等比较,它与哈希的计算方式一致,即 a.eql?(b)
保证 a.hash == b.hash
。它不只是简单地比较哈希码。
bar === foo
而不是 foo === bar
?我希望后者是正确的,这很重要,因为编译器调用左侧:===`'
bar === foo
:Ruby 使用左侧的 case 值和右侧的 case 变量。这可能与避免 NPE(空指针异常)有关。
等式运算符:== 和 !=
== 运算符,也称为相等或双重相等,如果两个对象相等则返回 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。
:zen === "zen"
上返回 false
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
希望它可以帮助别人。
我想扩展 ===
运算符。
===
不是等式运算符!
不是。
让我们真正理解这一点。
您可能熟悉 ===
作为 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]
=== #---大小写相等
== #--- 泛型相等
两者的工作方式相似,但“===”甚至可以做案例陈述
"test" == "test" #=> true
"test" === "test" #=> true
这里的区别
String === "test" #=> true
String == "test" #=> false
a==b
时 a===b
时往往是正确的。但是 a===b
更强大。 ===
不是对称的,a===b
的含义与 b===a
完全不同,更不用说 a==b
。
.eql? - 如果接收者和参数具有相同的类型和相等的值,则此运算符返回 true。
例如 - 10.eql?(10.0) 是假的。
=== - 它会在 case 语句中测试相等性。
例如 - (1...10) === 1 为真
== - 此运算符检查两个给定操作数是否相等。如果等于,则返回 TRUE,否则返回 FALSE。
例如 - (1...10) == 1 是假的
更多示例 click here
我为以上所有内容编写了一个简单的测试。
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)
Numeric
以比==
更严格的方式处理它。这真的取决于课程的作者。===
很少在case
语句之外使用。===
的意思是“匹配”(大致)。如,“正则表达式是否匹配字符串”或“范围是否匹配(包括)数字”。