6.Swift初探-枚举、类和结构体
2015-11-04 10:25
417 查看
swift中的枚举可以设置关联值和原始值,关联值用于实例化的时候赋值,原始值用于预设枚举成员
关联值(Associated Values)
上一小节的例子演示了如何定义和分类枚举的成员。你可以为Planet.Earth设置一个常量或者变量,并在赋值之后查看这个值。然而,有时候能够把其他类型的关联值和成员值一起存储起来会很有用。这能让你连同成员值一起存储额外的自定义信息,并且你每次在代码中使用该枚举成员时,还可以修改这个关联值。
你可以定义 Swift 枚举来存储任意类型的关联值,如果需要的话,每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的可识别联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有使用
0到
9的数字的
UPC-A 格式的一维条形码。每一个条形码都有一个代表“数字系统”的数字,该数字后接五位代表“厂商代码”的数字,接下来是五位代表“产品代码”的数字。最后一个数字是“检查”位,用来验证代码是否被正确扫描:
其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO 8859-1 字符,并且可以编码一个最多拥有 2,953 个字符的字符串:
这便于库存跟踪系统用包含四个整型值的元组存储 UPC-A 码,以及用任意长度的字符串储存 QR 码。
在 Swift 中,使用如下方式定义表示两种商品条形码的枚举:
enum Barcode { case UPCA(Int, Int, Int, Int) case QRCode(String) }
原始值(Raw Values)
在关联值小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为原始值)预填充,这些原始值的类型必须相同。这是一个使用 ASCII 码作为原始值的枚举:
enum ASCIIControlCharacter: Character { case Tab = "\t" case LineFeed = "\n" case CarriageReturn = "\r" }
枚举类型
ASCIIControlCharacter的原始值类型被定义为
Character,并设置了一些比较常见的
ASCII 控制字符。
Character的描述详见字符串和字符部分。
原始值可以是字符串,字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的。
注意
原始值和关联值是不同的。原始值是在定义枚举时被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终不变。关联值是创建一个基于枚举成员的常量或变量时才设置的值,枚举成员的关联值可以变化。
递归枚举(Recursive Enumerations)
当各种可能的情况可以被穷举时,非常适合使用枚举进行数据建模,例如可以用枚举来表示用于简单整数运算的操作符。这些操作符让你可以将简单的算术表达式,例如整数5,结合为更为复杂的表达式,例如
5 + 4。
算术表达式的一个重要特性是,表达式可以嵌套使用。例如,表达式
(5 + 4) * 2,乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也需要支持这种嵌套——这意味着枚举类型需要支持递归。
递归枚举(recursive enumeration)是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上
indirect来表示该成员可递归。
例如,下面的例子中,枚举类型存储了简单的算术表达式:
enum ArithmeticExpression { case Number(Int) indirect case Addition(ArithmeticExpression, ArithmeticExpression) indirect case Multiplication(ArithmeticExpression, ArithmeticExpression) }
你也可以在枚举类型开头加上
indirect关键字来表明它的所有成员都是可递归的:
indirect enum ArithmeticExpression { case Number(Int) case Addition(ArithmeticExpression, ArithmeticExpression) case Multiplication(ArithmeticExpression, ArithmeticExpression) }
上面定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。枚举成员
Addition和
Multiplication的关联值也是算术表达式——这些关联值使得嵌套表达式成为可能。
要操作具有递归性质的数据结构,使用递归函数是一种直截了当的方式。例如,下面是一个对算术表达式求值的函数:
func evaluate(expression: ArithmeticExpression) -> Int { switch expression { case .Number(let value): return value case .Addition(let left, let right): return evaluate(left) + evaluate(right) case .Multiplication(let left, let right): return evaluate(left) * evaluate(right) } } // 计算 (5 + 4) * 2 let five = ArithmeticExpression.Number(5) let four = ArithmeticExpression.Number(4) let sum = ArithmeticExpression.Addition(five, four) let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2)) print(evaluate(product)) // 输出 "18"
该函数如果遇到纯数字,就直接返回该数字的值。如果遇到的是加法或乘法运算,则分别计算左边表达式和右边表达式的值,然后相加或相乘。
结构体和枚举是值类型
值类型被赋予给一个变量、常量或者本身被传递给一个函数的时候,实际上操作的是其的拷贝。在之前的章节中,我们已经大量使用了值类型。实际上,在 Swift 中,所有的基本类型:整数(Integer)、浮点数(floating-point)、布尔值(Boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,并且都是以结构体的形式在后台所实现。
在 Swift 中,所有的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型属性,在代码中传递的时候都会被复制。
请看下面这个示例,其使用了前一个示例中
Resolution结构体:
let hd = Resolution(width: 1920, height: 1080) var cinema = hd
在以上示例中,声明了一个名为
hd的常量,其值为一个初始化为全高清视频分辨率(1920 像素宽,1080 像素高)的
Resolution实例。
然后示例中又声明了一个名为
cinema的变量,其值为之前声明的
hd。因为
Resolution是一个结构体,所以
cinema的值其实是
hd的一个拷贝副本,而不是
hd本身。尽管
hd和
cinema有着相同的宽(width)和高(height)属性,但是在后台中,它们是两个完全不同的实例。
类是引用类型
与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,操作的是引用,其并不是拷贝。因此,引用的是已存在的实例本身而不是其拷贝。请看下面这个示例,其使用了之前定义的
VideoMode类:
let tenEighty = VideoMode() tenEighty.resolution = hd tenEighty.interlaced = true tenEighty.name = "1080i" tenEighty.frameRate = 25.0
以上示例中,声明了一个名为
tenEighty的常量,其引用了一个
VideoMode类的新实例。在之前的示例中,这个视频模式(video
mode)被赋予了HD分辨率(1920*1080)的一个拷贝(
hd)。同时设置为交错(interlaced),命名为
“1080i”。最后,其帧率是
25.0帧每秒。
然后,
tenEighty被赋予名为
alsoTenEighty的新常量,同时对
alsoTenEighty的帧率进行修改:
let alsoTenEighty = tenEighty alsoTenEighty.frameRate = 30.0
因为类是引用类型,所以
tenEight和
alsoTenEight实际上引用的是相同的
VideoMode实例。换句话说,它们是同一个实例的两种叫法。
下面,通过查看
tenEighty的
frameRate属性,我们会发现它正确的显示了基本
VideoMode实例的新帧率,其值为
30.0:
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)") // 输出 "The frameRate property of theEighty is now 30.0"
需要注意的是
tenEighty和
alsoTenEighty被声明为常量((constants)而不是变量。然而你依然可以改变
tenEighty.frameRate和
alsoTenEighty.frameRate,因为这两个常量本身不会改变。它们并不
存储这个
VideoMode实例,在后台仅仅是对
VideoMode实例的引用。所以,改变的是被引用的基础
VideoMode的
frameRate参数,而不改变常量的值。
恒等运算符
因为类是引用类型,有可能有多个常量和变量在后台同时引用某一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)如果能够判定两个常量或者变量是否引用同一个类实例将会很有帮助。为了达到这个目的,Swift 内建了两个恒等运算符:
等价于 ( === )
不等价于 ( !== )
以下是运用这两个运算符检测两个常量或者变量是否引用同一个实例:
if tenEighty === alsoTenEighty { print("tenEighty and alsoTenEighty refer to the same Resolution instance.") } //输出 "tenEighty and alsoTenEighty refer to the same Resolution instance."
请注意
“等价于"(用三个等号表示,===) 与
“等于"(用两个等号表示,==)的不同:
“等价于”表示两个类类型(class type)的常量或者变量引用同一个类实例。
“等于”表示两个实例的值“相等”或“相同”,判定时要遵照类设计者定义定义的评判标准,因此相比于“相等”,这是一种更加合适的叫法。
当你在定义你的自定义类和结构体的时候,你有义务来决定判定两个实例“相等”的标准。在章节等价操作符中将会详细介绍实现自定义“等于”和“不等于”运算符的流程。