Swift 结构体
深入了解 Swift 结构体,并详细探讨值类型和引用类型的概念。
在前面的课程中,我们已经学习了 Swift 中的类。本节课将介绍结构体(struct)的使用。虽然从表面上看,结构体和类非常相似,但在选择使用哪种类型时,需要理解一些重要的区别。本节课将讲解如何声明和使用结构体,探讨结构体与类之间的差异,并引入值类型和引用类型的概念。
Swift 结构体概览
与类一样,结构体是面向对象编程的基础,提供了一种将数据和功能封装为可重用实例的方式。结构体的声明方式与类类似,唯一的不同是使用 struct 关键字代替 class 关键字。例如,下面的代码定义了一个简单的结构体,包含一个 String 类型的变量、一个初始化器和一个方法:
1struct SampleStruct { 2 3 var name: String 4 5 init(name: String) { 6 self.name = name 7 } 8 9 func buildHelloMsg() -> String { 10 return "Hello " + name 11 } 12}
将上述结构体声明与对应的类声明进行比较:
1class SampleClass { 2 3 var name: String 4 5 init(name: String) { 6 self.name = name 7 } 8 9 func buildHelloMsg() -> String { 10 return "Hello " + name 11 } 12}
除了使用 struct 关键字替代 class 外,两者的声明完全相同。两种类型的实例也使用相同的语法创建:
1let myStruct = SampleStruct(name: "Mark") 2let myClass = SampleClass(name: "Mark")
与类类似,结构体也可以被扩展,能够遵循协议,并且可以包含初始化器。
由于类和结构体具有高度的相似性,因此理解它们之间的区别至关重要。在深入探讨最重要的区别之前,首先需要了解 值类型 和 引用类型 的概念。
值类型 vs 引用类型
尽管结构体和类在外观上相似,但当结构体或类的实例被复制或作为参数传递给方法或函数时,它们的行为存在显著差异。这是因为结构体实例是值类型,而类实例是引用类型。
当一个结构体实例被复制或传递给方法时,会创建该实例的一个完整副本,包括其中包含的所有数据。这意味着副本拥有自己独立的数据版本,与原始结构体实例无关。换句话说,在运行的应用程序中,可以存在多个结构体实例的副本,每个副本都持有其自身独立的数据副本。对其中一个副本的修改不会影响其他副本。
相比之下,当一个类实例被复制或作为参数传递时,仅复制或传递的是指向该类实例在内存中位置的引用。通过这些引用对实例所做的任何更改,都会作用于同一个实例。换句话说,只有一个类实例,但可能有多个引用指向它。通过任意一个引用对实例数据进行修改,所有其他引用看到的数据也会随之改变。
为了直观展示值类型和引用类型的行为,考虑以下代码:
1struct SampleStruct { 2 3 var name: String 4 5 init(name: String) { 6 self.name = name 7 } 8 9 func buildHelloMsg() -> String { 10 return "Hello " + name 11 } 12} 13 14let myStruct1 = SampleStruct(name: "Mark") 15print(myStruct1.name)
执行这段代码后,输出结果为 “Mark”。现在修改代码,创建 myStruct1 的一个副本,修改其 name 属性,并分别打印两个实例的名称:
1struct SampleStruct { 2 3 var name: String 4 5 init(name: String) { 6 self.name = name 7 } 8 9 func buildHelloMsg() -> String { 10 return "Hello " + name 11 } 12} 13 14let myStruct1 = SampleStruct(name: "Mark") 15var myStruct2 = myStruct1 16myStruct2.name = "David" 17 18print(myStruct1.name) 19print(myStruct2.name)
执行后,输出结果为:
1Mark 2David
显然,名称的更改只影响了 myStruct2,因为它是 myStruct1 的一个实际副本,拥有自己的数据副本,如图所示:
再来看类的例子:
1class SampleClass { 2 3 var name: String 4 5 init(name: String) { 6 self.name = name 7 } 8 9 func buildHelloMsg() -> String { 10 return "Hello " + name 11 } 12} 13 14let myClass1 = SampleClass(name: "Mark") 15var myClass2 = myClass1 16myClass2.name = "David" 17 18print(myClass1.name) 19print(myClass2.name)
执行后,输出结果为:
1David 2David
在这种情况下,name 属性的更改同时反映在 myClass1 和 myClass2 上,因为两者都是指向同一个类实例的引用,如图所示:
除了上述值类型和引用类型的区别外,结构体不支持继承和子类化,即一个结构体无法从另一个结构体继承。此外,结构体不能包含析构器(deinit)方法。最后,虽然可以在运行时识别类实例的类型,但无法对结构体做同样的操作。
注意: 通常情况下,除非需要类特有的功能,否则应优先使用结构体。
总结
Swift 的结构体和类都提供了创建实例的机制,用于定义属性、存储值并定义方法。尽管两者看起来很相似,但在实例被复制或传递给方法时,行为上有显著差异。类属于引用类型,而结构体属于值类型。当复制或传递结构体实例时,会创建一个全新的实例,包含独立的数据副本;而类实例则是通过引用传递或复制,所有引用都指向同一个实例。类独有的特性还包括支持继承、析构器以及在运行时识别类型的能力。
课程回顾
-
当传递值类型实例作为参数时,实际传递的是该实例的副本。
-
对某个值类型实例副本的修改,不会影响原始实例或其他副本。
-
传递引用类型实例时,传递的是对原始实例的引用,而非副本。
-
对任一引用所做的修改,都会反映在所有其他引用上。
-
结构体使用
struct关键字声明。 -
结构体实例是值类型。
-
类实例是引用类型。
测验
完成以下测验,以确保你理解了类与结构体之间的区别。
Quiz
Swift 类是以下哪种类型?