swift类和结构体_swift面向对像编程

分享到:

类别和结构是通用的,灵活的结构有助于编制程序代码。你可以通过准确使用有关常量、变量和功能同样的句法,定义属性和类函数以为类别和结构增加函数性。Swift不同于其他编程语言,其不需要为自定义类别和结构创造独立的界面和实施文件。在Swift语言中,你可在单个文件里定义类别和结构,其外部界面自动供其他代码使用。

注:一个类的实例一般被称为一个对象。然而,Swift语言的类别和结构在功能性方面比在其他语言中相近的多,而且本章节所述的功能性,可用于类别或结构类型的实例中。正因为如此,将会用到更普通的术语实例。

比较类别和结构

 

Swift的类和结构有许多共同之处。均可以:

    定义存储值的属性

    定义规定功能的类函数

    定义下标利用下标句法访问其数值

    定义初始化块设置其初始状态

    扩展其默认实现外的功能

    依照协议规定某种标准功能

若需了解更多信息,请参见属性,类函数,下标,初始化,扩展和协议。

类具有结构不具有的额外功能:

    继承功能使一个类继承另一个类的特征。

若需了解更多信息,请参见继承,类型转换,初始化和自动引用计数。

注:结构总是在被传递至代码时被处理,并且不使用引用计数。

定义语法

 

类和结构具有相似的定义语法。你利用class关键字引用类,struct关键字引用结构。其整个定义必须置于一对大括号内: class SomeClass {

 

// class definition goes here

}

struct SomeStructure {

// structure definition goes here

}

 

注:每当你定义一个新类或结构时,你有效地定义一个全新的Swift类型。赋予类型UpperCamelCase名称(例如此处为SomeClass和SomeStructure)以匹配标准Swift类型(例如字符串、整数型和布尔型)的大写操作。相反地,始终赋予属性和类函数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,用于说明基于像素的显示分辨率。这种结构包含两个存储属性,即width和Height。存储属性为常量或者变量,可以作为类别或结构的一部分进行组合和存储。通过将其设置为初始整数值0,可推出这两种属性为Int类型。

上面的例子也详述了一个新建类别称为VideoMode,用于说明视频显示的特定视频模式。这个类具有四个变量存储属性。第一个,resolution,与一个新建Resolution结构实体进行初始化,可推出Resolution的属性类别。对于其它三个属性,新建VideoMode实体将与false的一个interlaced设置(指非交织视讯)进行初始化,0.0的播放帧率,以及一个可选String值称为name。name的属性将自动设置默认值为nil,或设置为“无name值”,因其为可选类型。

类和结构实体

 

Resolution结构定义以及VideoMode类别定义只说明什么是Resolution或VideoMode。其本身不能描述一个特定的分辨率或视频模式。要做到这一点,你需要创建一个结构或类实体。

创建实体的语法与结构和类非常类似:

 

let someResolution = Resolution()

let someVideoMode = VideoMode()

 

结构和类均使用初始化语法创建新实体。初始化语法最简单的形式是采用类或结构的类型名称加空括号的形式,如Resolution()或VideoMode()。这就为类或结构创建了一个新建实体,所有属性均被初始化至默认值。在初始化中更加详细地描述了类和结构初始化。

 

访问属性

 

您可以利用点语法访问实体属性。在点语法中,你可以在实体名称后接属性名称,用句号(.)隔开,中间不要空格:

 

println("The width of someResolution is \(someResolution.width)")

// prints "The width of someResolution is 0"

 

在这个例子中,someResoluton.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你可以直接设置结构属性的子属性。在上述最后一个例子中,someVideoMode中resolution属性中的width属性被直接设置,你无需为所有resolution属性设置新值。

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

 

所有结构均含有一个自动生成的成员逐一初始化程序,你可以使用其初始化新建结构实体中的成员属性。新建实体中的属性初始值可以通过名称传递至成员逐一初始化程序。

 

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

 

与结构不同,类实体不能接收默认的成员逐一初始化程序。在初始化更加详细地描述了初始化程序。

结构和枚举均属于值类型

 

值类型被赋值为变量或常量或当其被传递到函数时所复制的类型。

