"== 和 === 之间的区别"

335
在 swift 中似乎有两个等于运算符:双等于 (==) 和三等于 (===),它们之间有什么区别?
10个回答

263

!=====是身份运算符,用于确定两个对象是否具有相同的引用。

Swift还提供了两个身份运算符(===和!==),用于测试两个对象引用是否都指向同一个对象实例。

摘自:Apple Inc.《The Swift Programming Language》。 iBooks. https://itun.es/us/jEUH0.l


57
没错。从 ObjC 来说,== 就是 isEqual: 或者说是类定义的语义等价。Swift 中的 === 相当于 (Obj)C 中的 == — 指针相等或对象标识。 - rickster
@rickster,值也有内存位置吗?它们最终都在内存中的某个地方。难道你永远无法比较它们吗?或者说它们的内存位置没有提供任何有意义的值? - mfaani
2
至少有两种思考语言定义值类型与内存的方式。一种是每个名称到值的绑定(varlet)都是唯一的副本 - 因此创建指针是无意义的,因为您指向的值与您最初创建的值不同。另一种是Swift对值语义的定义抽象了存储 - 编译器可以自由地进行优化,包括从未将您的值存储在可访问的内存位置(寄存器,指令编码等)超出其使用行的范围。 - rickster

199

简而言之:

== 运算符检查它们实例的值是否相等,"等于"

=== 运算符检查引用是否指向同一个实例,"全等于"

详细解释:

类是引用类型,多个常量和变量可以在幕后引用同一个类的单个实例。类引用保留在运行时堆栈(RTS)中,它们的实例保留在内存的堆区域中。当你使用 == 控制相等性时,这意味着它们的实例彼此相等。不需要是相同的实例才能相等。为此,您需要向自定义类提供相等标准。默认情况下,自定义类和结构体不会收到等价操作的默认实现,称为“等于”运算符 == 和“不等于”运算符 !=。要实现此功能,您的自定义类需要符合 Equatable 协议并实现其 static func == (lhs:,rhs:)-> Bool 函数。

让我们看一个例子:

class Person : Equatable {
    let ssn: Int
    let name: String

    init(ssn: Int, name: String) {
        self.ssn = ssn
        self.name = name
    }

    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.ssn == rhs.ssn
    }
}

P.S.:由于社会安全号码(ssn)是唯一的,您无需比较他们的姓名是否相同。

let person1 = Person(ssn: 5, name: "Bob")
let person2 = Person(ssn: 5, name: "Bob")

if person1 == person2 {
   print("the two instances are equal!")
}

尽管person1和person2引用指向堆区的两个不同实例,但它们的实例是相等的,因为它们的ssn号码是相等的。 因此输出将是the two instance are equal!

if person1 === person2 {
   //It does not enter here
} else {
   print("the two instances are not identical!")
}

===运算符检查引用是否指向相同的实例,也就是"identical to"。由于person1和person2在堆内存中有两个不同的实例,它们不是相同的,因此输出结果为the two instance are not identical!

let person3 = person1

P.S:类是引用类型,person1的引用被复制到person3中,因此两个引用都指向堆区中的同一实例。

if person3 === person1 {
   print("the two instances are identical!")
}

它们是相同的,输出将为the two instances are identical!


63

在Objective-C和Swift中,==!=运算符测试数字值的相等性(例如,在Objective-C中的NSInteger, NSUInteger, int,以及在Swift中的IntUInt等)。对于对象(Objective-C中的NSObject / NSNumber及其子类以及Swift中的引用类型),==!=测试对象/引用类型是否是完全相同的东西 - 即,具有相同的哈希值 - 或者不是完全相同的东西。

let a = NSObject()
let b = NSObject()
let c = a
a == b // false
a == c // true

Swift的恒等性运算符===!==,检查引用相等性--因此,在我看来,它们应该被称为引用相等性运算符

a === b // false
a === c // true

值得一提的是,在Swift中,自定义引用类型(不继承符合Equatable协议的类)不会自动实现等于操作符,但身份相等性运算符仍然适用。此外,通过实现==!=也会被自动实现。

class MyClass: Equatable {
  let myProperty: String

  init(s: String) {
    myProperty = s
  }
}

func ==(lhs: MyClass, rhs: MyClass) -> Bool {
  return lhs.myProperty == rhs.myProperty
}

