版本
Xcode 11.3.1
Swift 5.1.3
Swift 中结构体和类的功能很相近,本文中所讨论的大部分功能都可以用在结构体或者类上,因此将两者放在同一篇章里。
结构体和类对比
Swift 中结构体和类有很多共同点。两者都可以:
- 定义属性用于存储值
- 定义方法用于提供功能
- 定义下标操作用于通过下标语法访问它们的值
- 定义构造器用于设置初始值
- 通过扩展以增加默认实现之外的功能
- 遵循协议以提供某种标准功能
与结构体相比,类还有如下的附加功能:
- 继承允许一个类继承另一个类的特征
- 类型转换允许在运行时检查和解释一个类实例的类型
- 析构器允许一个类实例释放任何其所被分配的资源
- 引用计数允许对一个类的多次引用
选结构体还是类?
类支持的附加功能是以增加复杂性为代价的。按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:
- 结构体的主要目的是用来封装少量相关简单数据值。
- 有理由预计一个结构体实例在赋值或传递时,封装的数据将会被拷贝而不是被引用。
- 任何在结构体中储存的值类型属性,也将会被拷贝,而不是被引用。
- 结构体不需要去继承另一个已存在类型的属性或者行为。
举例来说,以下情境中适合使用结构体:
- 几何形状的大小,封装一个width属性和height属性,两者均为Double类型。
- 一定范围内的路径,封装一个start属性和length属性,两者均为Int类型。
- 三维坐标系内一点,封装x,y和z属性,三者均为Double类型。
类型定义 & 创建实例
// 定义结构体
struct Characteristic {
var height = 0
var weight = 0
}
// 定义类
class People {
var name: String?
var age = 0
var characteristic = Characteristic()
}
// 实例化
// 类的实例化
let people = People()
// 结构体的实例化
var characteristic1 = Characteristic()
// 结构体的另一种方法实例化 (成员逐一构造器,类没有这种方法)
let characteristic2 = Characteristic(height: 180, weight: 200)
people.name = "小明"
people.age = 18
people.characteristic.height = 180
people.characteristic.weight = 150
print("\(people.name!)的年龄是\(people.age)岁,身高是\(people.characteristic.height)厘米,体重是\(people.characteristic.weight)斤")
// 小明的年龄是18岁,身高是180厘米,体重是150斤
characteristic1.height = 100;
characteristic1.weight = 50;
print("特征1 身高为\(characteristic1.height), 体重为\(characteristic1.weight)")
// 特征1 身高为100, 体重为50
print("特征2 身高为\(characteristic2.height), 体重为\(characteristic2.weight)")
// 特征2 身高为180, 体重为200
结构体是值类型
值类型是这样一种类型,当它被赋值给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。
例如,声明一个特征3并用特征2的值赋值给前者,然后改变特征3的height和weight,发现特征2里的值没有跟随改变:
var characteristic3 = characteristic2
characteristic3.height = 200
characteristic3.weight = 300
print("特征2 身高为\(characteristic2.height), 体重为\(characteristic2.weight)")
print("特征3 身高为\(characteristic3.height), 体重为\(characteristic3.weight)")
// 特征2 身高为180, 体重为200
// 特征3 身高为200, 体重为300
实际上,Swift 中所有的基本类型:整数(integer)、浮点数(floating-point number)、布尔值(boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,其底层也是使用结构体实现的。
类是引用类型
与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,使用的是已存在实例的引用,而不是其拷贝。
例如,声明一个people1并把people赋值给他,然后改变people1的名字,发现原来的people也跟着改变了
let people1 = people
people1.name = "小红"
print("people 名称为\(people.name!)")
print("people1 名称为\(people1.name!)")
// people 名称为小红
// people1 名称为小红
需要注意的是 people1 被声明为常量而不是变量。然而你依然可以改变 people1.name 等属性,这是因为 people1这个常量的值并未改变。它们并不“存储”这个 People 实例,而仅仅是对 People 实例的引用。所以,改变的是底层 People 实例的 name 属性,而不是指向 People 的常量引用的值。
恒等运算符
因为类是引用类型,所以多个常量和变量可能在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)
判定两个常量或者变量是否引用同一个类实例有时很有用。为了达到这个目的,Swift 提供了两个恒等运算符:
- 相同(===)
- 不相同(!==)
if people1 === people {
print("people1 和 people 引用同一个实例")
}
// 打印 people1 和 people 引用同一个实例
指针
如果你有 C,C++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用指针来引用内存中的地址。Swift 中引用了某个引用类型实例的常量或变量,与 C 语言中的指针类似,不过它并不直接指向某个内存地址,也不要求你使用星号(*)来表明你在创建一个引用。相反,Swift 中引用的定义方式与其它的常量或变量的一样。