在前述章节中,实际上你已经广泛应用了值类型。实际上,所有Swift中的所有基本类型—整数、浮点数、布尔值、串符、数组及词典---均为值类型,并作为后台应用结构。

在Swift内,所有结构和枚举均为值类型。这意味着你所创建的任何结构或枚举实体,以及其所有作为属性的值类型,在通过你的代码时常被复制。 考虑这个例子,即从先前例子中选用一个Resolution结构:

 

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

var cinema = hd

 

这个例子说明了一个称为hd的常量并将其设置到Resolution中,与全高清视频的宽度和高度(1920像素宽x1080像素高)进行初始化。

然后,声明一个称为cinema的变量并设置其当前值为hd。因为Resolution是一个结构,因此会拷贝现有实体,新建拷贝将赋值到cinema中。虽然现在hd和cinema都含有相同的宽度和高度,但是它们却是后台中完全不同的两个实体。

然后,修改cinema的width属性,使宽度达到较宽2K标准,用于数字电影投影(2048像素宽x1080像素高):

 

cinema.width = 2048

 

检查cinema的width属性显示确实已经更改为2048:

 

println("cinema is now \(cinema.width) pixels wide")

// prints "cinema is now 2048 pixels wide"

 

然而,原hd实体的width属性的旧值仍为1920:

 

println("hd is still \(hd.width) pixels wide")

// prints "hd is still 1920 pixels wide"

 

 

当cinema配置为当前数值hd时,存储在hd中的数值将被拷贝至新建cinema实体中。最终结果是产生两种完全独立的实体。而其又恰好包含相同的数值。因为这两个实体完全独立,将cinema宽度设置为2048并不会影响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配置为currentDirection时,实际上是对该数值进行了一次拷贝。改变currentDirection的数值之后并不会影响存储在rememberedDirection的原始数值的拷贝。

 

类是引用类型

 

与数值类型不同,引用类型被赋值为变量或常量或当其被传递至函数时,并不会进行拷贝。相反使用现有的相同实体的引用,而不是副本。 下述示例中使用了以上定义的VdeoMode类:

 

let tenEighty = VideoMode()

tenEighty.resolution = hd

tenEighty.interlaced = true

tenEighty.name = "1080i"

tenEighty.frameRate = 25.0

 

这个例子说明了一个新建常量,即tenEighty,并将其设置为指代Video Mode类别中的一个新建实体。视频模式由之前的1080配置为高清分辨率1920的副本。其被设置为隔行扫描并命名为“1080i”。最后,其帧速率被设置为25.0帧/秒。

 

然后,tenEighty被赋值为一个新建常量,即alsoTenEghty,此时alsoTenEghty的帧率会被改变:

 

let alsoTenEighty = tenEighty

alsoTenEighty.frameRate = 30.0

 

因为类是引用类型,tenEighty和alsoTenEighty实际上均指代同一个VideoMode实体。实际上,他们只是相同单一实体的两个不同的名称。

通过检查tenEighty的frameRate属性,我们发现其可以从相关的VideoMode实体中正确报告出新帧率为30.0。

 

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

// prints "The frameRate property of tenEighty is now 30.0"

 

注,声明tenEighty和TenEighty为常量,而不是变量。但是,因为tenEighty和alsoTenEighty的常量数值本身不会发生实际变化,你仍然可以改变tenEighty.frameRate和 alsoTenEighty.frameRate。tenEighty和alsoTenEighty本身不会“存储”VideoMode实体,但是两者均指代后台的VideoMode实体发生变化的是相关的VideoMode的frameRate属性,而非该VideoMode引用的常量值。

 

恒等运算符

 

因为类是引用类型,因此多个常量和变量可以指代后台某一类别的相同的单一实体。(结构和枚举则不同,因为其为数值类型且被赋值到一个常量或变量或传递给函数时,通常会被拷贝。)

这有时可以帮助识别两个常量或变量是否指代某一类的同一实体。为了启用该特性,Swift规定了两个恒等运算符:

等于 (===)

不等于 (!==)

使用这些操作程序检查两个常量或变量是否指代相同的单一实体。

 

