2.4 两种特殊的基本数据类型
Swift语言还支持两种特殊的基本数据类型,分别是元组类型与可选值类型。元组在实际开发中十分常用,开发者使用元组可以创建出任意数据类型组合的自定义数据类型;而可选值类型是Swift语言的一大特点,通过可选值类型,Swift语言对数值为空进行了严格的把控。
2.4.1 元组
元组是Swift语言中重要的数据类型之一,元组允许一些并不相关的类型自由组合成为新的集合类型。Objective-C语言并不支持元组这样的数据类型,这在很多时候会给开发者带来麻烦。类比生活中的一种情景,元组类型类似于日常生活中的套餐,现在各种服务业都推出了许多有特色的套餐供顾客选择,方便为顾客提供一站式服务。元组提供的就是这样一种编程结构,试想一下,编程中遇到这样一种情形:一个商品有名字和价格,使用元组可以很好地对这种商品类型进行模拟,示例如下:
//创建钢笔元组类型,其中有两种类型,即字符串类型的名称和整数类型的价钱 var pen:(name:String,price:Int) = ("钢笔",2)
上面的代码在创建元组类型的同时指定了其中参数的名称,即名称参数为name,价格参数为price,开发者可以使用这些参数名称来获取元组中各个参数的值,示例如下:
//获取pen变量名称 var name = pen.name //获取pen变量价格 var price = pen.price
开发者在创建元组时,也可以不指定元组中参数的名称,元组会自动为每个参数分配下标,下标值将从0开始依次递增,示例如下:
//不指定参数名称的元组 var car:(String,Int) = ("奔驰",2000000) //通过下标来取得元组中各个组成元素的值 var carName = car.0 var carPrice = car.1
元组实例被创建后,开发者也可以通过指定的变量或者常量来分解它,示例如下:
//不指定参数名称的元组 var car:(String,Int) = ("奔驰",2000000) //进行元组的分解 var (theName,thePrice) = car //此时 theName变量被赋值为"奔驰",thePrice变量被赋值为2000000 print(theName,thePrice)
上面的代码将元组实例car中的各个组成元素分解到具体变量,有一点读者需要注意,分解后的变量必须与元组中的元素一一对应(个数相等),否则编译器就会报错。代码中使用的print()函数为打印输出函数,print()函数可以接收多个参数,将其以逗号分隔即可。有些时候,开发者可能并不需要获取某个元组实例中所有元素的值,这种情况下,开发者也可以将某些不需要获取的元素使用匿名的方式来接收,示例如下:
//不指定参数名称的元组 var car:(String,Int) = ("奔驰",2000000) //进行元组的分解,将Int型参数进行匿名 var (theName,_) = car //此时theName变量被赋值为"奔驰" print(theName)
在Swift语言中,常常使用符号“_”来表示匿名的概念,因此“_”也被称为匿名标识符。上面的代码实际上只分解出了元组car中的第一个元素(String类型)。
提示
元组虽然使用起来十分方便,但是其只适用于简单数据的组合,对于结构复杂的数据,要采用结构体或者类来实现。
2.4.2 可选值类型
可选值类型(Optional类型)是Swift语言特有的一种类型。首先,Swift语言是一种十分强调类型安全的语言,开发者在使用到某个变量时,编译器会尽最大可能保证此变量的类型和值的明确性,保证减少编程中的不可控因素。然而在实际编程中,任何类型的变量都会遇到值为空的情况,在Objective-C语言中并没有机制来专门监控和管理为空值的变量,程序的运行安全性全部靠开发者手动控制。Swift语言提供了一种包装的方式来对普通类型进行Optional包装,实现对空值情况的监控。
在Swift语言中,如果使用了一个没有赋值的变量,程序会直接报错并停止运行。读者可能会想,如果一个变量在声明的时候没有赋初值,在后面的程序运行中就有可能被赋值,那么对于这种“先声明后赋值”的应用场景,我们应该怎么做呢?在Objective-C中,这个问题很好解决,只需要使用的时候判断一下这个变量是否为nil即可。在Swift中是否也可以这样做呢?使用如下代码来进行试验:
var obj:String if obj==nil { }
编写上面的代码后,可以看到Xcode工具依然抛出了一个错误提示,其实在Swift语言中,未做初始化的普通类型是不允许使用的,哪怕是用来进行判空处理也不被允许,当然也就不可以与nil进行比较运算,这种机制极大地减小了代码的不可控性。因此,开发者在使用前必须保证变量被初始化,代码如下:
var obj0:String obj0 = "HS" print(obj0)
但是上面的做法在实际开发中并不常用,如果一个变量在逻辑上可能为nil,则开发者需要将其包装为Optional类型,改写上面的代码如下:
var obj:String? if obj==nil { }
此时代码就可以正常运行了。分析上面的代码,在声明obj变量的时候,这里将其声明成了String?类型,在普通类型后面添加符号“?”,即可将普通类型包装为Optional类型。
Optional类型不会独立存在,其总是附着于某个具体的数据类型之上,具体的数据类型可以是基本数据类型,可以是结构体,也可以是类等。Optional类型只有两种值,读者可以将其理解为:
- 如果其附着类型对应的量值有具体的值,则其为具体值的包装。
- 如果其附着类型对应的量值没有具体的值,则其为nil。
举一个例子,将Int类型的变量a进行Optional包装,此时a的类型为Int?,如果对a进行了赋值,则可以通过拆包的方式获取a的Int类型值,如果没有对a进行赋值,则a为nil。
提示
Optional类型中的nil读者也可以理解为一种值,其表示空。
Optional类型是对普通类型的一种包装,因此在使用的时候需要对其进行拆包操作,拆包将使用到Swift中的操作符“!”。“?”与“!”这两个操作符是很多Swift语言初学者的噩梦,如果读者理解了Optional类型,那么对这两个操作符的理解和使用将容易许多。首先需要注意,“?”符号可以出现在类型后面,也可以出现在实例后面,如果出现在类型后面,其代表的是此类型对应的Optional类型,如果出现在实例后面,则代表的是可选链的调用,后面章节会详细介绍。“!”符号同样可以出现在类型后面与实例后面,它出现在类型后面代表的是一种隐式解析的语法结构,后面章节会介绍;出现在实例后面代表的是对Optional类型实例的拆包操作。示例如下:
//声明obj为String?类型 var obj:String? = "HS" //进行拆包操作 obj!
读者需要注意,在使用“!”进行Optional值的拆包操作时,必须保证要拆包的值不为nil,否则程序运行会出错。可以在拆包前使用if语句进行安全判断,示例如下:
//声明obj为String?类型 var obj:String? = "HS" if obj != nil { obj! }
上面的代码演示的编程结构在实际应用中十分广泛,因此Swift语言还提供了一种if-let语法结构来进行Optional类型值的绑定操作,可以将上面的结构改写如下:
上面的代码可以这样理解:如果obj有值,则if-let结构将创建一个临时常量tmp来接收obj拆包后的值,并且执行if为真时所对应的代码块,在执行的代码块中,开发者可以直接使用拆包后的obj值tmp。如果obj为nil,则会进入if为假的代码块中,开发者可以在else代码块中将obj重新赋值使用。这种if-let结构实际上完成了判断、拆包、绑定拆包后的值到临时常量3个过程。
if-let结构中也可以同时进行多个Optional类型值的绑定,之间用逗号隔开,示例如下:
在同时进行多个Optional类型值的绑定时,只有所有Optional值都不为nil,绑定才会成功,代码执行才会进入if为真的代码块中。如果开发者需要在if语句的判断中添加更多业务逻辑,可以通过追加子句的方式来实现,示例如下:
上面的代码在obj1不为nil、obj2不为nil并且obj1所对应的拆包值小于obj2对应的拆包值的时候才会进入if为真的代码块中,即打印绑定的tmp1与tmp2的值。
你可能发现了,对于一个可选值类型的变量,每次使用时我们都需要为其进行拆包操作,这相对会有些麻烦,其实Swift中还有一种语法:隐式解析。隐式解析适用于这样的场景:当我们明确某个变量初始时为nil,并且在之后使用之前一定会被赋值时,我们可以将其声明为隐式解析的可选值,再对这个变量进行使用,就不需要进行拆包操作了,例如下面的代码会产生运行错误:
var obj4:Int? obj4 = 3 print(obj4 + 1) //会编译异常,因为obj4没有进行拆包
如果将上面的代码做如下的修改,就可以正常运行了:
// 声明obj4为隐式解析的变量 var obj4:Int! obj4 = 3 // 在使用时,无须再进行拆包操作,Swift会自动帮我们拆包 print(obj4 + 1)
Optional值在Swift语言编程中的应用十分灵活,在以后的编程练习中,读者会逐步体会其中的奥妙。