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

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

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

与其他编程语言不同,Swift不需要您为自定义结构和类创建单独的接口和实现文件。在Swift中,您可以在单个文件中定义结构或类,并且该类或结构的外部接口会自动提供给其他代码使用。

注意

传统上将类的实例称为对象然而,夫特结构和类在功能上更接近比在其他语言,等等本章的描述适用于实例功能一类或结构类型。因此,使用了更通用的术语实例

比较结构和类

Swift中的结构和类有很多共同点。两者都可以:

  • 定义属性以存储值
  • 定义提供功能的方法
  • 定义下标以使用下标语法提供对其值的访问
  • 定义初始化程序以设置其初始状态
  • 扩展以扩展其功能,超出默认实现
  • 符合协议以提供某种标准功能

有关更多信息,请参见属性方法,下初始化扩展协议

类具有结构没有的其他功能:

  • 继承使一个类可以继承另一个类的特征。
  • 通过类型转换,您可以在运行时检查和解释类实例的类型。
  • 反初始化程序使类的实例可以释放其已分配的所有资源。
  • 引用计数允许对一个类实例进行多个引用。

有关更多信息,请参见继承类型转换取消初始化自动引用计数

类支持的其他功能是以增加复杂性为代价的。作为一般准则,应首选结构,因为它们更易于推理,并在适当或必要时使用类。实际上,这意味着您定义的大多数自定义数据类型将是结构和枚举。有关更详细的比较,请参见在结构和类之间进行选择

定义语法

结构和类具有相似的定义语法。您可以使用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称为的可选来初始化namename属性会自动获得默认值nil或“无name值”,因为它是可选类型。

结构和类实例

Resolution结构定义和VideoMode类定义只是描述一个东西ResolutionVideoMode看起来像。它们本身并没有描述特定的分辨率或视频模式。为此,您需要创建结构或类的实例。

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

  1. let someResolution = Resolution()
  2. let someVideoMode = VideoMode()

结构和类都对新实例使用初始化程序语法。初始化程序语法的最简单形式是使用类或结构的类型名称,后跟空括号,例如Resolution()VideoMode()这将创建类或结构的新实例,并将所有属性初始化为其默认值。类和结构的初始化在Initialization中有更详细的描述

访问属性

您可以使用点语法访问实例的属性在点语法中,您应在实例名称之后立即写上属性名称,并用句点(.分隔,不能有任何空格:

  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"

结构类型的成员初始化器

所有结构都有一个自动生成的Memberwise初始化程序,您可以使用它初始化新结构实例的成员属性。可以通过名称将新实例的属性的初始值传递给成员初始化器:

  1. let vga = Resolution( 640, height: 480)

与结构不同,类实例不接收默认的成员初始化器。初始化在初始化中有更详细的描述

结构和枚举是值类型

值类型是一个类型,其值被复制时,它的传递给一个函数时,它的分配给一个变量或常数,或。

实际上,在前几章中,您一直在广泛使用值类型。实际上,Swift中的所有基本类型(整数,浮点数,布尔值,字符串,数组和字典)都是值类型,并实现为幕后结构。

所有结构和枚举都是Swift中的值类型。这意味着您创建的任何结构和枚举实例以及它们具有的任何值类型作为属性,都将在它们在代码中传递时始终被复制。

注意

由标准库定义的集合(例如数组,字典和字符串)使用优化来降低复制的性能成本。这些集合不共享立即复制的功能,而是共享存储在原始实例和任何副本之间的元素的内存。如果修改了集合的副本之一,则在修改之前就将元素复制。您在代码中看到的行为始终就像是立即进行了复制一样。

考虑以下示例,该示例使用Resolution上一个示例结构:

  1. let hd = Resolution( 1920, height: 1080)
  2. var cinema = hd

本示例声明一个常量hd,并将其设置为使用Resolution全高清视频的宽度和高度(1920像素宽x 1080像素高)初始化实例。

然后,它声明一个名为的变量cinema,并将其设置为的当前值hd因为Resolution是结构,所以将创建现有实例副本,并将此新副本分配给cinema尽管hd并且cinema现在具有相同的宽度和高度,但它们是幕后的两个完全不同的实例。

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

  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该类的新实例在视频模式下分配了1920by 1080之前的HD分辨率的副本它设置为隔行扫描,其名称设置为"1080i",其帧速率设置为25.0每秒的帧数。

接下来,tenEighty将分配给一个称为的新常数alsoTenEighty,并alsoTenEighty修改的帧频

  1. let alsoTenEighty = tenEighty
  2. alsoTenEighty.frameRate = 30.0

因为类是引用类型,tenEighty并且alsoTenEighty实际上两者都引用同一 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反之亦然。相反,值类型更容易推论,因为与同一值交互的所有代码在源文件中都在一起。

请注意,tenEightyalsoTenEighty被声明为常量,而不是变量。但是,您仍然可以更改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."

需要注意的是相同的(由三个代表等号,或===)并不意味着作为同一件事等于(由两个代表等号,或==)。等同于表示类类型的两个常量或变量引用完全相同的类实例。等于表示两个实例的值相等或相等,这对于类型的设计者所定义equal的一些适当含义

在定义自己的自定义结构和类时,您有责任确定两个实例相等时的资格。等价运算符中介绍了定义==!=运算符自己的实现的过程

指针

如果您有使用C,C ++或Objective-C的经验,您可能会知道这些语言使用指针来引用内存中的地址。引用某种引用类型的实例的Swift常量或变量类似于C中的指针,但不是指向内存中地址的直接指针,并且不需要您写星号(*)来表示您正在创建参考。相反,这些引用的定义与Swift中的其他任何常量或变量一样。标准库提供了指针和缓冲区类型,如果您需要直接与指针进行交互,可以使用它们(请参见《手动内存管理》)

原文地址:https://www.cnblogs.com/strengthen/p/9729270.html