一、可空类型
使用过C、C++的程序员一定被空指针问题困扰过,我们可以在程序中声明一个指针并将其默认值设置为空NULL,然而在后续的程序中我们可能忘记了初始化该指针就去使用该指针,这样的程序将在运行时(注意是运行时)引发异常状况。类似的情况在Objective-C中也存在,而且编译器并不能帮助你检查出一个指针是不是空指针。Java中的对象引用,其本质也是指针,和C、C++不同的是Java中的指针是不计数的指针,也就是说你只能通过它去引用到某个对象而不能直接将某个地址赋值给它或者通过算术运算修改引用到的对象。同样,如果你忘了初始化引用,或者说引用的值是null,那么将在运行时引发NullPointerException异常。Swift中没有运行时异常机制,Swift更倾向于让问题在编译的时候暴露出来,这无疑是一个很好的决定。在编译时发现的问题通常比在运行时发现的问题更容易修正,而且修改代码所付出的代价也更小。对于空指针(空引用)的问题,Swift是如何处理的呢,我们可以看下的代码。
var str = "Hello, world" println(str)
上面的代码会输出Hello, world,这一点大家都不会陌生,但是如果我们尝试将第一行代码的赋值语句却掉,看看编译器会说什么。
var str println(str)
编译器会报告“Type annotation missing in pattern”的错误。在没有赋值语句的情况下,我们需要为变量指定类型,因为无法根据赋给变量的值来推断出变量的类型。我们试着为str变量指定它的类型,再看看编译器又说了什么。
var str: String println(str)
这一次,编译器将错误报告在打印的地方,说“Variable ‘str’ used before being initialized”。显然str没有初始化,编译器是不会让这样的代码运行的。但是我们可能需要一个变量暂时是一个空值,但是当我们尝试把代码变成下面的样子时,编译器又有话说了。
var str: String = nil println(str)
这一次,编译器说“Type ‘String’ does not conform to protocol NilLiteralConvertible”。字符串不能被赋值为nil。如果真的希望这么做,可以使用可空类型(optional),代码如下所示:
var str: String? println(str)
我们在类型标记的后面加一个问号,就可以将变量声明为可空类型,而且你不用为它赋值为nil,它的值已经是nil,这样我们可以在需要的时候再为变量赋值,完整的代码如下所示:
import Foundation var str: String? str = "Hello, world" println(str?.uppercaseString)
这时候,如果去掉代码中为str变量赋值的那行代码,下面的打印语句将打印出“nil”。
可以通过!强制将可空类型还原成变量的原始类型,当然如果变量的值是nil,则会产生运行时错误,如果变量值不是nil则可以被顺利的还原,如下面代码1所示。当然,也可以下面代码2的方式来获取可空变量的值。两种方式的代码如下所示:
代码1:
import Foundation var str: String? = "Hello, world" println(str!.uppercaseString)
代码2:
import Foundation var str: String? = "Hello, world" if let newstr = str { println(newstr.uppercaseString) }
要使用可空类型还有一种更简单的方式,就是在类型标记后面不写问号而是感叹号,这样的话会有隐式的装箱和拆箱。
二、容器
任何语言中都需要容器,容器顾名思义就是承载其他对象的对象,也就是说是一个对象的持有者。最常见的容器是数组、字典和集合(就是我们在数学书上讲的集合,不允许有重复元素),当然,有的地方也把容器叫做集合框架(这个名字容易跟数学上的集合混淆,因此本书中我们把对象的持有者都称为容器)。在Objective-C的Foundation框架中,最常用的容器有NSArray和NSDictionary,因此Swift中也提供了这两种容器。
1. 数组
跟其他很多语言一样,Swift中的数组也是一系列元素的有序集合,注意这里说的有序并不是指从小到大或者从大到小的顺序,而是指元素一个挨着一个,它们有自己的序号。下面的代码演示了如何创建和使用数组。
var array = [12, 7, 38, 65] println(array[2]) // 38 array.append(93) println(array) // [12, 7, 38, 65, 93] array.extend(100...103) println(array) // [12, 7, 38, 65, 93, 100, 101, 102, 103] array.removeAtIndex(0) println(array) // [7, 38, 65 93, 100, 101, 102, 103]
如果愿意,你也可以使用下面的方式来创建数组对象并指定数组元素的类型。
var yourArray = Array<Int>(count: 5, repeatedValue: 0) yourArray[4] = 1000 println(yourArray) // [0, 0, 0, 0, 1000] var thyArray = [Int](count: 5, repeatedValue: 100) thyArray[2] = 55 println(thyArray) // [100, 100, 55, 100, 100]
2. 字典
顾名思义,被称为字典的容器中存储的元素不是单个的值,而是键和值的映射,就像《新华字典》一样,上面每个可以查的字就是一个键,对这个字的解释就是值。
var myDict = [1: "Dog", 2: "Cat"] println(myDict[2]) // Optional(“Cat”) var yourDict: [Int: String] = [1: "飞机", 2: "坦克"] println(yourDict[3]) // nil
上面的代码创建了两个字典,它们的键都是Int类型,值都是String类型。需要注意的是,当我们想从字典容器中取出数据时,需要通过下标运算用键取到键对应的值,但是这个值可能是不存在的,就像你自己臆造的字在字典中是查不到的,所以如果有和键对应的值,则取出的值是可空类型,如果没有和键对应的值则得到nil。
可以定义常量容器,这样的容器只能在定义的时候完成初始化操作,之后不能再添加元素、删除元素或修改元素的值。
可以通过循环对容器中的元素进行遍历,请看下面的代码。
var myArray = [12, 7, 38, 65] for x in myArray { println(x) } var myDict = [1: "Dog", 2: "Cat", 31: "飞机", 32: "坦克"] for x in myDict.keys { println("\(x) ---> \(myDict[x])") }