Swift5.1 语言指南(十一) 结构和类

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9729270.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

结构是通用的,灵活的结构,它们成为程序代码的构建块。您可以使用与定义常量,变量和函数相同的语法来定义属性和方法,以便为结构和类添加功能。

与其他编程语言不同,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名称(如SomeStructureSomeClass这里)来匹配标准斯威夫特类型的资本(如StringIntBool)。提供属性和方法lowerCamelCase名称(例如frameRateincrementCount)以区别于类型名称。

这是结构定义和类定义的示例:

  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,用于描述基于像素的显示分辨率。此结构有两个存储的属性,称为widthheight。存储的属性是捆绑起来并作为结构或类的一部分存储的常量或变量。Int通过将它们设置为初始整数值,推断这两个属性是类型的0

上面的示例还定义了一个名为的新类VideoMode,用于描述视频显示的特定视频模式。该类有四个变量存储属性。第一个,resolution用一个新的Resolution结构实例初始化,推断出一个属性类型Resolution。对于其他三个属性,新的VideoMode实例将与被初始化interlaced的设置false(意为“逐行视频”),的播放帧速率0.0,和一个可选的String调用值name。该name属性自动赋予默认值nil或“无name值”,因为它是可选类型。

结构和类实例

Resolution结构定义和VideoMode类定义只说明什么ResolutionVideoMode看起来像。它们本身并不描述特定的分辨率或视频模式。为此,您需要创建结构或类的实例。

对于结构和类,创建实例的语法非常相似:

  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。虽然hdcinema现在有相同的宽度和高度,他们是幕后两种完全不同的情况。

接下来,将width属性cinema修改为用于数字电影投影(2048像素宽和1080像素高)的稍宽2K标准的宽度:

  1. cinema.width = 2048

检查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实例中。最终结果是包含相同数值的两个完全独立的实例。但是,因为它们是单独的实例,所以设置宽度cinemato 2048不会影响存储的宽度hd,如下图所示:

../_images/sharedStateStruct_2x.png

相同的行为适用于枚举:

  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. print("The current direction is \(currentDirection)")
  11. print("The remembered direction is \(rememberedDirection)")
  12. // Prints "The current direction is north"
  13. // 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

由于类是引用类型,tenEightyalsoTenEighty实际上都指向同一个 VideoMode实例。实际上,它们只是同一个实例的两个不同名称,如下图所示:

../_images/sharedStateClass_2x.png

检查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"

此示例还显示了引用类型如何更难以推理。如果tenEightyalsoTenEighty人相距甚远在你的程序的代码,它可能是很难找到的视频模式改变的所有方式。无论您在何处使用tenEighty,您还必须考虑使用的代码,alsoTenEighty反之亦然。相反,值类型更容易推理,因为与相同值交互的所有代码在源文件中都是紧密相连的。

请注意,tenEighty并将alsoTenEighty其声明为常量,而不是变量。但是,您仍然可以更改tenEighty.frameRatealsoTenEighty.frameRate因为tenEightyalsoTenEighty常量本身的值实际上并未更改。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中的任何其他常量或变量一样。标准库提供指针和缓冲区类型,如果需要直接与指针交互,可以使用它们 - 请参阅手动内存管理

转载于:https://www.cnblogs.com/strengthen/p/9729270.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值