iOS开发——swift篇&经典语法(十七)类与结构

类与结构

类与结构是编程人员在代码中会经常用到的代码块。在类与结构中可以像定义常量,变量和函数一样,定义相关的属性和方法以此来实现各种功能。

和其它的编程语言不太相同的是,Swift不需要单独创建接口或者实现文件来使用类或者结构。Swift中的类或者结构可以在单文件中直接定义,一旦定义完成后,就能够被直接其它代码使用。

注意:一个类的实例一般被视作一个对象,但是在Swift中,类与结构更像是一个函数方法,在后续的章节中更多地是讲述类和结构的功能性。

1、类和结构的异同

类和结构有一些相似的地方,它们都可以:

定义一些可以赋值的属性;

定义具有功能性的方法

定义下标,使用下标语法

定义初始化方法来设置初始状态

在原实现方法上的可扩展性

根据协议提供某一特定类别的基本功能

更多内容可以阅读:属性,方法,下标,初始化,扩展和协议等章节

类还有一些结构不具备的特性:

类的继承性

对类实例实时的类型转换

析构一个类的实例使之释放空间

引用计数,一个类实例可以有多个引用

更多内容可以阅读:继承,类型转换,初始化自动引用计数

注意:结构每次在代码中传递时都是复制了一整个,所以不要使用引用计数

定义语法

类和结构拥有相似的定义语法,使用class关键词定义一个类,struct关键词定义结构。每个定义都由一对大括号包含:

1
2
3
4
5
6
class SomeClass {
// class definition goes here
}
struct SomeStructure {
// structure definition goes here
}

注意:在定义类和结构时,一般使用UpperCamelCase命名法来定义类和结构的名称,比如SomeClass和 SomeStructure,这样也符合Swift其它类型的标准。而给属性和方法命名时,一般时候lowerCamelCase命名法,比如 frameRate和incrementCount等。
下面是一个结构和一个类的定义示例:

1
2
3
4
5
6
7
8
9
10
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = falsevar
frameRate = 0.0
var name: String?
}

上面的例子首先定义了一个叫Resolution的结构,用来描述一个像素显示的分辨率,它有两个属性分别叫width和height。这两个属性被默认定义为Int类型,初始化为0.

之后定义了一个叫VideoMode的类,为视频显示的显示方式。这个类有四个属性,第一个属性resolution本身又是一个结构,然后是另外两个属性。最后一个属性用到了可选字符串类型String?,表示这个属性可以存在,或者不存在为nil。

类和结构的实例

上面的两个定义仅仅是定义了结构Resolution和类VideoMode的整体样式,它们本身不是一个特定的分辨率或者显示方式,这时候就需要实例化这个结构和类。

实例化的语法相似:

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

类和结构都使用实例语法来完成实例化。最简单的实例语法就是用两个括号()完成。在这种情况下定义的实例中的属性都会完成默认初始化。更多内容可以参考初始化一章。

访问属性

使用.语法就可以方便地访问一个实例的属性。在.语法中,在实例名之后加上(.)再加上属性名即可,不需要空格:

1
2
println("The width of someResolution is \(someResolution.width)")
// prints "The width of someResolution is 0"

在这个例子中,someResolution.width表示someResolution的width属性,返回了它的初始值0

也可以使用.语法连续地获取属性的属性,比如VideoMode中resolution属性的width属性

1
2
println("The width of someVideoMode is \(someVideoMode.resolution.width)")
// prints "The width of someVideoMode is 0"

使用这种方法不仅可以访问,也可以赋值:

