您的位置:首页 > 移动开发 > Swift

Swift中的可选类型(Optional)以及?和!的用法详解

2016-04-02 18:38 423 查看
大家好呀!逗比老师又来啦!今天要给大家分享的是Swift语言中的Optional类型,哈哈,我相信很多初学Swift的同学一定会和我一样,在这个地方各种懵圈,看到那个神马问号啦,感叹号啦,会感觉一个头四个大,尤其是从其他语言过渡过来的同学更是啦。没错,这个可选类型的确是Swift语言的一大特色,不过呢,一开始也很让人费解,但是不要紧,只要你真的学会了,掌握了,你就会发现,它真的相当相当好用。

举一个例子来说明可选类型的用途吧(如果你看不太懂C语言代码的话,没关系,跳过这一段就行,不会影响接下来的理解),在C语言中,假如我们需要这么一个函数,传入一个字符,返回一个无符号短整型,把字符变成数字,这个函数实现起来很简单,给出一个简单实例:
unsigned short charToUshort(const char input) {
return input - '0';
}
好,这么写当然没问题,但是呢,有一个问题,假如说我传入的字符并不是一个数字字符(比如说'a', '*', ' ', '\t'之类的),那么返回出来的一定不是0-9这十个数字间的。那么,我们就需要做一个错误判断,如果输入的不是合法数字,就要返回一个错误:
#include <ctype.h>
unsigned short charToUshort(const char input) {
if (isnumber(input)) {
return input - '0';
}
return /*这到底写个啥好咧??!!→_→*/
}
你一定为类似这样的问题苦恼过,是的,这就逼着我们必须要取一个值来表示“不合法”,而表示不合法的值一般会取0,但是在这个函数里显然不能取0,如果你写的函数要求能够对任意的取值都能进行处理,那就根本找不到一个可以表示“不合法”的这么一个值(比如说在变参函数中,以哪个值表示参数结尾)。Swift语言中为了解决这个问题,创造了可选类型,这种类型提供一种合法的表示“没有值”的这么一个值,也就是nil。

接下来介绍如何定义一个可选类型的变量。首先要知道,在Swift中,任何一个类型都存在与其对应的可选类型。比如说,我们想定义一个整型变量,它可能有值,也可能没有值,那么就需要这样来书写:
var optional_int: Int?
在对应的类型后面加一个问号,表示该变量为一个可选类型,这里需要注意的是,通常Swift语言中的变量在定义时并不会赋初始值,但可选类型是个例外,它的初始值为nil。那么我们在使用时,就可以这么来用:如果这个变量有值,那么就取出它的值,否则,做一些其他的处理:
if optional_int != nil {
print(optional_int!) // 感叹号在这里表示解包,即取出其值,作为Int类型返回
} else {
// 说明值为nil,在这里做一些其他的事情(例如异常处理、或指定其值)
}
这里一定一定要注意的是,如果一个变量为nil,则不可莽撞对其进行解包,例如:
var test: Int? = nil
print(test!) // 这里会运行报错,直接被crash掉!!
所以,如果要使用可选类型,记住万万不可偷懒,如果要解包,一定要进行判断以确保此时变量非空!

使用这种显式的可选类型虽好,但是每次都需要解包,有时会比较麻烦,因此,Swift提供了另一种定义可选类型的方法,用这种方法定义的可选类型的变量,将会在它每一次被调用时自动解包:
var test: Int!
test = 8
print(test)
上面的代码等价于
var test: Int?
test = 8
print(test!)
会在变量每一次取值时自动解包。如果要使用这种方法,每次调用时会自动解包,那么更需要注意变量是否为空,例如下面的代码就会在运行时被crash掉:
var test: Int!
print(test) // 此行会运行报错,因为对一个nil变量进行了解包(解包操作是自动进行的)


以上的内容并不难,但是我们在实际运用时,会将可选变量和各种对象相结合,这时会变得非常复杂,以至于会出现很多令人费解的语法,接下来我们就来看看一个可选对象的情况:
class TestClass {
func show() {
print("show!!")
}
}

var t1: TestClass? = TestClass() // 初始化一个可选对象t1,并赋值
t1?.show() // 调用t1的show()方法
注意,在通过t1调用show()方法时,我们在t1后面多加了一个问号,这是什么意思呢?因为t1是可选类型,可能有值也可能没有,这里加的问号就表示,如果t1有值,那么它一定是TestClass的一个实例,我们调用这个实例的show()方法,而如果t1为nil,则当前操作不进行。换句话说,上面的操作等价于下面的代码:
if t1 != nil {
t1!.show()
}
是的,用问号加方法,你就不用再去专门判断一下它是否为nil了,否则,你就需要确定它非空,然后将其解包得到TestClass类型的对象,再调用它的show()方法,就像上面那样。

