Swift教程之类与结构详解

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

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

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

1、类和结构的异同

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

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

定义具有功能性的方法

定义下标,使用下标语法

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

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

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

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

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

类的继承性

对类实例实时的类型转换

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

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

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

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

定义语法

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

代码如下:

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

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

代码如下:

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的整体样式,它们本身不是一个特定的分辨率或者显示方式,这时候就需要实例化这个结构和类。

实例化的语法相似:

代码如下:

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

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

访问属性

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

代码如下:

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

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

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

代码如下:

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

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

代码如下:

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能够直接设置一个结构属性的子属性,就像上面这个例子一样。

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

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

代码如下:

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

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

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

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

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

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

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

代码如下:

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

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

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

代码如下:

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的属性不会变化。

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

代码如下:

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被定义为一个类:

代码如下:

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

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

代码如下:

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

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

代码如下:

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

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

特征操作

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

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

相同的实例(===)

不同的实例(!==)

使用这些操作可以检查:

代码如下:

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的时候,里面的数值同时被完整复制。当改变复制了的数值的时候,原有的数值不会变化,如下例子:

代码如下:

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

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

代码如下:

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

数组的赋值和复制操作

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

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

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

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

代码如下:

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

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

代码如下:

println(a[0])
// 1
println(b[0])
// 1
println(c[0])
// 1

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

代码如下:

a[0] = 42
println(a[0])
// 42
println(b[0])
// 42
println(c[0])
// 42

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

代码如下:

a.append(4)
a[0] = 777
println(a[0])
// 777
println(b[0])
// 42
println(c[0])
// 42

设置数组是唯一的

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

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

代码如下:

b.unshare()

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

代码如下:

b[0] = -105
println(a[0])
// 777
println(b[0])
// -105
println(c[0])
// 42

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

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

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

代码如下:

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."

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

代码如下:

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中去。

代码如下:

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

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

代码如下:

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

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

(0)