1
2
3
someVideoMode.resolution.width = 1280
println("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// prints "The width of someVideoMode is now 1280"

注意:和Objective-C不同,Swift能够直接设置一个结构属性的子属性,就像上面这个例子一样。

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

每个结构都有一个成员初始化方法,可以在初始化的时候通过使用属性名称来指定每一个属性的初始值:

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

但是和结构不同,类实例不能够使用成员初始化方法,在初始化一章有专门的介绍。

2、结构和枚举类型是数值类型

数值类型是说当它被赋值给一个常量或者变量,或者作为参数传递给函数时,是完整地复制了一个新的数值,而不是仅仅改变了引用对象。

事实上读到这里你已经在前面几章见过数值类型了,所有Swift中的基础类型-整型,浮点型,布尔类型,字符串,数组和字典都是数值类型。它们也都是由结构来实现的。

在Swift中所有的结构和枚举类型都是数值类型。这意味这你实例化的每个结构和枚举,其包含的所有属性,都会在代码中传递的时候被完整复制。

下面的这个例子可以说明这个特性:

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

声明了一个常量hd,是Resolution的实例化,宽度是1920,高度是1080,然后声明了一个变量cinema,和hd相同。这个时候表明,cinema和hd是两个实例,虽然他们的宽度都是1920,高度都是1080。

如果把cinema的宽度更改为2048,hd的宽度不会变化,依然是1920

1
2
3
4
5
cinema.width = 2048
println("cinema is now \(cinema.width) pixels wide")
// prints "cinema is now 2048 pixels wide"
println("hd is still \(hd.width) pixels wide")
// prints "hd is still 1920 pixels wide"

这表明当hd被赋值给cinema时,是完整地复制了一个全新的Resolution结构给cinema,所以当cinema的属性被修改时,hd的属性不会变化。

下面的例子演示的是枚举类型:

1
2
3
4
5
6
7
8
9
10
enum CompassPoint {
case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
println("The remembered direction is still .West")
}
// prints "The remembered direction is still .West"

尽管经过几次赋值,rememberedDirection依然没有变化,这是因为在每一次赋值过程中,都是将数值类型完整地复制了过来。

3、类是引用类型

和数值类型不同引用类型不会复制整个实例,当它被赋值给另外一个常量或者变量的时候,而是会建立一个和已有的实例相关的引用来表示它。

下面是引用的示例,VideoMode被定义为一个类:

1
2
3
4
5
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

分别将这个实例tenEighty的四个属性初始化,然后tenEighty被赋值给了另外一个叫alsoTenEighty的常量,然后alsoTenEighty的frameRate被修改了

1
2
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

由于类是一个引用类型,所以tenEighty和alsoTenEighty实际上是同一个实例,仅仅只是使用了不同的名称而已,我们通过检查frameRate可以证明这个问题:

1
2
println("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// prints "The frameRate property of tenEighty is now 30.0"

注意到tenEighty和alsoTenEighty是被定义为常量的,而不是变量。但是我们还是可以改变他们的属性值,这是因为它们本身实际上 没有改变,它们并没有保存这个VideoMode的实例,仅仅只是引用了一个VideoMode实例,而我们修改的也是它们引用的实例中的属性。

特征操作

因为类是引用类型,那么就可能存在多个常量或者变量只想同一个类的实例(这对于数值类型的结构和枚举是不成立的)。

可以通过如下两个操作来判断两个常量或者变量是否引用的是同一个类的实例:

相同的实例(===)

不同的实例(!==)

使用这些操作可以检查:

1
2
3
4
if tenEighty === alsoTenEighty {
println("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
// prints "tenEighty and alsoTenEighty refer to the same Resolution instance."

注意是相同的实例判断使用三个连续的等号,这和相等(两个等号)是不同的

实例相同表示的是两个变量或者常量所引用的是同一个类的实例

相等是指两个实例在数值上的相等,或者相同。

当你定义一个类的时候,就需要说明什么样的时候是两个类相等,什么时候是两个类不相等。更多内容可以从相等操作一章中获得。

指针

如果你有C,C++或者Objective-C的编程经验,你一定知道在这些语言中使用指针来引用一个内存地址。Swift中引用一个实例的常量或变量跟C中的指针类似,但是不是一个直接指向内存地址的指针,也不需要使用*记号表示你正在定义一个引用。Swift中引用和其它变量,常量的定义方法相同。

4、如何选择使用类还是结构

在代码中可以选择类或者结构来实现你所需要的代码块,完成相应的功能。但是结构实例传递的是值,而类实例传递的是引用。那么对于不同的任务,应该考虑到数据结构和功能的需求不同,从而选择不同的实例。

一般来说,下面的一个或多个条件满足时,应当选择创建一个结构:

结构主要是用来封装一些简单的数据值

当赋值或者传递的时候更希望这些封装的数据被赋值,而不是被引用过去

所有被结构存储的属性本身也是数值类型

结构不需要被另外一个类型继承或者完成其它行为

一些比较好的使用结构的例子:

一个几何形状的尺寸,可能包括宽度,高度或者其它属性,每个属性都是Double类型的

一个序列的对应关系,可能包括开始start和长度length属性,每个属性都是Int类型的

3D坐标系中的一个点,包括x,y和z坐标,都是Double类型

在其它情况下,类会是更好的选择。也就是说一般情况下,自定义的一些数据结构一般都会被定义为类。

5、集合类型的赋值和复制操作

Swift中,数组Array和字典Dictionary是用结构来实现的,但是数组与字典和其它结构在进行赋值或者作为参数传递给函数的时候有一些不同。

并且数组和字典的这些操作,又与Foundation中的NSArray和NSDictionary不同,它们是用类来实现的。

注意:下面的小节将会介绍数组,字典,字符串等的复制操作。这些复制操作看起来都已经发生,但是Swift只会在确实需要复制的时候才会完整复制,从而达到最优的性能。

字典的赋值和复制操作

每次将一个字典Dictionary类型赋值给一个常量或者变量,或者作为参数传递给函数时,字典会在赋值或者函数调用时才会被复制。这个过程在上面的小节:结构和枚举是数值类型中描述了。

如果字典中的键值是数值类型(结构或者枚举),它们在赋值的时候会同时被复制。相反,如果是引用类型(类或者函数),引用本身将会被复制,而不是类实例或者函数本身。字典的这种复制方式和结构相同。

下面的例子演示的是一个叫ages的字典,存储了一些人名和年龄的对应关系,当赋值给copiedAges的时候,里面的数值同时被完整复制。当改变复制了的数值的时候,原有的数值不会变化,如下例子:

1
2
var ages = ["Peter": 23, "Wei": 35, "Anish": 65, "Katya": 19]
var copiedAges = ages

这个字典的键是字符串String类型,值是Int类型,都是数值类型,那么在赋值的时候都会被完整复制。

1
2
3
copiedAges["Peter"] = 24
println(ages["Peter"])
// prints "23"

数组的赋值和复制操作

和字典Dictionary类型比起来,数组Array的赋值和复制操作就更加复杂。Array类型和C语言中的类似,仅仅只会在需要的时候才会完整复制数组的值。

如果将一个数组赋值给一个常量或者变量,或者作为一个参数传递给函数,复制在赋值和函数调用的时候并不会发生。这两个数组将会共享一个元素序列,如果你修改了其中一个,另外一个也将会改变。

对于数组来说,复制只会在你进行了一个可能会修改数组长度操作时才会发生。包括拼接,添加或者移除元素等等。当复制实际发生的时候,才会像字典的赋值和复制操作一样。

下面的例子演示了数组的赋值操作:

1
2
3
var a = [1, 2, 3]
var b = a
var c = a

数组a被赋值给了b和c,然后输出相同的下标会发现:

1
2
3
4
5
6
println(a[0])
// 1
println(b[0])
// 1
println(c[0])
// 1

如果改变a中的某个值,会发现b和c中的数值也会跟着改变,因为赋值操作没有改变数组的长度:

1
2
3
4
5
6
7
a[0] = 42
println(a[0])
// 42
println(b[0])
// 42
println(c[0])
// 42

但是,如果在a中添加一个新的元素,那么就改变了数组的长度,这个时候就会发生实际的复制操作。如果再改变a中元素的值,b和c中的元素将不会发生改变:

1
2
3
4
5
6
7
8
a.append(4)
a[0] = 777
println(a[0])
// 777
println(b[0])
// 42
println(c[0])
// 42

设置数组是唯一的

如果可以在对数组进行修改前,将它设置为唯一的就最好了。我们可以通过使用unshare方法来将数组自行拷贝出来,成为一个唯一的实体。

如果多个变量引用了同一个数组,可以使用unshare方法来完成一次“独立”

b.unshare()

这时候如果再修改b的值,c的值也不会再受影响

1
2
3
4
5
6
7
b[0] = -105
println(a[0])
// 777
println(b[0])
// -105
println(c[0])
// 42

检查两个数组时候共用了相同的元素

使用实例相等操作符来判断两个数组是否共用了元素(===和!===)

下面这个例子演示的就是判断是否共用元素:

1
2
3
4
5
6
if b === c {
println("b and c still share the same array elements.")
} else {
println("b and c now refer to two independent sets of array elements.")
}
// prints "b and c now refer to two independent sets of array elements."

也可以使用这个操作来判断两个子数组是否有共用的元素:

1
2
3
4
5
6
if b[0...1] === b[0...1] {
println("These two subarrays share the same elements.")
} else {
println("These two subarrays do not share the same elements.")
}
// prints "These two subarrays share the same elements."

强制数组拷贝

通过调用数组的copy方法来完成强制拷贝。这个方法将会完整复制一个数组到新的数组中。

下面的例子中这个叫names的数组会被完整拷贝到copiedNames中去。

1
2
var names = ["Mohsen", "Hilary", "Justyn", "Amy", "Rich", "Graham", "Vic"]
var copiedNames = names.copy()

通过改变copiedNames的值可以验证,数组已经被完整拷贝,不会影响到之前的数组:

1
2
3
copiedNames[0] = "Mo"
println(names[0])
// prints "Mohsen"

注意:如果你不确定你需要的数组是否是独立的,那么仅仅使用unshare就可以了。而copy方法不管当前是不是独立的,都会完整拷贝一次,哪怕这个数组已经是unshare的了。

时间: 2024-08-01 18:33:19

iOS开发——swift篇&经典语法(十七)类与结构的相关文章

iOS开发——swift篇&经典语法(八)初始化

初始化 初始化是类,结构体和枚举类型实例化的准备阶段.这个阶段设置这个实例存储的属性的初始化数值和做一些使用实例之前的准备以及必须要做的其他一些设置工作. 通过定义构造器(initializers)实现这个实例化过程,也就是创建一个新的具体实例的特殊方法.和Objective-C不一样的是,Swift的构造器没有返回值.它们主要充当的角色是确保这个实例在使用之前能正确的初始化. 类实例也能实现一个析构器(deinitializer),在类实例销毁之前做一些清理工作.更多的关于析构器(deinit

iOS开发——swift篇&经典语法(十八)拓展

扩展 扩展就是向一个已有的类.结构体或枚举类型添加新功能(functionality).这包括在没有权限获取原始源代码的情况下扩展类型的能力(即逆向建模).扩展和 Objective-C 中的分类(categories)类似.(不过与Objective-C不同的是,Swift 的扩展没有名字.) Swift 中的扩展可以: 添加计算型属性和计算静态属性 定义实例方法和类型方法 提供新的构造器 定义下标 定义和使用新的嵌套类型 使一个已有类型符合某个接口 注意: 如果你定义了一个扩展向一个已有类型

iOS开发——swift篇&经典语法(二十)高级运算符

高级运算符 除了基本操作符中所讲的运算符,Swift还有许多复杂的高级运算符,包括了C语和Objective-C中的位运算符和移位运算. 不同于C语言中的数值计算,Swift的数值计算默认是不可溢出的.溢出行为会被捕获并报告为错误.你是故意的?好吧,你可以使用Swift为你准备的另一套默认允许溢出的数值运算符,如可溢出加&+.所有允许溢出的运算符都是以&开始的. 自定义的结构,类和枚举,是否可以使用标准的运算符来定义操作?当然可以!在Swift中,你可以为你创建的所有类型定制运算符的操作.

iOS开发——swift篇&经典语法(十九)协议

协议 Protocol(协议)用于统一方法和属性的名称,而不实现任何功能.协议能够被类,枚举,结构体实现,满足协议要求的类,枚举,结构体被称为协议的遵循者. 遵循者需要提供协议指定的成员,如属性,方法,操作符,下标等. 协议的语法 协议的定义与类,结构体,枚举的定义非常相似,如下所示: protocol SomeProtocol { // 协议内容 } 在类,结构体,枚举的名称后加上协议名称,中间以冒号:分隔即可实现协议:实现多个协议时,各协议之间用逗号,分隔,如下所示: struct Some

iOS开发——swift篇&经典语法(二十一)泛型

泛型 泛型代码可以让你写出根据自我需求定义.适用于任何类型的,灵活且可重用的函数和类型.它的可以让你避免重复的代码,用一种清晰和抽象的方式来表达代码的意图. 泛型是 Swift 强大特征中的其中一个,许多 Swift 标准库是通过泛型代码构建出来的.事实上,泛型的使用贯穿了整本语言手册,只是你没有发现而已.例如,Swift 的数组和字典类型都是泛型集.你可以创建一个Int数组,也可创建一个String数组,或者甚至于可以是任何其他 Swift 的类型数据数组.同样的,你也可以创建存储任何指定类型

iOS开发——swift篇&经典语法(十)属性

属性 属性是描述特定类.结构或者枚举的值.存储属性作为实例的一部分存储常量与变量的值,而计算属性计算他们的值(不只是存储).计算属性存在于类.结构与枚举中.存储属性仅仅只在类与结构中. 属性通常与特定类型实例联系在一起.但属性也可以与类型本身联系在一起,这样的属性称之为类型属性. 另外,可以定义属性观察者来处理属性值发生改变的情况,这样你就可以对用户操作做出反应.属性观察者可以被加在自己定义的存储属性之上,也可以在从父类继承的子类属性之上. 1.存储属性 最简单的情形,作为特定类或结构实例的一部

iOS开发——swift篇&经典语法(二十二)类型嵌套

类型嵌套 枚举类型常被用于实现特定类或结构体的功能.也能够在有多种变量类型的环境中,方便地定义通用类或结构体来使用,为了实现这种功能,Swift允许你定义类型嵌套,可以在枚举类型.类和结构体中定义支持嵌套的类型. 要在一个类型中嵌套另一个类型,将需要嵌套的类型的定义写在被嵌套类型的区域{}内,而且可以根据需要定义多级嵌套. 类型嵌套实例 下面这个例子定义了一个结构体BlackjackCard(二十一点),用来模拟BlackjackCard中的扑克牌点数.BlackjackCard结构体包含2个嵌

iOS开发——swift篇&经典语法(四)特性

特性 特性提供了关于声明和类型的更多信息.在Swift中有两类特性,用于修饰声明的以及用于修饰类型的.例如,required特性,当应用于一个类的指定或便利初始化器声明时,表明它的每个子类都必须实现那个初始化器.再比如noreturn特性,当应用于函数或方法类型时,表明该函数或方法不会返回到它的调用者. 通过以下方式指定一个特性:符号@后面跟特性名,如果包含参数,则把参数带上: @attribute name @attribute name(attribute arguments) 有些声明特性

iOS开发——swift篇&经典语法(二十七)Swift与Objective-C简单对比

Swift与Objective-C的对比 系列(一) WWDC 2014上苹果再次惊世骇俗的推出了新的编程语言Swift 雨燕, 这个消息会前没有半点风声的走漏.消息发布当时,会场一片惊呼,相信全球看直播的码农们当时也感觉脑袋被敲了一记闷棍吧.于是熬夜学习了Swift大法,越看越想高呼 ” Swift大法好!“ 程序员,最讲究的就是实事求是和客观,下面就开始对比两种语言. 首先要强调的是,Swift绝对不是解释性语言,更不是脚本语言,它和Objective-C,C++一样,编译器最终会把它翻译成