if tenEighty === alsoTenEighty {

println("tenEighty and alsoTenEighty refer to the same Resolution instance.")

}

// prints "tenEighty and alsoTenEighty refer to the same Resolution instance."

 

注“相同”(用三个等号或===表示)并不与“等于”(用两个等号或==表示)表示同一事物:

 

same class instance.

 

“等于” 指两个实例的值是“相等的” or “等价物”, 对于“等于”的某些适当意思, 由类型的计划着定义。

当你在定义你自己的自定义类和结构时,你应该负责决定两个实体“相同”的依据是什么。“等于”及“不等于”运算符的自身实现的定义过程,见等价运算符。

 

指示字

 

如果你有C、C++或Objective-C方面的经验,你可能就知道这些语言使用指示字指代内存中的地址。一个Swift常量或者变量指代一些引用类型中的一个实体类似于C中的指示字,但这个指示字不会直接指示内存地址,你也无需用星号(*)来注明正在创建一个引用。相反,和任何常量或变量一样,在Swift内也定义了这些引用。

 

在类和结构之间选择

 

你可以使用类和结构定义自定义数据类型,并作为程序代码的组块使用。

然而,结构实体总是按值传递,类实体总是按引用传递。这意味着类和结构适合于不同类型的任务。当你考虑根据项目所需的数据结构和功能,决定每个数据结构是否应被定义为类或结构。

作为一般指引,当适用其中一个或多个条件时考虑创建一个结构:

该结构的最初目的是压缩几个比较简单的数据值。

当用户配置或传递该结构实例时,封装值会被拷贝而不是被引用,这是合理的期望。

由该结构存储的任何属性是其自身的数值类型, 其也会被期望拷贝而不是引用。

该结构不需要从另外的现存类型继承属性或行为。

优秀入选的结构示例包括:

几何形状的尺寸,或许以宽属性和高属性进行封装,均以Double类型。

引用一个系列的排列的方法, 或许以开始属性和长度属性进行封装, 均以Int型。

A point in a 3D coordinate system三维坐标系里的一个点, 或许以x, y, z属性进行封装,均以Double类型。

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

 

集合类型的赋值和拷贝行为

 

Swift的Array和Dictonary类型作为结构应用。然而,当被赋值到一个常量或变量时及被传递到一个函数或类函数时,数组与字典和其他结构具有略微不同的拷贝行为。

以下Array和Dictionary所描述的行为与基础中NSArray和NSDictionary的行为又有所不同,该行为按类而不是按结构实现。NSArray和NSDictionary实体总是按引用而不是按拷贝赋值并传递给现有实体。

注:以下描述指数组、字典、字符串和其他数值的“拷贝”。涉及拷贝的,在代码中看到的行为将始终像拷贝一样。然而,Swift只在绝对必要时在幕后进行实际拷贝。Swift对所有值拷贝进行管理,以确保获得最佳性能,你不应回避试图优先优化的赋值。

 

字典的赋值和拷贝行为

 

每当向常量或变量赋值一个Dictionary实体时,或向一个函数或类函数调用传递一个作为参数的字典实体时,字典就会在赋值或调用时进行拷贝。这将在结构和枚举是数值型中被描述。

如果Dictionary实体中存储的密钥和/或值属于值类型(结构或枚举),当赋值或调用时,对这些密钥和/或值进行拷贝。相反,如果密钥和/或值属于引用类型(类或函数),这些引用会进行拷贝,但不是他们所指的类实体或函数。这种拷贝字典密匙和值的行为与结构拷贝时结构的存储属性的拷贝行为相同。

下面的例子定义了一个称为ages的字典,其中存储了四个人的名字和年龄。然后,将该ages字典赋值到一个名称为copiedAges的新变量中,并在赋值时进行拷贝。赋值后,ages和copiedAges是两个独立的字典。

 

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

var copiedAges = ages

 

此字典的密匙为String类型,其数值为Int型。在Swift中,这两种类型均属于值类型,所以在对字典进行拷贝的同时对该密匙和值进行拷贝。

可证明ages字典已通过更改其中一个字典中的年龄值并按照另一字典中相应的值进行了拷贝。如果将copiedAges字典中“Peter”的值设为24,拷贝前,年龄字典仍返回旧值23:

 

