Swift中的Classes and Structures

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

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

传统上将类的实例称为对象。然而,Swift类和结构在功能上比其他语言更接近,本章的大部分内容描述了可以应用于类或结构类型实例的功能。因此,使用更一般的术语实例。

比较类和结构 Swift中的类和结构有许多共同之处。两者都可以:

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

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

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

  • 继承使一个类能够继承另一个类的特性。
  • 类型转换使您能够在运行时检查和解释类实例的类型。
  • 去初始化器使类的一个实例释放它分配的任何资源。
  • 引用计数允许对一个类实例的多个引用。

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

结构在代码中传递时总是被复制,并且不使用引用计数。

定义语法

类和结构具有类似的定义语法。您可以使用struct关键字引入类关键字和结构的类。两者都将其整个定义放在一对大括号内:

class SomeClass {
    // class definition goes here
}
struct SomeStructure {
    // structure definition goes here
}

每当你定义一个新的类或结构时,你都可以有效地定义一个全新的Swift类型。给类型UpperCamelCase名称(如SomeClass和SomeStructure这里)以匹配标准Swift类型(如String,Int和Bool)的大小写。相反,总是给lowerCamelCase名称(如frameRate和incrementCount)的属性和方法区分它们和类型名称。

下面是一个结构定义和类定义的例子:

struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

上面的例子定义了一个称为Resolution的新结构来描述基于像素的显示分辨率。这个结构有两个存储的属性,称为宽度和高度。存储属性是常量或变量,它们被捆绑并存储为类或结构的一部分。通过将这两个属性设置为初始整数值0,可以将这两个属性推断为Int类型。

上面的例子还定义了一个名为VideoMode的新类,用于描述视频显示的特定视频模式。这个类有四个变量存储的属性。第一个解决方案使用新的Resolution结构实例进行初始化,该实例推断出Resolution的属性类型。对于其他三个属性,新的VideoMode实例将以隔行设置为false(意为“非隔行视频”),0.0的回放帧率和名为name的可选String值进行初始化。名称属性会自动给出默认值nil或“无名称值”,因为它是可选类型。

类和结构实例

Resolution结构定义和VideoMode类定义仅描述分辨率或视频模式的外观。他们自己没有描述特定的分辨率或视频模式。要做到这一点,你需要创建一个结构或类的实例。

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

let someResolution = Resolution()
let someVideoMode = VideoMode()

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

访问属性

您可以使用点语法访问实例的属性。在点语法中,您将在实例名称后面立即写入属性名称,用句点(.)分隔,而不带任何空格:

print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"

在这个例子中,someResolution.width指向someResolution的width属性,并返回其默认的初始值0。

您可以深入查看子属性,例如VideoMode的解析属性中的width属性:

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"

与Objective-C不同的是,Swift使您能够直接设置结构属性的子属性。在上面的最后一个示例中,someVideoMode的resolution属性的width属性可以直接设置,而无需将整个分辨率属性设置为新值。

结构类型的成员初始化程序

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

let vga = Resolution(width: 640, height: 480)

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

结构和枚举是值类型

值类型是一种类型,其值在分配给变量或常量或将其传递给函数时被复制。

在前面的章节中,你实际上已经广泛地使用了值类型。

实际上,Swift整数,浮点数,布尔值,字符串,数组和字典中的所有基本类型都是值类型,并在后台实现为结构。

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

这个例子,它使用了前面例子中的Resolution结构:

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

这个例子声明了一个叫做hd的常量,并将其设置为一个分辨率实例,该实例用全高清视频的宽度和高度(1920像素宽×1080像素高)初始化。

然后它声明一个名为cinema的变量并将其设置为hd的当前值。

由于“解决方案”是一个结构,现有实例的副本已制作完成,并且此新副本将分配给电影院。尽管高清和电影现在具有相同的宽度和高度,但它们是幕后的两个完全不同的实例。

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

cinema.width = 2048

检查电影的宽度属性表明它确实已经改变为2048:

print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"

但是,原始HD实例的宽度属性仍旧具有1920的旧值:

print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"

当电影获得hd的当前值时,存储在hd中的值被复制到新的电影实例中。最终结果是两个完全分离的实例,它们恰好包含相同的数值。由于它们是独立的实例,因此将电影的宽度设置为2048不会影响存储在hd中的宽度。

相同的行为适用于枚举:

