结构体和类是通用的、灵活的构造,它们成为程序代码的构建块。使用与定义常量、变量和函数相同的语法,定义属性和方法,以便向结构和类添加功能。
与其他编程语言不同,Swift不需要为自定义结构和类创建单独的接口和实现文件。在Swift中,您在单个文件中定义一个结构或类,该类或结构的外部接口将自动提供给其他代码使用。
注意:类的实例传统上称为对象。然而,Swift结构和类在功能上比在其他语言中更接近,本章的大部分内容描述了应用于类或结构类型实例的功能。因此,使用了更通用的术语instance。
Comparing Structures and Classes 比较结构体和类
相同性:
- 定义属性来存储值
- 定义方法来提供功能
- 定义下标以使用下标语法提供对其值的访问
- 定义初始化器来设置它们的初始状态
- 扩展以扩展其功能,使其超出默认实现
- 遵守协议以提供某种标准功能
更多详情请参考
Properties,
Methods,
Subscripts,
Initialization,
Extensions,
Protocols.
类具有结构所不具备的附加功能:
- 继承使一个类能够继承另一个类的特征
- 类型转换使您能够在运行时检查和解释类实例的类型
- 反初始化器使类的实例能够释放它所分配的任何资源
- 引用计数允许对类实例的多个引用
更多详情,请参考
Inheritance,
Type Casting,
Deinitialization,
Automatic Reference Counting.
类支持的附加功能是以增加复杂性为代价的。作为一般的指导原则,选择结构体,因为它们更容易推理,并且在适当或必要时使用类。实际上,这意味着您定义的大多数自定义数据类型都是结构体和枚举。有关更详细的比较,请参考 Choosing Between Structures and Classes.
Definition Syntax 定义的语法
定义结构体
struct SomeStructure {
}
定义类
class SomeClass {
}
无论何时定义一个新结构或类,都要定义一个新的Swift类型。任何类型名字都是大写字母开头,任何属性名和方法名字都是小写字母开头
下面是一个结构定义和类定义的例子:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
Structure and Class Instances 结构体和类 的实例
Resolution结构体定义和VideoMode类定义 只描述Resolution或VideoMode的样子。它们本身并不描述特定的分辨率或视频模式。为此,您需要创建结构或类的实例。
对于结构和类,创建实例的语法非常相似:
let someResolution = Resolution()
let someVideoMode = VideoMode()
结构和类都对新实例使用初始化器语法。初始化器语法的最简单形式是使用类或结构的类型名,后跟一对小括号,如Resolution()或VideoMode()。这将创建类或结构的新实例,并将所有属性初始化为其默认值。
类和结构初始化更详细的描述,请参考 Initialization.
Accessing Properties 访问属性
可以使用点语法访问实例的属性。在点语法中,您可以在实例名之后立即编写属性名,用句点(.)分隔,不使用任何空格:
print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"
在这个例子中,是一个解决方案。width是someResolution的width属性,返回其默认初始值0。
您可以深入到子属性中,例如VideoMode的resolution属性中的宽度属性:
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is 0"
还可以使用点语法为变量属性赋值:
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"
Memberwise Initializers for Structure Types 结构体带成员初始器
所有结构都有一个自动生成的成员初始化器,您可以使用它初始化新结构实例的成员属性。新实例属性的初始值可以通过名称传递给memberwise初始化器:
let vga = Resolution(width: 640, height: 480)
与结构体不同,类不接受默认成员初始化。关于初始化更多信息,请参考 Initialization.
Structures and Enumerations Are Value Types 结构和枚举是值类型
值类型是这样一种类型,当它被赋值给一个变量或常量时,或者当它被传递给一个函数时,它的值被复制。
实际上,在前几章中,您已经广泛地使用了值类型。事实上,swift中的所有基本类型——整数、浮点数、布尔值、字符串、数组和字典——都是值类型,并且都是作为结构体在底层实现的。
所有结构和枚举都是Swift中的值类型。这意味着您创建的任何结构和枚举实例(以及它们作为属性的任何值类型)在代码中传递时都将被复制。
注意:由标准库(如数组、字典和字符串)定义的集合使用优化来降低复制的性能成本。这些集合不是立即复制,而是共享元素存储在原始实例和任何副本之间的内存。如果修改集合的一个副本,则在修改之前复制元素。您在代码中看到的行为总是像立即发生了复制一样。
考虑这个例子,它使用了前一个例子的分辨率结构:
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
本例声明一个名为hd的常量,并将其设置为一个分辨率实例,该实例初始化为全高清视频的宽度和高度(1920像素宽,1080像素高)。
然后,它声明一个名为cinema的变量,并将其设置为hd的当前值。因为分辨率是一种结构,所以会生成现有实例的副本,并将这个新副本分配给cinema。尽管高清和电影现在有相同的宽度和高度,但它们在幕后是两个完全不同的例子。
下一步,将影院宽度属性修改为略宽的2K标准(2048像素宽,1080像素高)的宽度:
cinema.width = 2048
print("cinema is now \(cinema.width) pixels wide")
print("hd is still \(hd.width) pixels wide")
输出结果 cinema 的 width 为 2048,hd 的width 为1920。
当cinema被赋予当前的hd值时,hd中存储的值被复制到新的cinema实例中。最终的结果是两个完全独立的实例,其中包含相同的数值。但是,由于它们是独立的实例,所以将cinema的宽度设置为2048并不影响hd中存储的宽度,如下图所示:

在枚举中也是这样的:
enum CompassPoint {
case north, south, east, west
mutating func turnNorth() {
self = .north
}
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()
print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
currentDirection 为 north,rememberedDirection 为west。
Classes Are Reference Types 类是引用类型
与值类型不同,引用类型在分配给变量或常量时,或传递给函数时,不会复制。使用的不是副本,而是对相同现有实例的引用。
下面是一个例子,使用上面定义的VideoMode类:
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
print(tenEighty.frameRate)
tenEighty.frameRate值 为 30.0

注意,tenEighty 和alsoTenEighty 被声明为常量,而不是变量。不过,你仍然可以改变它们的frameRate。因为tenEighty和alsoTenEighty常数本身的值实际上没有变化。tenEighty和alsoTenEighty本身都不“存储”VideoMode实例——相反,它们都在幕后引用一个VideoMode实例。改变的是底层VideoMode类型对象的frameRate属性,而不是引用该VideoMode的常量 的值。
Identity Operators 身份操作符
因为类是引用类型,所以多个常量和变量可以在幕后引用类的同一个实例。(对于结构和枚举则不一样,因为当它们被分配给常量或变量,或传递给函数时,它们总是被复制。)
有时候,找出两个常量或变量是否引用了一个类的完全相同的实例是很有用的。为此,Swift提供了两个身份操作符:
- === 引用同一个对象
- !== 引用的不是同一个对象
使用这些操作符检查两个常量或变量是否指向同一个实例:
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."
当您定义自己的自定义结构和类时,确定两个对象相等性条件是您的责任。定义您自己的==和!=操作符实现的过程,请参考 Equivalence Operators。
Pointers 指针
如果您有使用C、c++或Objective-C的经验,您可能知道这些语言使用指针来引用内存中的地址。引用引用类型实例的Swift常量或变量类似于C中的指针,但不是指向内存中的地址的直接指针,并且不需要编写星号(*)来表示正在创建引用。相反,这些引用像Swift中的任何其他常量或变量一样定义。标准库提供指针和缓冲区类型,如果需要直接与指针交互,可以使用这些类型—请参阅手动内存管理 Manual Memory Management。
网友评论