copiedAges["Peter"] = 24

println(ages["Peter"])

// prints "23"

 

 

数组的赋值和拷贝行为

 

Swift的Array类型的赋值和拷贝行为比其Dictionary类型的赋值和拷贝行为更加复杂。当使用数组内容时,Array规定了类一样的性能,并在拷贝必要时拷贝数组的内容。

当向常量或变量赋值一个Array实体时或向一个函数或类函数调用传递一个作为参数的Array实体时,在赋值或调用时该数组的内容不会进行拷贝。相反,两数组共用相同序列的元素值。当你通过数组对其中一个元素值进行修改时,通过另一个阵列是可以观察到结果的。

对于数组,拷贝只发生在执行有可能修改数组长度的行为时。其中包括项目追加、插入或移除,或使用远程下标取代数组中一系列项目。只有当进行数组拷贝时,对数组内容拷贝的行为才与字典密匙和值的拷贝行为相同,如字典的配置与拷贝行为所述。

下面的示例向a变量赋值了一个Int值的新数组。还将该数组赋值到另外两个进一步变量b和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中添加一个新项,你实际上修改了数组的长度。这将提示Swift在追加新值时对数组进行新的拷贝。从此,a为本数组的一个单独且独立的副本。

如果在拷贝后改变a中的一个值,a将返回一个不同于b和c的值,拷贝前,b和c两个值仍引用原有的数组内容:

 

a.append(4)

a[0] = 777

println(a[0])

// 777

println(b[0])

// 42

println(c[0])

// 42

 

 

确保数组的唯一性

 

确保在对数组内容进行任何行为前或将数组传递给函数或类函数前单独对数组进行唯一拷贝,这可能有用。通过调用数组类型变量上的unshare类函数确保数组引用的唯一性。(在常量数组内,不能调用unshare类函数。)

如果多个变量目前指的是同一数组,并调用这些变量之一上unshare类函数,对该数组进行拷贝,从而使变量对数组进行单独拷贝。然而,如果已经声明该变量仅能由数组引用,则不能进行拷贝操作。

在前述示例的最后,b和c均引用了相同的数组。调用unshare类函数使b成为独特拷贝:

 

b.unshare()

 

如果在调用unshare类函数后改变b中的第一个值,所有三个数组现在会报告不同的值:

 

b[0] = -105

println(a[0])

// 777

println(b[0])

// -105

println(c[0])

// 42

 

检查是否两个数组共用相同的元素

 

通过与恒等运算符比较,检查两个数组或子数组是否共享相同的存储器和元素(===和!==)

下面的示例使用“相同”运算符(===)来检查b和c是否仍共享相同的数组元素:

 

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

 

或者,使用恒等运算符检查两个子数组是否共享相同的元素。下面的示例对b中两个相同的子数组进行了比较,并确认其指的是相同元素:

 

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

 

数组的强制拷贝

 

通过调用数组的拷贝方法,强制拷贝数组的显式副本。该类函数对数组进行了浅拷贝,并返回一个包含拷贝项目的新数组。

下面的示例定义了一个名称为names的数组,其中存储了七个人的名字。将一个名称为copedNames的新变量设为调用names数组上copy类函数的结果:

 

var names = ["Mohsen", "Hilary", "Justyn", "Amy", "Rich", "Graham", "Vic"]

var copiedNames = names.copy()

 

可证明names数组已通过更改其中一个数组中的一个项并按照另一数组中相应的值进行了拷贝。如果将copiedNames数组中第一项设为“MO”,而不是“Mohsen”,names数组在拷贝前仍返回旧值“Mohsen”:

 

copiedNames[0] = "Mo"

println(names[0])

// prints "Mohsen"

 

注:如果只需要确保对数组内容的引用是现存的唯一引用,那么调用unshare类函数,而不是copy类函数。unshare类函数并不拷贝数组,除非有必要这么做。copy类函数可一直拷贝数组,即使已取消了本数组的共享条件。

昵    称:
验证码:

相关文档:

swift
IOS实例
ObjectiveC