enum CompassPoint {
    case north, south, east, west
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection = .east
if rememberedDirection == .west {
    print("The remembered direction is still .west")
}
// Prints "The remembered direction is still .west"

当rememberedDirection被分配了currentDirection的值时,它实际上被设置为该值的一个副本。此后更改currentDirection的值不会影响存储在rememberedDirection中的原始值的副本。

类是引用类型

与值类型不同,引用类型在分配给变量或常量时,或者传递给函数时不会被复制。而不是副本,而是使用对相同现有实例的引用。 这里有一个例子,使用上面定义的VideoMode类:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

此示例声明了一个名为tenEighty的新常量,并将其设置为引用VideoMode类的新实例。视频模式被分配了之前1920 x 1080的高清分辨率的副本。它被设置为隔行扫描,并被命名为“1080i”。最后,它被设置为每秒25.0帧的帧速率。

接下来,tenEighty被分配一个新的常量,称为alsoTenEighty,并修改alsoTenEighty的帧速率:

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

因为类是引用类型,所以tenEighty和alsoTenEighty实际上都指向相同的VideoMode实例。实际上,它们只是同一个实例的两个不同名称。

检查tenEighty的frameRate属性表明它可以正确地从底层VideoMode实例报告30.0的新帧速率

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"

请注意,tenEighty和alsoTenEighty被声明为常量,而不是变量。但是,您仍然可以更改tenEighty.frameRate和alsoTenEighty.frameRate,因为tenEighty和alsoTenEighty常量的值本身并未发生实际变化。 tenEighty和另外TenEighty本身不会“存储”VideoMode实例 - 相反,它们都指向幕后的VideoMode实例。它是基础VideoMode的frameRate属性发生更改,而不是对该VideoMode的常量引用的值。

Identity Operators

因为类是引用类型,所以多个常量和变量可以在幕后引用同一个类的单个实例。 (结构和枚举也是如此,因为它们在分配给常量或变量或传递给函数时总是被复制。) 找出两个常量或变量是否指向一个类的完全相同的实例有时会很有用。

为了实现这一点,Swift提供了两个身份运算符:

  • Identical to (===)
  • Not identical to (!==)

使用这些运算符来检查两个常量或变量是否引用同一个单一实例:

if tenEighty === alsoTenEighty {
    print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."

请注意,“与……相同”(由三个等号或===表示)并不等同于“等于”(由两个等号或==表示):

  • “与……相同”表示类型的两个常量或变量指向完全相同的类实例。
  • “等于”意味着两个实例在值中被认为是“相等的”或“等价的”,对于类型的设计者定义的“相等”的某些适当的含义。

当您定义自己的自定义类和结构时,您有责任决定两个“平等”实例的合格性。在等价运算符中描述定义您自己的“等于”和“不等于”运算符的实现过程。

指针

如果您有使用C,C ++或Objective-C的经验,您可能会知道这些语言使用指针来指向内存中的地址。引用某个引用类型的实例的Swift常量或变量类似于C中的指针,但不是指向内存中地址的直接指针,也不需要您编写星号(*)来表示您正在创造一个参考。相反,这些引用是像Swift中的其他常量或变量一样定义的

选择类和结构

您可以使用类和结构来定义自定义数据类型,以用作程序代码的构建块。

但是,结构实例总是按值传递,而类实例总是按引用传递。这意味着它们适合于不同类型的任务。在考虑项目所需的数据结构和功能时,请确定每个数据结构是应该定义为类还是结构。

作为一般指导原则,考虑在适用以下一个或多个条件时创建结构:
- 该结构的主要目的是封装一些相对简单的数据值。
- 当分配或传递该结构的实例时,期望封装值将被复制而不是被引用是合理的。
- 结构存储的任何属性都是它们自己的值类型,也可能被复制而不是引用。
- 该结构不需要继承其他现有类型的属性或行为。

良好的结构候选人的例子包括:

  • 几何形状的大小,可能包含一个宽度属性和一个高度属性,这两个类型都是Double类型。
  • 一种引用一系列范围内的范围的方法,可能封装一个类型为Int的start属性和一个length属性。
  • 3D坐标系中的一个点,可能会封装x,y和z属性,每种属性都是Double类型。

在所有其他情况下,定义一个类,并创建该类的实例,以便通过引用进行管理和传递。实际上,这意味着大多数自定义数据结构应该是类而不是结构。

字符串,数组和字典的赋值和复制行为

在Swift中,许多基本数据类型(如String,Array和Dictionary)都以结构体的形式实现。这意味着如果将字符串,数组和字典等数据分配给新的常量或变量,或者将它们传递给函数或方法时,它们将被复制。

此行为与Foundation不同:NSString,NSArray和NSDictionary作为类实现,而不是结构。Foundation中的字符串,数组和字典始终作为对现有实例的引用进行分配和传递,而不是作为副本。 注意 上面的描述涉及字符串,数组和字典的“复制”。

您在代码中看到的行为总是会像发生副本一样。但是,当绝对必要时,Swift仅在幕后执行实际的副本。 Swift管理所有值复制以确保最佳性能,并且您不应该避免分配尝试抢占此优化。

注意
上面的描述涉及字符串,数组和字典的“复制”。您在代码中看到的行为总是会像发生副本一样。但是,当绝对必要时,Swift仅在幕后执行实际的副本。 Swift管理所有值复制以确保最佳性能,并且您不应该避免分配尝试抢占此优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值