当然,如果你是这样写的话:
var t1: TestClass! = TestClass()
t1.show()
那就和前面说的一样了,t1会在调用时自动解包,所以如果t1为空,那么就会报错。这样显然没有前一种方法安全,所以到底用?还是!来定义可选类型,还是得视情况而定。

Swift语言是一门面向对象的语言,在使用OO时,我们会经常使用到多态,而使用了多态,就不得不考虑对象类型转换的问题。例如:
class BaseClass {
func show() {
print("base")
}
}

class SubClass1: BaseClass {
override func show() {
print("sub1")
}
func show2() {
print("sub1 show2")
}
}

class SubClass2: BaseClass {
override func show() {
print("sub2")
}
func show3() {
print("sub3 show3")
}
}

func test(object: BaseClass) {
// 注意这个函数
}
如果要在test()函数中调用object的show()方法,没问题,因为只要是BaseClass或其派生类的对象,一定含有这个方法
func test(object: BaseClass) {
object.show() // 完全没有问题
}
可是,如果我们要调用show2()方法呢?那么我们就得保证这个object变量得是一个SubClass1类型的对象,然后我们把它强转成SubClass2类型,再调用它的show2()方法。问题是,传入这个函数的合法参数是BaseClass类型,我们并不能保证它一定能被转化成SubClass1类型,因此,转化有可能失败:
func test(object: BaseClass) {
let temp = object as? SubClass1 // 把object强转成SubClass1类型,如果失败,则返回nil,因此,temp的类型为SubClass1?类型
if temp != nil {
temp!.show2() // 如果temp非空,则将其解包,并调用show2()方法
} // 否则,跳过此步骤
}
这样写代码过于冗长,我们可以利用前面介绍的技巧,把代码简化成下面的样子:
func test(object: BaseClass) {
(object as? SubClass1)?.show2()
}
如果上面讲述的内容你都明白了,那么理解这行代码也就不成问题了。

当然,与之相对的,如果你能够以各种方式确定object一定能够强转成SubClass1类型,你还可以这么写:
func test(object: BaseClass) {
(object as! SubClass1).show2()
}
这样写的含义就是,括号中的内容返回了一个SubClass1!类型的量,在调用show2()方法前会自动解包(同样,如果转化失败,会报错)

值得注意的是,我们以上的例子都是把一个父类的对象强转为一个子类对象,这样的转化可能会出现不成功的情况,所以我们才要以各种方式来避免错误,而如果我们是将一个子类对象转化为父类对象,那么这是一定会成功的,我们就没有必要再去使用可选类型了,比如下面的例子:

func test2(object: SubClass1) {
(object as BaseClass).show() // 转化永远成功,所以直接转化即可
}
好啦,到这里相信你已经把可选类型这部分的概念理解得差不多啦。最后,我们来考虑这么一个问题。我们在定义一个 对象变量的时候,如果我们不显示的指定它为可选类型,那么它会被默认的定义为普通的类型,例如:
class TestClass {
func show() {
print("test")
}
}

var t1 = TestClass() // t1的类型为TestClass
var t2: TestClass? = TestClass() // 只有显示指定才会被定义为可选类型
这是因为,系统默认生成的构造函数,返回的类型就是一个普通的对象类型,那么,我们可不可以像函数那样,直接返回一个可选类型的对象呢?当然可以了,请看下面的书写方式:
class TestClass {
var a: Int
init() {
// 标准的构造函数,返回普通类型
a = 0
}
init?(a: Int?) {
// 可选构造函数,返回可选类型(即TestClass?)
if a != nil {
self.a = a!
} else {
self.a = 0
}
}
init!(b: Int!) {
// 自解包可选构造函数,返回自解包可选类型(即TestClass!)
a = b
}
}

var t1 = TestClass() // t1的类型为TestClass
var t2 = TestClass(a: 5) // t2的类型为TestClass?
var t3 = TestClass(b: 5) // t3的类型为TestClass!
只需要在init后面适当的加上?或者!就可以达到这种效果,这种构造函数的书写方法在实际的开发中是经常用到的,你也可以在iOS提供的类库中找到很多这样的类的声明。

好啦,关于Swift可选类型的内容就是这些啦!如果你还有要补充的或者有什么不明白的,欢迎在下面留言,我们来一起交流和讨论。今天就逗比到这里,下次见喽!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: