Swift学习笔记(四)

八 枚举类型

swift中枚举类型相比OC有更多的功能,用起来也比OC方便,OC使用枚举代码很长,敲起来麻烦,而swift一般直接用.语法,这就要归功于类型推断啦~

 

枚举定义了一个常用的具有相关性的一组数据,并在你的代码中以一个安全的方式使用它们。

如果你熟悉C语言,你就会知道,C语言中的枚举指定相关名称为一组整数值。在Swift中枚举更为灵活,不必为枚举的每个成员提供一个值。如果一个值(被称为“原始”的值)被提供给每个枚举成员,则该值可以是一个字符串,一个字符,或者任何整数或浮点类型的值。

另外,枚举成员可以指定任何类型,每个成员都可以存储的不同的相关值,就像其他语言中使用集合或变体。你还可以定义一组通用的相关成员为一个枚举,每一种都有不同的一组与它相关的适当类型的值的一部分。

Swift中枚举类型是最重要的类型。它采用了很多以前只有类才具有的特性,如计算性能,以提供有关枚举的当前值的更多信息,方法和实例方法提供的功能相关的枚举表示的值传统上支持的许多功能。枚举也可以定义初始化,以提供一个初始成员值;可以在原有基础上扩展扩大它们的功能;并使用协议来提供标准功能。

1、枚举语法

使用枚举enum关键词并把他们的整个定义在一对大括号内:

enum SomeEnumeration {

     enumeration definition goes here

}

下面是一个指南针的四个点一个例子:

 

enum CompassPoint {

    case North

    case South

    case East

    case West

}

在枚举中定义的值(如North,South,East和West)是枚举的成员值(或成员)。这个例子里case关键字表示成员值一条新的分支将被定义。

不像C和Objective-C,Swift枚举成员在创建时不分配默认整数值。在上面的例子CompassPoints中North,South,Eath,West不等于隐含0,1,2和3,而是一种与CompassPoint明确被定义的类型却各不相同的值。

多个成员的值可以出现在一行上,用逗号分隔:

enum Planet {

    case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune

}

每个枚举定义中定义了一个全新的类型。像其他Swift的类型,它们的名称(如CompassPoint和Planet)应为大写字母。给枚举类型单数而不是复数的名字,这样理解起来更加容易如:

var directionToHead = CompassPoint.West

使用directionToHead的类型时,用CompassPoint的一个可能值初始化的推断。一旦directionToHead被声明为一个CompassPoint,您可以将其设置为使用更短的.语法而不用再书写枚举CompassPoint值本身:

directionToHead = .South

directionToHead的类型是已知的,所以你可以在设定它的值时,不写该类型。使用类型明确的枚举值可以让代码具有更好的可读性。

 

2、匹配枚举值与switch语句

你可以使用单个枚举值匹配switch语句:

directionToHead = .East

switch directionToHead {

case .North:

    print("Lots of planets have a north")

case .South:

    print("Watch out for penguins")

case .East:

    print("Where the sun rises")

case .West:

    print("Where the skies are blue")

}

正如控制流所描述,Switch语句考虑枚举的成员,如果省略了West时,这段代码无法编译,因为它没有考虑CompassPoint成员的完整性。Switch语句要求全面性确保枚举成员,避免不小心漏掉情况发生。

 

3、关联值

Swift的枚举类型可以由一些数据类型相关的组成,如果需要的话,这些数据类型可以是各不相同的。枚举的这种特性跟其它语言中的奇异集合,标签集合或者变体相似

例如,假设一个库存跟踪系统需要由两种不同类型的条形码来跟踪产品。有些产品上标有UPC-A代码格式,它使用数字0到9的一维条码,每一个条码都有一个“数字系统”的数字,后跟十“标识符”的数字。最后一位是“检查”位,以验证代码已被正确扫描

其他产品都贴有二维条码QR码格式,它可以使用任何的ISO8859-1字符,并可以编码字符串,最多2,953个字符

这将是方便的库存跟踪系统能够存储UPC-A条码作为三个整数的元组,和QR代码的条形码的任何长度的字符串。

Swift中可以使用一个枚举来定义两种类型的产品条形码,结构可以是这样的:

enum Barcode {

    case UPCA(Int, Int, Int)

    case QRCode(String)

}

这可以被理解为:

“定义一个名为条形码枚举类型,它可以是UPC-A的任一值类型的关联值(Int,Int,Int),或QRCode的一个类型为String的关联值。

 

这个定义不提供任何实际的Int或String值,它只是定义了条形码常量和变量当等于Barcode.UPCA或Barcode.QRCode关联值的类型的时候的存储形式。

然后可以使用任何一种类型来创建新的条码

var productBarcode = Barcode.UPCA(8, 85909_51226, 3)

此示例创建一个名为productBarcode新的变量,并与相关联的元组值赋给它Barcode.UPCA的值(8,8590951226,3)。提供的“标识符”值都有整数加下划线的文字,85909_51226,使其更易于阅读的条形码。

同一产品可以分配不同类型的条形码:

productBarcode = .QRCode("ABCDEFGHIJKLMNOP")

在这一点上,原来Barcode.UPCA和其整数值被新的Barcode.QRCode及其字符串值代替。_条形码的常量和变量可以存储任何一个_UPCA或QRCode的(连同其关联值),但它们只能存储其中之一在任何指定时间。