let myClass1 = MyClass(s: "Hello")
let myClass2 = MyClass(s: "Hello")
myClass1 == myClass2 // true
myClass1 != myClass2 // false
myClass1 === myClass2 // false
myClass1 !== myClass2 // true

这些等式操作符对于其他类型(例如结构体)在两种语言中都没有实现。然而,在Swift中可以创建自定义操作符,例如,您可以创建一个用于检查CGPoint等式的运算符。

infix operator <==> { precedence 130 }
func <==> (lhs: CGPoint, rhs: CGPoint) -> Bool {
  return lhs.x == rhs.x && lhs.y == rhs.y
}

let point1 = CGPoint(x: 1.0, y: 1.0)
let point2 = CGPoint(x: 1.0, y: 1.0)
point1 <==> point2 // true

3
抱歉,但在Obj-C中,“==”运算符并不比较相等性,而是像C语言一样比较指针引用(对象标识)。 - Motti Shneor
在Objective-C中,==不会测试NSNumber的相等性。NSNumber是一个NSObject,因此它测试的是身份。它有时能够工作的原因是由于标记指针/缓存对象字面量。当比较非字面量和足够大的数字时,在32位设备上会失败。 - Accatyyc

50

在Swift 3及以上版本中

===(或!==

  • 检查值是否完全相同 (都指向同一内存地址)
  • 比较引用类型
  • 类似于Obj-C中的==(指针相等性)

==(或!=

  • 检查值是否相等
  • 比较值类型
  • 类似于Obj-C行为中的默认isEqual:

这里我比较了三个实例 (类是引用类型)

class Person {}

let person = Person()
let person2 = person
let person3 = Person()

person === person2 // true
person === person3 // false

在Swift中,您还可以重写isEqual:override func isEqual(_ object: Any?) -> Bool {} - Thomas Elliot

37

Swift中使用===存在一些微妙之处,这不仅仅是指针运算。在Objective-C中,您可以使用==比较任何两个指针(即NSObject *),但在Swift中不再适用,因为类型在编译期间扮演了更重要的角色。

Playground将为您提供:

1 === 2                    // false
1 === 1                    // true
let one = 1                // 1
1 === one                  // compile error: Type 'Int' does not conform to protocol 'AnyObject'
1 === (one as AnyObject)   // true (surprisingly (to me at least))

对于字符串,我们需要逐渐适应这个:

var st = "123"                                 // "123"
var ns = (st as NSString)                      // "123"
st == ns                                       // true, content equality
st === ns                                      // compile error
ns === (st as NSString)                        // false, new struct
ns === (st as AnyObject)                       // false, new struct
(st as NSString) === (st as NSString)          // false, new structs, bridging is not "free" (as in "lunch")
NSString(string:st) === NSString(string:st)    // false, new structs
var st1 = NSString(string:st)                  // "123"
var st2 = st1                                  // "123"
st1 === st2                                    // true
var st3 = (st as NSString)                     // "123"
st1 === st3                                    // false
(st as AnyObject) === (st as AnyObject)        // false

但你也可以像下面这样玩得开心:

var st4 = st             // "123"
st4 == st                // true
st4 += "5"               // "1235"
st4 == st                // false, not quite a reference, copy on write semantics

我相信你能想出更多有趣的案例 :-)

关于 Swift 3 的更新(根据 Jakub Truhlář 的评论所建议)

1===2                                    // Compiler error: binary operator '===' cannot be applied to two 'Int' operands
(1 as AnyObject) === (2 as AnyObject)    // false
let two = 2
(2 as AnyObject) === (two as AnyObject)  // false (rather unpleasant)
(2 as AnyObject) === (2 as AnyObject)    // false (this makes it clear that there are new objects being generated)

这似乎更符合Type 'Int' does not conform to protocol 'AnyObject',但接下来我们会得到:

type(of:(1 as AnyObject))                // _SwiftTypePreservingNSNumber.Type

但是显式转换明确表明可能会发生一些事情。 在字符串方面,只要我们import CocoaNSString仍然可用。

var st = "123"                                 // "123"
var ns = (st as NSString)                      // "123"
st == ns                                       // Compile error with Fixit: 'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?
st == ns as String                             // true, content equality
st === ns                                      // compile error: binary operator '===' cannot be applied to operands of type 'String' and 'NSString'
ns === (st as NSString)                        // false, new struct
ns === (st as AnyObject)                       // false, new struct
(st as NSString) === (st as NSString)          // false, new structs, bridging is not "free" (as in "lunch")
NSString(string:st) === NSString(string:st)    // false, new objects
var st1 = NSString(string:st)                  // "123"
var st2 = st1                                  // "123"
st1 === st2                                    // true
var st3 = (st as NSString)                     // "123"
st1 === st3                                    // false
(st as AnyObject) === (st as AnyObject)        // false

仍然有两个 String 类是令人困惑的,但放弃隐式转换可能会使其稍微更容易理解一些。


3
在Swift 3中,您不能使用===运算符比较Ints - Jakub Truhlář
每当你说正在创建一个“新结构体”时,实际上正在创建一个新对象(类型)。对于结构体来说,“===”是没有意义的,因为它们是值类型。特别是,有三种类型需要记住:字面量类型,例如1或“foo”,它们还没有绑定到变量,并且通常只影响编译,因为在运行时通常不处理它们;结构体类型,例如IntString,这是当您将字面量分配给变量时得到的类型;以及类,例如AnyObjectNSString - saagarjha

13
例如,如果您创建了一个类的两个实例,例如myClass
var inst1 = myClass()
var inst2 = myClass()

你可以比较这些实例,

if inst1 === inst2

引用:

你可以使用它测试两个对象引用是否都指向同一个对象实例。

节选自:Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/sk/jEUH0.l


实际上这只是为了展示我们如何进行比较。以上语句的结果为false,因为每个实例inst1和inst2都会分配单独的内存。 如果 inst1 === inst2 { print("这里我们得到false") } var inst3 = inst1 如果 inst1 === inst3 { print("这里我们得到true") } - Vin
1
@vin 当然。正如摘录所说,_测试两个对象引用是否都指向同一个对象实例_。var inst1 = myClass() var inst2 = myClass() 两个不同的实例。 - clt60
之前我把它和isKindOfClass联系起来了,但我意识到它并不是这样的! - Vin

11
在Swift中,我们有 === 符号,它表示两个对象是否引用相同的引用地址。
class SomeClass {

    var a: Int

    init(_ a: Int) {
        self.a = a
    }
}

var someClass1 = SomeClass(4)
var someClass2 = SomeClass(4)
someClass1 === someClass2 // false
someClass2 = someClass1
someClass1 === someClass2 // true

4

这只是与Any对象相关的小贡献。

我正在处理与NotificationCenter相关的单元测试,它使用Any作为我想要比较相等性的参数。

然而,由于Any不能用于相等操作,因此必须进行更改。最终,我采用了以下方法,使我在特定情况下获得了相等性,在这里通过简单的示例展示:

func compareTwoAny(a: Any, b: Any) -> Bool {
    return ObjectIdentifier(a as AnyObject) == ObjectIdentifier(b as AnyObject)
}

该函数利用了ObjectIdentifier,它为对象提供了唯一的地址,使我能够进行测试。
但需要注意的是,根据上面链接中的Apple公司说明,ObjectIdentifier仅适用于类实例和元类型,对于结构体、枚举、函数或元组等没有身份概念。

2

==用于检查两个变量是否相等,即2 == 2。但是在===的情况下,它表示相等性,即如果两个实例引用同一个对象,则会创建一个引用,并由许多其他实例持有,例如,在类的情况下。


1

Swift 4: 另一个使用单元测试的示例,只能使用 === 运算符

注意:下面的测试使用 == 失败,但使用 === 成功

func test_inputTextFields_Delegate_is_ViewControllerUnderTest() {

        //instantiate viewControllerUnderTest from Main storyboard
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        viewControllerUnderTest = storyboard.instantiateViewController(withIdentifier: "StoryBoardIdentifier") as! ViewControllerUnderTest 
        let _ = viewControllerUnderTest.view

        XCTAssertTrue(viewControllerUnderTest.inputTextField.delegate === viewControllerUnderTest) 
    }

而类别是

class ViewControllerUnderTest: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var inputTextField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        inputTextField.delegate = self
    }
}

如果您使用==在单元测试中会出现错误,错误信息为:Binary operator '==' cannot be applied to operands of type 'UITextFieldDelegate?' and 'ViewControllerUnderTest!'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接