相关推荐

  • 详解Swift语言中的类与结构体

    类 在 Swift 中类是建立灵活的构建块.类似于常量,变量和函数,用户可以定义的类的属性和方法.Swift给我们提供了声明类,而无需用户创建接口和实现文件的功能.Swift 允许我们创建类作为单个文件和外部接口,将默认在类一次初始化来创建. 使用类的好处: 继承获得一个类的属性到其他类 类型转换使用户能够在运行时检查类的类型 初始化器需要处理释放内存资源 引用计数允许类实例有一个以上的参考 类和结构的共同特征: 属性被定义为存储值 下标被定义为提供访问值 方法被初始化来改善功能 初始状态是由初

  • Swift中的类class与结构体struct体学习笔记

    一.引言 Swift中的类与结构体十分相似,和Objective-C不同的事,Swift中的结构体不仅可以定义属性,也可以像类一样为其定义方法. Swift中的类与结构体有如下相似点: 1.定义属性来存储值. 2.定义函数来提供功能. 3.通过定义下标语法使用下标的方式取值. 4.定义构造方法来对其进行初始化. 5.通过扩展来在原始基础上添加功能. 6.通过协议来定义实现标准. 当然类和结构体也有许多不同点,下面这些功能是类独有的,结构体没有: 1.通过继承来创建类的子类. 2.在运行时允许对类

  • Swift教程之类的析构详解

    在一个类的实例被释放之前,析构函数会被调用.用关键字deinit来定义析构函数,类似于初始化函数用init来定义.析构函数只适用于class类型. 1.析构过程原理 Swift 会自动释放不再需要的实例以释放资源.如自动引用计数那一章描述,Swift 通过自动引用计数(ARC)处理实例的内存管理.通常当你的实例被释放时不需要手动地去清理.但是,当使用自己的资源时,你可能需要进行一些额外的清理.例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前关闭该文件.

  • Swift教程之类与结构详解

    类与结构是编程人员在代码中会经常用到的代码块.在类与结构中可以像定义常量,变量和函数一样,定义相关的属性和方法以此来实现各种功能. 和其它的编程语言不太相同的是,Swift不需要单独创建接口或者实现文件来使用类或者结构.Swift中的类或者结构可以在单文件中直接定义,一旦定义完成后,就能够被直接其它代码使用. 注意:一个类的实例一般被视作一个对象,但是在Swift中,类与结构更像是一个函数方法,在后续的章节中更多地是讲述类和结构的功能性. 1.类和结构的异同 类和结构有一些相似的地方,它们都可以

  • Swift教程之枚举类型详解

    枚举定义了一个常用的具有相关性的一组数据,并在你的代码中以一个安全的方式使用它们. 如果你熟悉C语言,你就会知道,C语言中的枚举指定相关名称为一组整数值.在Swift中枚举更为灵活,不必为枚举的每个成员提供一个值.如果一个值(被称为"原始"的值)被提供给每个枚举成员,则该值可以是一个字符串,一个字符,或者任何整数或浮点类型的值. 另外,枚举成员可以指定任何类型,每个成员都可以存储的不同的相关值,就像其他语言中使用集合或变体.你还可以定义一组通用的相关成员为一个枚举,每一种都有不同的一组

  • Swift教程之基础数据类型详解

    基础类型 虽然Swift是一个为开发iOS和OS X app设计的全新编程语言,但是Swift的很多特性还是跟C和Objective-C相似. Swift也提供了与C和Objective-C类似的基础数据类型,包括整形Int.浮点数Double和Float.布尔类型Bool以及字符串类型String.Swift还提供了两种更强大的基本集合数据类型,Array和Dictionary,更详细的内容可以参考:Collection Types. 跟C语言一样,Swift使用特定的名称来定义和使用变量.同

  • Swift教程之集合类型详解

    Swift 提供两种集合类型来存储集合,数组和字典.数组是一个同类型的序列化列表集合.字典是一个能够使用类似于键的唯一标识符来获取值的非序列化集合. 在Swift中,数组和字典的键和值都必须明确它的类型.这意味这数组和字典不会插入一个错误的类型的值,以致于出错.这也意味着当你在数组和字典中取回数值的时候能够确定它的类型. Swift 使用确定的集合类型可以保证代码工作是不会出错,和让你在开发阶段就能更早的捕获错误. note: Swift的数组 储存不同的类型会展示出不同的行为,例如变量,常量或

  • Swift教程之基本运算符详解

    运算符是一种特定的符号或表达式,用来检验.修改或合并变量.例如,用求和运算符+可以对两个数字进行求和(如let i = 1 + 2):稍微复杂一点的例子有逻辑与操作符&& (如if enteredDoorCode && passedRetinaScan) ,自增长运算符 ++i (这是i=i+1的简写方式) Swift支持C标准库中的大多数运算符并提升了各自的兼容性,从而可以排除常见的编码错误.赋值操作符 (=)不会返回一个值,这样可以防止你因粗心将赋值运算符 (=)写成

  • Swift进阶教程Mirror反射示例详解

    目录 元类型与.self AnyObject AnyClass Any type(Of:) self self在方法里面的作用 Self Swift Runtime Mirror Mirror的基本用法 Mirror的简单应用-JSON解析 Mirror源码解析 Enum Metadata探索 还原TargetEnumMetadata 还原TargetEnumDescriptor 相对偏移指针 打印枚举中的属性 Struct Metadata探索 获取结构体的属性 swift_getTypeBy

  • Svg.js实例教程及使用手册详解(一)

    什么是SVG? SVG 指可伸缩矢量图形 (Scalable Vector Graphics) SVG 用来定义用于网络的基于矢量的图形 SVG 使用 XML 格式定义图形 SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失 SVG 是万维网联盟的标准 SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体 简介: SVG.js是一个轻量级的JavaScript库,允许你轻松操作SVG和定义动画. SVG(Scalable Vector Graphics,可缩放矢量图形)是基

  • kotlin 官方学习教程之基础语法详解

    kotlin 官方学习教程之基础语法详解 Google 在今天的举行了 I/O 大会,大会主要主要展示内有容 Android O(Android 8.0)系统.Google Assistant 语音助手.Google 智能音箱.人工智能.机器学习.虚拟现实等.作为一个 Android 开发者,我关心的当然是 Android O(Android 8.0)系统了,那么关于 Android O 系统的一个重要消息是全面支持 Kotlin 编程语言,使得 Kotlin 成为了 Android 开发的官方

  • Python NumPy教程之数据类型对象详解

    每个 ndarray 都有一个关联的数据类型 (dtype) 对象.这个数据类型对象(dtype)告诉我们数组的布局.这意味着它为我们提供了以下信息: 数据类型(整数.浮点数.Python 对象等) 数据大小(字节数) 数据的字节顺序(小端或大端) 如果数据类型是子数组,它的形状和数据类型是什么. ndarray 的值存储在缓冲区中,可以将其视为连续的内存字节块.所以这些字节将如何被解释由dtype对象给出. 构造数据类型(dtype)对象 数据类型对象是 numpy.dtype 类的一个实例,

随机推荐