不同的条码类型像以前一样可以使用一个switch语句来检查,但是这一次相关的值可以被提取作为switch语句的一部分。您提取每个相关值作为常数(let前缀)或变量(var前缀)不同的情况下,在switch语句的case代码内使用:

switch productBarcode {

case .UPCA(let numberSystem, let identifier, let check):

    print("UPC-A with value of (numberSystem), (identifier), (check).")

case .QRCode(let productCode):

    print("QR code with value of (productCode).")

}

 

如果所有的枚举成员的关联值的提取为常数,或者当所有被提取为变量,为了简洁起见,可以放置一个var,或let标注在成员名称前:

switch productBarcode {

case let .UPCA(numberSystem, identifier, check):

    print("UPC-A with value of (numberSystem), (identifier), (check).")

case let .QRCode(productCode):

    print("QR code with value of (productCode).")

}

 

4、原始值

在关联值的条形码的例子演示了一个枚举的成员如何能声明它们存储不同类型的关联值。作为替代关联值,枚举成员可以拿出预先填入缺省值(称为原始值),从而具有相同的类型。

这里是一个存储原始的ASCII值命名枚举成员的一个例子:

enum ASCIIControlCharacter: Character {

    case Tab = " "

    case LineFeed = " "

    case CarriageReturn = " "

}

在这里,原始值被定义为字符类型的枚举叫做ASCIIControlCharacter,并设置了一些比较常见的ASCII控制字符。字符值的字符串和字符的描述。

原始值可以是字符串,字符,或任何整数或浮点数类型。每个原始值必须在它的枚举中唯一声明。当整数被用于原始值,如果其他​​枚举成员没有值时,它们自动递增。

下面列举的是一个细化的早期Planet枚举,使用原始整数值来表示每个Planet的太阳系的顺序

enum PlanetInt: Int {

    case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune

}

自动递增意味着Planet.Venus具有2的原始值,依此类推。

 

访问其rawValue方法枚举成员的原始值:

PlanetInt.Mars.rawValue

 

使用枚举的init(rawValue: Int)方法来试图找到一个特定的原始值枚举成员。这个例子识别Uranus的位置通过原始值为7:

let possiblePlanet = PlanetInt.init(rawValue: 7)

然而,并非所有可能的Int值都会找到一个匹配的星球。正因如此,该init(rawValue: Int)方法返回一个可选的枚举成员。在上面的例子中,是possiblePlanet类型Planet?或“可选的Planet”。

如果你试图找到一个Planet为9的位置,通过fromRaw返回可选的Planet值将是无:

let positionToFind = 9

if let somePlanet = PlanetInt.init(rawValue: positionToFind) {

    switch somePlanet {

    case .Earth:

        print("Mostly harmless")

    default:

        print("Not a safe place for humans")

    }

} else {

    print("There isn't a planet at position (positionToFind)")

}

 

类与结构体

: Playground - noun: a place where people can play

 

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

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

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

 

1、类和结构的异同

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

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

定义具有功能性的方法

定义下标,使用下标语法

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

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

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

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

 

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

类的继承性

对类实例实时的类型转换

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

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

 

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

 

定义语法

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

 

struct Resolution {

    var width = 0

    var height = 0

}

class VideoMode {

    var resolution = Resolution()

    var interlaced = false

    var frameRate = 0.0

    var name: String?

}

 

类和结构的实例

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

实例化的语法相似:

 

let someResolution = Resolution()

let someVideoMode = VideoMode()

 

访问属性

someResolution.height

someVideoMode.name

 

赋值

someVideoMode.name = "huihui"

 

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

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

let vga = Resolution( 640, height: 480)

但是和结构不同,类实例不能够使用成员初始化方法

 

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

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

所有Swift中的基础类型-整型,浮点型,布尔类型,字符串,数组和字典都是数值类型。它们也都是由结构来实现的。

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

例如

let hd = Resolution( 1920, height: 1080)

var cinema = hd

 

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

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

 

cinema.width = 2048

print("cinema is now (cinema.width) pixels wide")

print("hd is still (hd.width) pixels wide")

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

 

3、类是引用类型

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

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可以证明这个问题:

 

print("The frameRate property of tenEighty is now (tenEighty.frameRate)")

 

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

 

特征操作

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

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

相同的实例(===)

不同的实例(!==)

if tenEighty === alsoTenEighty {

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

}

 

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

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

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

当你定义一个类的时候,就需要说明什么样的时候是两个类相等,什么时候是两个类不相等

 

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

 

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

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

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

字典的赋值和复制操作

每次将一个字典Dictionary类型赋值给一个常量或者变量,或者作为参数传递给函数时,字典会在赋值或者函数调用时才会被复制。

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

 

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

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

var copiedAges = ages

 

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

copiedAges["Peter"] = 24

print(ages["Peter"])

prints Optional(23)

 

数组的赋值和复制操作和字典Dictionary类型一样

 

var a = [1, 2, 3]

var b = a

var c = a

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

a[0]

b[0]

c[0]

都是1

然后改变a

a[0] = 42

print(a[0])

 42

print(b[0])

print(c[0])

b,c 都还是1

 

 

 

 

 

 

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/yu3-/p/5001839.html