术语
你用,或不用,术语就在那里,不多不少。你懂,或是不懂,定义就在那里,不偏不倚
程序员总是喜欢说行话
。为了避免困扰,接下里我们会介绍一些贯穿本文的术语定义。我们将尽可能遵守官方文档中的术语用法,使用被Swift社区所广泛接受的定义。这些定义大多都会在接下来的章节中被详细介绍,所以就算一开始你对它们一头雾水,也大可不必在意。如果你已经对这些术语非常了解,我们也还是建议你在浏览一下它们,并且确定你能介绍我们的表述。
在Swift中,我们需要对值,变量,引用以及常量加以区分
值(value)是不变的,永久的,它从不会改变。比如,1,true和[1,2,3]都是值。这些是字面量(literal)的例子,值也可以是运行时生产的。当你算5的平方时,你得到的数字也是一个值。
当我们使用var x = [1,2]
来将一个值进行命名的时候,我们实际上创建了一个名为x的变量(variable�)来持有[1,2]这个值。通过像是执行x.append(3)
这样的操作来改变x时,我们并没有改变原来的值。相反,我们所做的是使用[1,2,3]这个新的值来替代原来x中的内容。可能实际上它的内部实现真的只是在某段内存的后面添加上一个条目,并不是全体的替换,但是至少从逻辑上来说值是全新的。我们将这个过程称为变量的改变。
我们还可以使用let
而不是var
来声明一个常量变量(constant variables),或者简称为常量。一旦常量被赋予一个值,它就不能再次被赋一个新值了。
我们不需要在一个变量被声明的时候就立即为它赋值。我们可以先对变量进行声明let x: Int
,然后稍后再给它赋值x = 1
。Swift是强调安全的语言,它将检查所有可能的代码路径,并确保变量在被读取之前一定是完成了赋值。在Swift中变量不会存在未定义的状态。当然,一个变量使用let
声明的,那么它只能被赋值一次。
结构体(struct)和枚举(enum)是值类型(value type)。当你把一个结构体变量赋值给另一个,那么这俩个变量将会包含同样的值。你可以将它理解为内容被复制了一遍,但是更精确的描述的话,是被赋值的变量与另外的那个变量包含了同样的值。
引用(reference)是一种特殊类型的值:它是一个“指向”另一只值的值。两个引用可能会指向同一个值,这引入了一种可能性,那就是这个值可能会被程序的俩个不同的部分所改变。
类(class)是引用类型(reference type)。你不能在一个变量里直接持有一个类的实例(我们偶尔可能会把这个实例成为对象(object),这个术语经常被滥用,会让人困惑)。对于一个类的实例,我们只能在变量里持有对它的引用,然后使用这个引用来访问团它。
引用类型具有同一性(identity),也就是说,你可以用 === 来检测俩个变量是方法确实引用了同一个对象。如果相应类型的 == 运算符被实现了的话,你也可以用 == 来判断俩个变量是否相等。俩个不同的对象按照定义也是可能相等的。
值类型不存在同一性的问题。比如你不能对某个变量判断它是否和另一个变量持有“相同”的数字。你只能检测它们都包含了2这个值。=== 运算实际做的是询问这两个变量是不是持有相同的引用。在程序语言的论文里,== 有时候被称为结构相等,而===则被称为指针相等或者引用相等
Swift中,类引用不是唯一的引用类型。Swift中依然有指针,比如使用withUnsafeMutablePointer和类似方法所得到的就是指针。不过类是使用起来最简单引用类型,这与它们的引用特性被部分隐藏在语法糖之后是不无关系的。你不需要像在其他一些语言中那样显示的处理指针的解引用。(我们会在稍后的互用性章节中详细提及其他种类的引用)
一个引用变量也可以用let
来声明,这样做回事引用变为常量。换句话说,这会使变量不能被改变为引用其他东西,不过很重要的是,这并不意味着这个变量所引用的对象本身不能被改变。所以,当用常量的方式来引用变量的时候要格外小心,只有指向关系被常量化了,而对象本身还是可变的。(如果前面这几句话看起来有些不明白的话,不要担心,我们在结构体和类还会详细解释)。这一点造成的问题是,就算在一个声明变量的地方看到let
,你也不能一下子就知道声明的东西是不是完全不可变的。想要做出正确的判断,你必须先知道这个变量持有的是值类型还是引用类型。
我们通过值类型是否执行深复制来对他们分类,判断他们是否具有值语义(value semantics)。这种复制可能是在赋值新变量时发生的,也可能会延迟到变量内容发生变更的时候。
这里我们会遇到另一件复杂的事情。如果我们的结构体中包含引用类型,在将结构体赋值给一个新变量时所发生的复制行为中,这些引用类型的内容是不会被自动复制一份的,只有引用本身会被复制。这种复制的行为被称为浅复制(shallow copy)。
举个例子,Foundation框架中的Data结构体实际上是对引用类型NSData的一个封装。不过,Data的作者采取了额外的步骤,来保证当Data结构体发生变化的时候对其中的NSData对象进行深复制。他们使用一种名为写时复制(copy-on-write)的技术来保证操作的高效,我们会在结构体和类里详细介绍这种机制。现在我们需要重点指导的是,这种写时复制的特性并不是直接具有的,它需要额外的进行实现。
Swift中,像是数组这样的集合类型也都是对引用类型的封装,他们同样使用了写时复制的方式来在提供值语义的同时保持高效。不过,如果集合类型的元素是引用类型(比如一个含有对象的数组)的话,对象本身就不会被复制,只有对它的引用会被复制。也就是说,Swift的数组只有当其中的元素满足值语义�时,数组本身才具有值语义。在下一章,我们会讨论Swift中的集合类型与Foundation框架中的NSArray和NSDictionary这些集合类型的不同之处。
有些类是完全不可变的,也就是说,从被创建以后,它们就不提供任何方法来改变它们的内部状态。这意味着即使它们是类,它们依然具有值语义(因为它们就算被到处使用也从不会改变)。但是要注意的是,只有那些标记为final
的类能够保证不被子类化,也不会被添加可变状态。
在Swift中,函数也是值,你可以将一个函数赋值给一个变量,也可以创建一个包含函数的数组,或者调用变量所持有的函数。如果一个函数接收别的函数作为参数(比如map函数接受一个转换函数,并将其应用到数组中的所有元素上),或者一个函数的返回值是函数,那么这样的函数就叫做高阶函数(higher-order function)
函数不需要被声明在最高层——你可以在一个函数内部声明另一个函数,也可以在一个do
作用域或者其他作用域中声明函数。如果一个函数被定义在外层作用域中,但是被传递出这个作用域(比如吧这个函数被作为其他函数的返回值时),他将能够“捕获”局部变量。这些局部变量将存在于函数中,不会随着局部作用域的结束而消亡,函数也将持有他们的状态。这种行为的变量被称为“闭合变量”,我们把这样的函数叫做闭包(closure)。
函数可用通过func
关键字来定义,也可以通过{ }这样的简短的闭包表达式(closure expression)来定义。有时候我们只把通过闭包表达式创建的函数叫做“闭包”,不过不让这种叫法蒙蔽你的双眼。实际上使用func
关键字定义的函数也是闭包。
函数是引用类型。也就是说,将一个通过闭合变量保存状态的函数赋值给另一个变量,并不会导致这些状态被复制。和对象引用类似,这些状态会被共享。换句话说,当俩个闭包持有同样的局部变量时,他们是共享这个变量以及他的状态的。这可能会让你有点儿惊讶,我们将在函数一章中涉及这方面的更多内容。
网友评论