作者:灵动的音乐xl | 来源:互联网 | 2023-09-24 15:02
结构和类是通用的,灵活的结构,它们成为程序代码的构建块。您可以使用与定义常量,变量和函数相同的语法来定义属性和方法,以向结构和类添加功能。与其他编程语言不同,Swift不要求您为自定义结构和类创建
结构和类是通用的,灵活的结构,它们成为程序代码的构建块。您可以使用与定义常量,变量和函数相同的语法来定义属性和方法,以向结构和类添加功能。
与其他编程语言不同,Swift不要求您为自定义结构和类创建单独的接口和实现文件。在Swift中,您可以在单个文件中定义结构或类,并且该类或结构的外部接口可自动用于其他代码以供使用。
注意
类的实例传统上称为对象。然而Swift结构和类在功能上比在其他语言更接近,并且多本章的描述适用于实例功能任一类或结构类型。因此,使用了更通用的术语实例。
结构和类的比较
Swift中的结构和类有许多共同点。两者都可以:
- 定义存储值的属性
- 定义提供功能的方法
- 定义下标以使用下标语法提供对其值的访问
- 定义初始化程序以设置其初始状态
- 扩展以扩展其功能,超越默认实现
- 符合协议以提供某种标准功能
有关更多信息,请参阅属性,方法,下标,初始化,扩展和协议。
类具有结构不具备的其他功能:
- 继承使一个类可以继承另一个类的特性。
- 类型转换使您可以在运行时检查和解释类实例的类型。
- Deinitializers使类的实例可以释放它已分配的任何资源。
- 引用计数允许对类实例的多个引用。
有关更多信息,请参阅继承,类型转换,取消初始化和自动引用计数。
类支持的附加功能是以增加复杂性为代价的。作为一般准则,更喜欢结构和枚举,因为它们更容易推理,并在适当或必要时使用类。实际上,这意味着您定义的大多数自定义数据类型都是结构和枚举。有关更详细的比较,请参阅在结构和类之间进行选择。
定义语法
结构和类具有类似的定义语法。您将使用struct
关键字和带有class
关键字的类来引入结构。两者都把它们的整个定义放在一对括号中:
1 struct SomeStructure {
2 // structure definition goes here
3 }
4 class SomeClass {
5 // class definition goes here
6 }
注意
无论何时定义新结构或类,都要定义新的Swift类型。给出类型UpperCamelCase
名称(如SomeStructure
和SomeClass
这里)来匹配标准斯威夫特类型的资本(如String
,Int
和Bool
)。提供属性和方法lowerCamelCase
名称(例如frameRate
和incrementCount
)以区别于类型名称。
这是结构定义和类定义的示例:
1 struct Resolution {
2 var width = 0
3 var height = 0
4 }
5 class VideoMode {
6 var resolution = Resolution()
7 var interlaced = false
8 var frameRate = 0.0
9 var name: String?
10 }
上面的示例定义了一个名为的新结构Resolution
,用于描述基于像素的显示分辨率。此结构有两个存储的属性,称为width
和height
。存储的属性是捆绑起来并作为结构或类的一部分存储的常量或变量。Int
通过将它们设置为初始整数值,可以推断出这两个属性的类型和初始值为0。
上面的示例还定义了一个名为的新类VideoMode
,用于描述视频显示的特定视频模式。该类有四个变量存储属性。第一个,resolution
用一个新的Resolution
结构实例初始化,推断出一个属性类型Resolution
。对于其他三个属性,新的VideoMode
实例将与被初始化interlaced
的设置false
(意为“逐行视频”),的播放帧速率0.0
,和一个可选的String
调用值name
。该name
属性自动被赋予默认值nil
,或“无name
值”,因为它是可选类型。
结构和类实例
该Resolution
结构定义和VideoMode
类定义只说明什么Resolution
或VideoMode
看起来像。它们本身并不描述特定的分辨率或视频模式。为此,您需要创建结构或类的实例。
对于结构和类,创建实例的语法非常相似:
1 let someResolution = Resolution()
2 let someVideoMode = VideoMode()
结构和类都使用初始化器语法用于新实例。最简单的初始化语法形式使用类或结构的类型名称,后跟空括号,例如Resolution()
或VideoMode()
。这将创建类或结构的新实例,并将任何属性初始化为其默认值。初始化中更详细地描述了类和结构初始化。
访问属性
您可以使用点语法访问实例的属性。在点语法中,您在实例名称后面立即编写属性名称,用句点(.
)分隔,不带任何空格:
1 print("The width of someResolution is \(someResolution.width)")
2 // Prints "The width of someResolution is 0"
在此示例中,someResolution.width
引用的width
属性为someResolution
,并返回其默认初始值0
。
您可以深入查看子属性,例如width
属性中的resolution
属性VideoMode
:
1 print("The width of someVideoMode is \(someVideoMode.resolution.width)")
2 // Prints "The width of someVideoMode is 0"
您还可以使用点语法为变量属性分配新值:
1 someVideoMode.resolution.width = 1280
2 print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
3 // Prints "The width of someVideoMode is now 1280"
结构类型的成员初始化器
所有结构都有一个自动生成的成员初始化程序,您可以使用它初始化新结构实例的成员属性。可以通过名称将新实例的属性的初始值传递给成员初始值设定项:
1 let vga = Resolution(width: 640, height: 480)
与结构不同,类实例不接收默认的成员初始值设定项。初始化中更详细地描述在初始化。
结构和枚举都是值类型
值类型是一个类型,其值被复制时,它的分配给一个变量或常数,或当它传递给函数。
在前面的章节中,您实际上已经广泛使用了值类型。实际上,Swift中整数,浮点数,布尔值,字符串,数组和字典中的所有基本类型都是值类型,并且在幕后实现为结构。
所有结构和枚举都是Swift中的值类型。这意味着您创建的任何结构和枚举实例以及它们作为属性的任何值类型在代码中传递时始终会被复制。
注意
标准库(如数组,字典和字符串)定义的集合使用优化来降低复制的性能成本。这些集合不是立即复制,而是共享内存,其中元素存储在原始实例和任何副本之间。如果修改了集合的其中一个副本,则在修改之前复制元素。您在代码中看到的行为总是好像立即发生了复制。
考虑这个示例,它使用Resolution
前一个示例中的结构:
1 let hd = Resolution(width: 1920, height: 1080)
2 var cinema = hd
此示例声明一个调用的常量hd
,并将其设置为使用Resolution
全高清视频(1920像素宽,1080像素高)的宽度和高度初始化的实例。
然后它声明一个被调用的变量cinema
并将其设置为当前值hd
。因为Resolution
是结构,所以会创建现有实例的副本,并将此新副本分配给cinema
。虽然hd
和cinema
现在有相同的宽度和高度,他们是幕后两种完全不同的情况。
接下来,将width
属性cinema
修改为用于数字电影投影(2048像素宽和1080像素高)的稍宽2K标准的宽度:
检查width
属性cinema
显示它确实已更改为2048
:
1 print("cinema is now \(cinema.width) pixels wide")
2 // Prints "cinema is now 2048 pixels wide"
但是,width
原始hd
实例的属性仍具有旧值1920
:
1 print("hd is still \(hd.width) pixels wide")
2 // Prints "hd is still 1920 pixels wide"
当cinema
给出当前值时hd
,存储的值hd
被复制到新cinema
实例中。最终结果是两个完全独立的实例,它们包含相同的数值。但是,因为它们是单独的实例,所以设置宽度cinema
to 2048
不会影响存储的宽度hd
,如下图所示:
相同的行为适用于枚举:
1 enum CompassPoint {
2 case north, south, east, west
3 mutating func turnNorth() {
4 self = .north
5 }
6 }
7 var currentDirection = CompassPoint.west
8 let rememberedDirection = currentDirection
9 currentDirection.turnNorth()
10
11 print("The current direction is \(currentDirection)")
12 print("The remembered direction is \(rememberedDirection)")
13 // Prints "The current direction is north"
14 // Prints "The remembered direction is west"
当rememberedDirection
赋值为currentDirection
,它实际上设置为该值的副本。更改currentDirection
此后的值不会影响存储的原始值的副本rememberedDirection
。
类是引用类型
与值类型不同,引用类型在分配给变量或常量时或者传递给函数时不会被复制。而不是副本,使用对同一现有实例的引用。
这是一个例子,使用VideoMode
上面定义的类:
1 let tenEighty = VideoMode()
2 tenEighty.resolution = hd
3 tenEighty.interlaced = true
4 tenEighty.name = "1080i"
5 tenEighty.frameRate = 25.0
此示例声明一个调用的新常量tenEighty
,并将其设置为引用VideoMode
该类的新实例。视频模式被分配的HD分辨率的副本,1920
通过1080
从之前。它被设置为隔行扫描,并且被命名为"1080i"
。最后,它被设置为25.0
每秒帧数的帧速率。
接下来,tenEighty
分配一个名为的新常量alsoTenEighty
,并alsoTenEighty
修改帧速率:
1 let alsoTenEighty = tenEighty
2 alsoTenEighty.frameRate = 30.0
由于类是引用类型,tenEighty
而alsoTenEighty
实际上都指向同一个 VideoMode
实例。实际上,它们只是同一个实例的两个不同名称,如下图所示:
检查frameRate
属性tenEighty
表明它正确地报告30.0
了基础VideoMode
实例的新帧速率:
1 print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
2 // Prints "The frameRate property of tenEighty is now 30.0"
此示例还显示了引用类型如何更难以推理。如果tenEighty
与alsoTenEighty
人相距甚远在你的程序的代码,它可能是很难找到的视频模式改变的所有方式。无论您在何处使用tenEighty
,您还必须考虑使用的代码,alsoTenEighty
反之亦然。相反,值类型更容易推理,因为与相同值交互的所有代码在源文件中都是紧密相连的。
请注意,tenEighty
并将alsoTenEighty
其声明为常量,而不是变量。但是,您仍然可以更改tenEighty.frameRate
,alsoTenEighty.frameRate
因为tenEighty
和alsoTenEighty
常量的值本身并未实际更改。tenEighty
并且alsoTenEighty
他们自己不“存储” VideoMode
实例 - 相反,他们都引用VideoMode
了幕后的实例。它是被更改frameRate
的底层的属性VideoMode
,而不是对其的常量引用的值VideoMode
。
身份运算符
因为类是引用类型,所以多个常量和变量可能在后台引用同一个类的单个实例。(对于结构和枚举,情况也是如此,因为它们在分配给常量或变量或传递给函数时总是被复制。)
有时可以找出两个常量或变量是否指向完全相同的类实例。为了实现这一点,Swift提供了两个身份运算符:
使用这些运算符来检查两个常量或变量是否引用同一个实例:
1 if tenEighty === alsoTenEighty {
2 print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
3 }
4 // Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."
注意,与(由三个等号表示,或===
)相同并不等同于(由两个等号表示,或==
)。与此类似意味着类类型的两个常量或变量引用完全相同的类实例。等于意味着两个实例被认为是相等或等效的值,为的一些适当的含义相同,通过该类型的设计者所定义的。
当您定义自己的自定义结构和类时,您有责任确定两个实例相等的条件。在等价运算符中描述了定义自己的“等于”和“不等于”运算符的实现的过程。
指针
如果您有使用C,C ++或Objective-C的经验,您可能知道这些语言使用指针来引用内存中的地址。引用某个引用类型的实例的Swift常量或变量类似于C中的指针,但它不是指向内存中地址的直接指针,并且不需要您编写星号(*
)来指示你正在创建一个参考。相反,这些引用的定义与Swift中的任何其他常量或变量一样。标准库提供指针和缓冲区类型,如果需要直接与指针交互,可以使用它们 - 请参阅手动内存管理。