Swift 结构体和类

结构体和类是通用的、灵活的结体,它们成为程序代码的构建部分。我们可以使用定义常量、变量和函数的相同语法来定义属性和方法,以在结构体和类中添加功能。

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

注意
类的实例传统上被称为对象。但是,Swift的结构体和类,在功能性上比在其他语言中更接近,本章的大部分内容描述了应用于类或结构体类型实例的功能。因此,使用术语实例更为通用。

比较结构体和类

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

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

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

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

  • 继承,使一个类能够继承另一个类的特性。
  • 类型转换,使我们能够在运行时检查和解释类实例的类型。
  • 释放实例,使类的实例能够释放它分配的任何资源。
  • 引用计数,允许对类实例进行多个引用。
    有关详细信息,请参见继承类型转换释放实例自动引用计数

类支持的额外功能,是增加其复杂性为代价的。作为一个一般的指南,更偏向使用结构体,因为它们更容易推理,并且在适当或必要时使用类。实际上,这意味着我们定义的大多数自定义数据类型将是结构体和枚举。有关更详细的比较,请参见结构体和类之间的选择

定义语法

结构体和类具有类似的定义语法。引入带有struct关键字的结构体和带有class关键字的类。两者都将其整个定义放在一对大括号中:

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

注意
无论何时定义新的结构体或类,都要定义一个新的Swift类型。为类型提供大写名称(如SomeStructureSomeClass)以匹配标准Swift类型(如StringIntBool)的大小写。为属性和方法提供小写的melcase名称(如frameRateincrementCount),以将它们与类型名称区分开来。

以下是结构体定义和类定义的示例:

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

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

上面的示例还定义了一个名为VideoMode的新类,用于描述视频显示的特定视频模式。这个类有四个变量存储属性。第一个是resolution,它用一个新的Resolution结构体实例初始化,该实例推断出resolution的属性类型。对于其他三个属性,新的VideoMode实例将使用interlaced设置false(表示“非插入视频”)、frameRate为0.0和名为name的可选字符串值进行初始化。name属性会自动指定一个默认值nil或“no name value”,因为它是可选类型。

结构体和类实例

Resolution结构体定义和VideoMode类定义仅描述ResolutionVideoMode的外观。它们本身并不描述特定的分辨率或视频模式。为此,需要创建结构体或类的实例。
对于结构体和类,创建实例的语法非常相似:

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引用width属性,并返回其默认初始值0。

我们可以深入到子属性,例如VideoModeresolution属性中的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"

结构体类型的成员式初始化方法

所有结构体都有一个自动生成的成员式初始化方法,我们可以使用它初始化新结构体实例的成员属性。新实例属性的初始值可以通过名称传递给成员式初始化方法:

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

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

结构体和枚举是值类型

值类型是一种类型,它的值在赋值给变量或常量或传递给函数时被复制。

在前面的章节中,我们已经广泛地使用了值类型。事实上,Swift的整数、浮点数、布尔、字符串、数组和字典中的所有基本类型都是值类型,并在系统中实现为结构体。

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

注意
由标准库定义的集合(如数组、字典和字符串)使用优化来降低复制的性能成本。这些集合不是立即创建副本,而是共享存储在原始实例和任何副本之间的元素的内存。如果修改了集合的一个副本,则将在修改之前复制元素。我们在代码中看到的行为总是好像一个拷贝立即发生。

考虑这个例子,它使用上一个例子中的Resolution结构体:

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

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

然后声明一个名为cinema的变量,并将其设置为hd的当前值。因为Resolution是一个结构体,所以将生成现有实例的副本,并将此新副本指定给cinema。尽管高清和影院现在有相同的宽度和高度,但它们在幕后却是两个完全不同的例子。

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

cinema.width = 2048

检查cinemawidth属性表明它确实已更改为2048:

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

但是,原始hd实例的width属性仍然具有旧值1920:

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

当给cinema以当前的hd值时,存储在hd中的值被复制到新的cinema实例中。最终结果是两个完全独立的实例,其中包含相同的数值。但是,由于它们是独立的实例,将cinema宽度设置为2048不会影响hd中存储的宽度,如下图所示:

截屏2021-04-06 下午9.41.34.png

同样的行为也适用于枚举:

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)")
// Prints "The current direction is north"
// Prints "The remembered direction is west"

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

类是引用类型

与值类型不同,当引用类型被赋给变量或常量,或者被传递给函数时,它们不会被复制。使用对同一现有实例的引用,而不是副本。

下面是一个示例,使用上面定义的VideoMode类:

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

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

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

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

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

截屏2021-04-06 下午9.43.19.png

检查tenEightyframeRate属性表明,它可以从VideoMode实例正确报告30.0的新帧速率:

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

这个例子还展示了引用类型如何更难推理。如果tenEightyalsoTenEighty在程序代码中相距很远,则很难找到更改视频模式的所有方法。无论在哪里使用tenEighty,都必须考虑使用alsoTenEighty的代码,反之亦然。相比之下,值类型更容易推理,因为与相同值交互的所有代码在源文件中都很接近。

请注意,tenEightyalsoTenEighty被声明为常量,而不是变量。但是,我们仍然可以改变tenEighty.frameRate以及alsoTenEighty.frameRate。因为tenEightyalsoTenEighty常量本身的值实际上并没有改变。tenEightyalsoTenEighty本身并不“存储”VideoMode实例,而是在幕后引用VideoMode实例。改变的是底层视频模式的frameRate属性,而不是该视频模式的常量引用值。

标识运算符

因为类是引用类型,所以多个常量和变量有可能在幕后引用同一个类的单个实例。(对于结构体和枚举,情况并非如此,因为当它们被赋给常量、变量或传递给函数时,它们总是被复制。)

有时,找出两个常量或变量是否引用了一个类的完全相同的实例会很有用。为了实现这一点,Swift提供了两个身份操作符:

  • 等同于(===
  • 不等同于(!==)
    使用这些运算符检查两个常量或变量是否引用同一个实例:
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++或Objto-C的经验,我们可能知道这些语言使用指针来引用内存中的地址。引用某个类型实例的Swift常量或变量类似于C中的指针,但不是指向内存中地址的直接指针,也不需要写星号(*)来表示正在创建引用。相反,这些引用的定义与Swift中的任何其他常量或变量一样。标准库提供了指针和缓冲区类型,如果需要直接与指针交互,可以使用这些类型请参见手动内存管理

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • 结构体和类 结构体和类作为一种通用而又灵活的结构,成为了人们构建代码的基础。你可以使用定义常量、变量和函数的语法,...
    Longshihua阅读 543评论 0 1
  • swift中,我们在一个文件中定义结构体和类并实现他的接口。 基础知识 结构体和类的比较 相同点: 属性:保存数据...
    枯树恋阅读 1,476评论 0 0
  • 一、类和结构体的对比 (一)、类和结构体的相同点 定义存储值的属性; 定义提供功能的方法; 定义下标以使用下标语法...
    WSJay阅读 1,684评论 2 4
  • swift中结构体与类有着密切的关系。通常他们都能:1.定义属性用于存储值,定义方法提供功能2.定义构造器生成初始...
    曲年阅读 4,515评论 0 4
  • 一、结构体 在Swift标准库中,绝大多数的公开类都是结构体,比如:Bool、Int、Double 、String...
    冷武橘阅读 69评论 0 0