第 1 章 Swift简介

第1章 Swift简介

欢迎来到Swift这个美丽的新世界。如果你一直关注新闻,可能听说过Swift是苹果公司打造的一款全新编程语言,目的是让开发人员能够更轻松、更高效地编写iOS和Mac应用。Swift简单易学,你在不知不觉间就能编写出简单应用。

Swift提供了一些编写代码的新方式,比功能强大而著名的前身Objective-C容易理解得多。Swift向开发人员提供了全新而有趣的方式表达,其功能学习起来也很有趣。

这种新语言功能强大、语法灵活,使用它来表达思想易如反掌。

鉴于Swift刚推出不久,苹果公司很可能对其进行修改和增补。从未有一种计算机语言像Swift这样,在即将修改和修订前能获得如此高的曝光度和采纳度,这都要归功于Swift的创新带来的刺激。

1.1 革命性的改良

语言是分享、交流和传达信息的工具,人类通过它向朋友、家人和同事表达自己的意图。与计算机系统交流也需要通过计算机语言,它们与人类语言的基本宗旨一样。

与人类的语言一样,计算机语言也非新鲜事物,事实上,它们以这样或那样的形式存在了很多年。计算机语言的目的始终是让人类能够与计算机交流,命令它执行特定的操作。

不断发展变化的是计算机语言本身。早期的计算机开拓者意识到,以0和1的方式向计算机发指令既繁琐又容易出错。一路上人们始终在不断努力,旨在在语言语法的丰富性和处理与解读它所需的计算能力之间寻求平衡,最终诸如C和C++语言在争夺现代计算机应用程序通用语言之战中取得了胜利。

在C和C++被广泛接受,得以用于主要的计算平台的同时,苹果携Objective-C给这场盛宴带来了清新之风。Objective-C是一款建立在C语言基础之上的丰富语言,既具备历史悠久的C语言的强大功能,又融合了面向对象的设计理念。苹果生态系统由Macintosh计算机和iOS设备构成,在为该生态系统开发应用程序中,Objective-C多年来始终发挥着中流砥柱的作用。

Objective-C虽然功能强大而优雅,但也存在着C语言遗留下来的包袱。对于熟悉C语言的人来说,这根本就不是什么问题,但近年来大量新开发人员进入Mac和iOS平台,他们渴望更容易理解和使用的新语言。

为满足这种需求,并降低进入门槛,苹果公司推出了Swift。使用它编写应用程序容易得多,向应用程序发出指令也更加简便。

1.2 准备工作

你可能会问,要学习Swift需要满足哪些条件呢?实际上,开始阅读本书就迈出了学习Swift的第一步。学习新的计算机语言可能令人望而却步,这正是笔者为Swift初学者编写本书的原因所在。如果你是Swift新手,本书正是为你编写的;如果你从未使用过C、C++和Objective-C,本书也适合你阅读。即便你是经验丰富的开发人员,熟悉前面提及的各种语言,本书也可帮助你快速掌握Swift。

虽然并非绝对必要,但熟悉或大致了解其他编程语言对阅读本书很有帮助。本书不介绍如何编程,也不提供有关软件开发的基本知识,而假定你对计算机语言的基本概念有一定认识,因此你必须对计算机语言有所了解。

虽然如此,本书将向你提供尽可能多的帮助:详尽地解释新引入的术语,并对概念做尽可能清晰的阐述。

1.2.1 专业工具

至此,你做好了学习Swift的心理准备。这很好!但首先得将学习用品准备妥当。回想一下上小学时的情形吧,开学前父母都会收到所需学习用品清单:笔记本、剪刀、美术纸、胶水、2号铅笔等。当然,阅读本书不需要这些东西,但要学习Swift,必须有合适的专业工具。

首先,强烈建议你以交互方式运行本书列出的代码。为此,需要一台运行OS X 10.9 Mavericks或10.10 Yosemite的Macintosh计算机;还需要Xcode 6,它提供了Swift编译器和配套环境。最重要的是,你需要加入苹果开发者计划,这样才能充分利用Yosemite和Xcode 6。如果你还未加入苹果开发者计划,可访问https://developer.apple.com/programs,其中提供了有关如何加入该计划的完整信息。

将Xcode 6下载并安装到Mac计算机后,便可以开始学习Swift了。

1.2.2 与Swift交互

首先,我们将通过一个有趣的交互式环境——REPL,来探索Swift。REPL是Read-Eval-Print- Loop(读取-执行-输出-循环)的首字母缩写,这指出了这个工具的特征:它读取指令、执行指令、输出结果,再重新开始。

事实上,这种交互性是Swift有别于C和Objective-C等众多编译型语言的特点之一。如果你使用过Ruby或Python等提供了REPL环境的脚本语言,就知道这并非什么新东西,但对编译型语言来说,这种理念还是很新颖的。只要问问C、C++或Objective-C开发人员就知道,他们很多时候都希望能够直接运行代码,而不用创建包含调试语句的源代码文件,再编译、运行并查看结果。Swift REPL的优点在于,它让上述重复而漫长的工作流程一去不复返了。

这种交互性带来的另一大好处是,它让学习新语言这种原本艰难的任务变得容易多了。你不用再学习一系列复杂的编译工具,也无需了解集成开发环境的细微末节,只需将全部精力都放在新语言本身上。事实上,本书前半部分将探索、测试、细究Swift的方方面面,你将很快发现,以这种交互方式学习能够更快地理解Swift语言本身。

不需要运行阶段环境就能实时运行代码,一开始这可能让人感觉怪怪的,但很快你就会喜欢它提供的即时结果。事实上,REPL会让有些人想起以前的岁月:在家用计算机革命的早期,BASIC等解释型语言就提供了这种交互性。真是从终点又回到了起点。

1.3 准备出发

已下载了Xcode 6?这很好,但请暂时将它抛在脑后吧。事实上,我鼓励你去探索Xcode 6及其新特性,但接下来的几章将把注意力完全放在Terminal1中的REPL上。

1在简体中文版Mac操作系统中,被称为“终端”,故本书有时也会用终端来代指它。——编者注

如果你以前没有运行过Terminal应用程序,也不用担心。在Mac计算机中,它位于文件夹Applications/Utilities下。要运行它,最简单的方式是单击图标Spotlight,再输入Terminal,如图1-1所示。

{%}

图 1-1 使用Spotlight来查找应用程序Terminal

另一种方法是,单击Dock中的Finder图标,再选择菜单Go>Utilities2,如图1-2所示。

2在简体中文版Mac操作系统中被称为“实用工具”。——编者注

{%}

图 1-2 Finder菜单栏中的Go菜单

这将打开一个新的Finder窗口,其中显示了文件夹Utilities的内容,如图1-3所示。要找到应用程序Terminal,可能需要向下滚动。双击Terminal图标启动这个应用程序。

图 1-3 在Finder中查找Terminal

启动Terminal后,将看到类似于图1-4所示的窗口。在你的Terminal中,文本和背景色可能与这里显示的不同。

图 1-4 Terminal窗口

至此,差不多为探索Swift做好了准备,但在此之前,还需要在这个新打开的Terminal窗口中执行几个命令。

首先,输入下面的命令并按回车:

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer/

系统将提示你输入管理员密码。按要求输入即可。必须执行这个命令,它是用来确保Xcode 6是Mac计算机运行的Xcode默认版本,以防止你安装的是以前的Xcode版本。好消息是,你只需执行一次这个命令。它指定的设置将被保存,除非你要切换到其他Xcode版本,否则不用再执行这个命令。

输入下面的命令并按回车以进入Swift REPL:

xcrun swift

根据你以前使用Xcode的情况,可能出现类似于图1-5所示的对话框,要求你输入密码。如果出现该对话框,输入密码即可。

图 1-5 输入用户名和密码

很快就会出现下面的问候消息:

Welcome to Swift! Type :help for assistance.
  1>

祝贺你走到了这一步。下面开始探索之旅。

1.4 开始探索Swift

至此,你运行了Swift REPL,它位于Terminal窗口中,耐心地等待你执行命令。Swift掌握了控制权,它显示一个提示符,告诉你可以输入命令了。每次启动REPL时,提示符都为1和大于号。下面按回车键执行检查:

Welcome to Swift! Type :help for assistance.
  1>
  2>

每当你输入一行后,提示符数字都加1——非常简单。当你输入命令时,提示符中不断增大的数字提供了参考点。

1.4.1 帮助和退出

Swift内置了REPL命令帮助信息。在提示符下输入:help可列出REPL命令清单,这些命令开头都有一个冒号,Swift使用它来区分REPL命令和Swift语句。请输入:help(它本身也是一个命令),以查看命令清单。清单中的命令很多,不少都与调试相关,但大部分都不用考虑。

要退出Swift并返回到Terminal的默认shell,可随时执行命令:quit。退出REPL后,要再次进入Swift,只需在shell提示符下执行命令xcrun swift

1.4.2 Hello World

期待已久的时刻到了。每个程序员学习新语言时,都首先会编写必要的代码来向世界问好。稍后你将看到,使用Swift编写这样的代码易如反掌。

来道开胃菜:显示一句俏皮话,作为你首次与这种新语言打交道的问候语。下面用法语向世界问好。在提示符2>下输入下面的代码并按回车:

print("Bonjour, monde")

屏幕上的内容如下:

Welcome to Swift! Type :help for assistance.
  1>
  2> print("Bonjour, monde")
  3>

祝贺你编写了第一行Swift代码。必须承认,这有点小儿科,但总算开始了。这个示例还表明编写可执行的代码很容易。

这行代码很简单。print是一个Swift方法,命令计算机显示括号内用引号括起的所有内容(字符串)。方法指的是一组可通过指定名称执行的指令。在本书中,你将用到很多常见的Swift方法,其中print你将经常使用到。

此时你可能会问,我刚才不是让计算机打印了吗?你确实这样做了,Swift也乖乖地按你的指示做了。计算机从不会犯错,不是吗?Swift严格按你的指示办事。那么你的指示是什么呢?你遇到第一个bug了吗?

事实上,方法print不会立即将指定的内容显示到屏幕上,而将这些数据放在缓冲区,并等着你用另一个方法将数据显示出来。这个方法就是println,它添加一个换行符,并将所有内容都显示到屏幕上。

下面使用这个方法显示一个字符:

  3> println("!")
Bonjour, monde!
  4>

现在所有文本都显示在屏幕上,且都位于一行中。请注意我们期望出现但并未出现的初始文本,这些文本并未消失,而在等待换行符将其显示出来。

至此,你掌握了一项基本技能——知道如何让Swift显示一串文本。这微不足道,但为理解Swift开了个好头。咱们接着往下走,看看一些简单而重要的Swift结构。

1.5 声明的威力

如果回想一下中学的代数课,你肯定还记得变量是表示某种量的占位符。当你说x等于12或y等于42时,实际上是在声明,将某个变量声明为特定的数字。

Swift让代数课老师自豪,它也能够声明变量,但使用的语法稍有不同。请输入如下内容:

  4> var x = 12
x: Int = 12
  5>

你刚才使用关键字var声明了第一个变量。第4行让Swift将变量x声明为12,Swift完全按你的指示做,将变量x声明为12。不仅如此,Swift还更进一步:将x声明为值为12的Int变量。

Int是什么呢?它是integer的缩写,表示不带小数部分的整数。通过像前面那样输入12,让Swift对被赋值的变量做出了推断:x是一个值为12的整数变量。在响应中,Swift使用表示法x: Int指出了这个变量的类型。稍后将更详细地介绍这种表示法。

前面不费吹灰之力就声明了一个名为x的变量,下面将问题再弄得复杂一些,声明第2个变量:

  5> var y = 42.0
y: Double = 42
  6>

这里添加了小数点和0,这种表示法告诉Swift,y是一个Double变量。Double表示带小数部分的数字,不用于表示整数,而用于表示实数(也叫浮点数)。

Float还是Double

如果你使用过其他编程语言,可能熟悉浮点数,知道它们分两种:FloatDoubleFloat通常长32位,而Double通常长64位(精度是Float的两倍)。除Double类型外,Swift也支持Float类型。然而,鉴于现代计算机体系结构是64位的,Swift默认使用Double类型来表示浮点数,而在本书的示例中,总是使用Double类型。

下面简单地复习一下。在前面的两种情况下,Swift都给变量指定了类型。变量xy的类型分别是IntDouble,只要不重新启动REPL,这种差别将始终存在。

声明变量后,就可将不同的值赋给它。例如,前面将数字12赋给了变量x。将不同的值赋给变量很简单,只需使用等号即可:

6> x = 28
7>

注意到将数字28赋给变量x时,Swift没有任何反应。下面核实一下这个新值是否赋给了变量x

  7> println(x)
28

与我们预期的一样,x存储的是最后一次赋给它的值:28。

还可以将一个变量的值赋给另一个变量。为核实这一点,下面将变量y赋给变量x。你猜结果将如何呢?

  8> x = y
<REPL>:8:5: error: 'Double' is not convertible to 'Int'
x = y
    ^

  8>

Swift显示的错误消息非常详细,提供了错误所在的行号和列号(这里是第8行和第5列),并使用冒号分隔它们。在错误消息后面,还显示了相应代码行的内容,并用脱字符指出了错误的位置。最后,由于存在错误,在接下来显示的提示符中没有将数字增加到9,而再次使用以前的数字8。(这相当于Swift在对你说:“哥们,别紧张,再试试。”)

变量名包含什么?

在Swift中,变量名可以用除数字外的任何字符打头。前面使用的是单字母变量名,但提倡使用更长、意义更丰富的变量名,以提高代码的可读性。

这里到底出了什么问题呢?很简单,你试图将类型为Double的变量y赋给类型为Int的变量x,这种赋值违反了Swift的类型规则。稍后将更详细地介绍类型,现在来看看能否规避这种规则。

假设你一根筋,就是要将y的值赋给x,即便它们的类型不同。你完全可以达到目的,但需要做些“说服”工作。前面说过,x的类型为Int,而y的类型为Double;考虑到这一点后,可输入如下语句:

 8> x = Int(y)
 9> println(x)
42
 10>

经过一番“说服”后,赋值成功了。个中原因是什么呢?

第8行将变量yDouble值“转换”成了变量x的类型。只要进行显式转换,Swift就允许这样赋值。稍后将更详细地讨论类型转换。

保险起见,我们使用命令println显示了变量x的值。与预期的一样,现在变量x的值为整数42。

1.6 常量

在很多情况下,变量都很有用,因为它们的值可随时间而变。在循环中,变量非常适合用于存储临时数字、字符串以及本书后面将讨论的其他对象。

在Swift中,另一种可用于存储值的结构是常量。顾名思义,常量存储的值始终不变。不同于变量,常量一旦赋值就不能修改,就像被锁定一样。然而,与变量一样,常量也有类型,且类型一旦指定就不能改变。

下面来看看如何使用常量:声明常量z,并将变量x的值赋给它:

 10> let z = x
z: Int = 42
 11>

第10行使用了let命令,这是用于创建常量的Swift关键字。常量z的类型和值都与变量x相同:它是一个值为42的Int常量。

如果常量的值真是固定不变的,就不能将另一个数字或变量赋给它。下面来检验这一点:

 11> z = 4
<REPL>:11:3: error: cannot assign to 'let' value 'z'
z = 4
~ ^

 11>

试图给常量z重新赋值引发了错误。同样,Swift精准的错误报告指明了方向,它指出了错误所处的行号(11)和列号(3)。

为何Swift要同时支持变量和常量呢?考虑到变量可以修改,而常量不能,使用变量不是更灵活吗?问得好,答案要在底层编译器技术中去找。知道内存单元存储的值不会变时,Swift编译器可更好地决策和优化代码。对于不变的值,务必在代码中使用常量来存储;仅当确定值将发生变化时,才使用变量来存储。总之,常量需要的开销比变量小,这正是因为它们不变。

在你学习Swift开发的过程中,将在确定值不变的情况下越来越多地使用常量。事实上,苹果鼓励在代码中使用常量,不管这样做出于什么考虑。

1.7 类型

在本章前面,Swift自动推断出了变量的类型,你注意到了吗?你不用输入额外的代码去告知Swift变量的类型究竟为Int还是Double,Swift自会根据等号右边的值推断出变量或常量的类型。

计算机语言使用类型将值和存储它们的容器分类。类型明确地指出了值、变量或常量的特征,让代码的意图更清晰,消除了二义性。类型犹如不可更改的契约,将变量或常量与其值紧密关联在一起。Swift是一种类型意识极强的语言,这一点在本章前面的一些示例中已经体现出来了。

表1-1列出了Swift基本类型。还有其他一些类型没有列出。另外你将在本书后面看到,可创建自定义类型,但目前我们只使用这些类型。

表1-1 变量类型

类型

特征

示例

Bool

只有两个可能取值的类型,要么为true要么为false

true、false

IntInt32Int64

32或64位的整数值,用于表示较大的数字,不包含小数部分

3、117、-502001、10045

Int8Int16

8或16位的整数,用于表示较小的数字,不包含小数部分

-11、83、122

UIntUInt32UInt64

32或64位的正整数,用于表示较大的数字,不包含小数部分

3、117、50、10045

UInt8UInt16

8或16位的正整数,用于表示较小的数字,不包含小数部分

44、86、255

FloatDouble

可正可负的浮点数,可能包含小数部分

324.147、-2098.8388、16.0

Character

用双引号括起的单个字符、数字或其他符号

“A”、“!”、“*”、“5”

String

用双引号括起的一系列字符

“Jambalaya”、“Crawfish Pie”、“Filet Gumbo”

前面介绍过Int,但未介绍Int8Int32Int64UIntUInt8UInt32UInt64也未介绍。可正可负的整数被称为有符号整数,其表示类型包括8、16、32和64位;只能为正的整数被称为无符号整数,也有8、16、32和64位版本。如果没有指定32或64位,IntUInt默认为64位。事实上,在开发工作中很少需要考虑类型的长度。就现在而言,请不要考虑这些细节。

1.7.1 检查上限和下限

表11-1列出的每种数值类型都有上限和下限,即每种类型可存储的数字都不能小于下限,也不能大于上限,这是因为用于表示数值类型的位数是有限的。Swift让你能够查看每种类型可存储的最大值和最小值:

 11> println(Int.min)
-9223372036854775808
 12> println(Int.max)
9223372036854775807
 13> println(UInt.min)
0
 14> println(UInt.max)
18446744073709551615
 15>

在类型名后面加上.min.max,即可获悉相应类型可存储的上下限值。第11~14行显示了类型IntUInt的取值范围。你也可以自己检查表1-1列出的其他类型的可能取值范围。

1.7.2 类型转换

鉴于类型是值、常量和变量的固有特征,你可能想知道不同类型交互式需要遵循的规则。还记得吗,在本书前面的一个示例中,你尝试将一种类型的变量赋给另一种类型的变量。第一次尝试这样做时引发了错误;经过“说服”后,才让Swift同意将一个Double型变量赋给一个Int型变量。下面来重温这个示例(不用重新输入代码,只需在Terminal中向上滚动到能够看到前面输入的代码即可):

  4> var x = 12
x: Int = 12
  5> var y = 42.0
y: Double = 42

这些代码分别将IntDouble值赋给变量xy,然后试图将y的值赋给x

  8> x = y
<REPL>:8:5: error: 'Double' is not convertible to 'Int'
x = y
    ^


  8> x = Int(y)
  9> println(x)
42

第4行声明了变量x并将数字12赋给它,这使其类型为Int。接下来,将y声明为Double变量。然后,将y赋给x时引发了错误,这迫使我们将y的值转换为Int,如第8行所示。这个过程称为强制转换(casting),即强制将值从一种类型转换为另一种类型。在计算机语言中,这种功能被称为类型转换。每种语言都有其类型转换规则,Swift当然也不例外。

一种常见规则是,类型转换只能在相似的类型之间进行。在C等流行的计算机语言中,可在整数和双精度浮点数之间转换,因为它们都是数值类型。但强行将整数转换为字符串属于非法类型转换,因为它们是截然不同的类型。在这方面,Swift更灵活些。请尝试下面的操作:

 15> var t = 123
t: Int = 123
 16> var s = String(t)
s: String = "123"
 17>

这里声明了变量t,并将一个Int值赋给它。接下来,声明了另一个变量s,将Int变量t的值强制转换为String类型,并将结果赋给变量s

能将String类型强行转换为Int乃至Double吗?

 17> var u = Int(s)
<REPL>:17:9: error: cannot invoke 'init' with an argument of type → '@lvalue String'
var u = Int(s)
        ^~~~~~

 17> var v = Double(s)
<REPL>:17:9: error: cannot invoke 'init' with an argument of type → '@lvalue String'
var v = Double(s)
        ^~~~~~~~~

在这方面,Swift划出了明确的界线。虽然可以将数值类型转换为String类型,但反过来不行。不过不用着急,完全可以将String的内容转换为IntDouble,但不是使用类型转换。

 17> var myConvertedInt = s.toInt()
myConvertedInt: Int? = 123
 18>

String类型有一个特殊方法,可用于将其内容转换为Int类型——toInt()。这个方法对字符串的内容进行评估,如果它包含的字符可组成有效的整数,就返回其整数表示。第17行声明了变量myConvertedInt,并将一个Int值赋给它。

你可能感到迷惑,响应第17行时Swift显示了Int?,这其中的问号到底是什么意思呢?这表明myConvertedInt是特殊的Int类型:可选Int类型。可选类型将在后面更详细地介绍,你现在只需知道它们让变量可以为特殊值nil

1.7.3 显式地声明类型

让Swift推断变量或常量的类型很方便:不用告诉Swift变量的类型是整型还是浮点型,它根据赋给变量的值就能推断出来。然后,有时需要显式地声明变量或常量的类型,Swift允许你在声明中指出这一点:

 18> var myNewNumber : Double = 3
myNewNumber: Double = 3
 19>

将变量或常量声明为特定类型很简单,只需在变量或常量的名称后面加上冒号和类型名。上面的代码将myNewNumber声明为Double变量,并将数字3赋给它,而Swift忠实地报告了声明结果。

如果在第18行省略: Double,结果将如何呢?Swift将根据赋给变量myNewNumber的值确定其类型为Int。在这个示例中,我们推翻了Swift的假设,强制将变量声明为所需的类型。

如果没有给变量或常量赋值,结果将如何呢?

 19> var m : Int
:19:5 error: variables currently must have an initial value when entered at the → top level of the REPL
 20> let rr : Int
<REPL>:20:5: error: 'let' declarations require an initializer expression
let rr : Int
    ^

第19行将变量m声明为Int类型,但没有在声明的同时给它赋值。Swift显示一条错误消息,指出在REPL中必须给变量赋初值。

接下来的一行使用let命令将rr声明为Int常量,但没有赋值。注意到Swift也显示了一条错误消息,指出必须有初始化表达式。由于常量是不变的,声明时必须给它们赋值。

1.8 字符串

前面简要地介绍了数值类型,但还有一种Swift类型也用得非常多,它就是String类型。前面说过,在Swift中,字符串是用双引号("")括起的一系列字符。

下面是合法的字符串声明:

 20> let myState = "Louisiana"
myState: String = "Louisiana"
 21>

下面的字符串声明亦如此:

 21> let myParish : String = "St. Landry"
myParish: String = "St. Landry"
 22>

这些示例分别演示了类型推断和显式声明类型。在第一个示例中,Swift根据赋给变量的值确定其类型;在第二个示例中,显式地指定了变量的类型。这两种做法都可行。

1.8.1 字符串拼接

可使用加号(+)运算符将多个字符串连接,或者说拼接起来,组成更大的字符串。下面声明了多个常量,再将它们拼接起来,生成一个更长的常量字符串:

 22> let noun = "Wayne"
noun: String = "Wayne"
 23> let verb = "drives"
verb: String = "drives"
 24> let preposition = "to Cal's gym"
preposition: String = "to the gym"
 25> let sentence = noun + " " + verb + " " + preposition + "."
sentence: String = "Wayne drives to Cal's gym."
 26>

第25行将6个字符串拼接在一起,再将结果赋给常量sentence

1.8.2 Character类型

前面介绍了三种类型:Int(用于存储整数)、Double(用于存储带小数的数字)和String(用于存储一系列字符)。在Swift中,你必将用到的另一种类型是Character,它实际上是特殊的String。类型为Character的变量和常量包含单个用双引号括起的字符。

下面就来试一试:

 26> let myFavoriteLetter = "A"
myFavoriteLetter: String = "A"
 27>

你可能抓破了头皮也想不明白,Swift为何说变量myFavoriteLetter的类型为String?如果没有显式地指定类型Character,Swift默认将用双引号括起的单个字符视为String类型。Character是Swift无法推断的类型之一,下面来纠正上述错误:

 27> let myFavoriteLetter : Character = "A"
myFavoriteLetter: Character = "A"
 28>

现在结果与期望一致了!

既然字符串是由一个或多个字符组成的,那么应该能够使用字符来创建字符串。确实如此,为此可使用前面用于拼接字符串的加号(+)运算符,但需要注意的是,必须先将字符强制转换为String类型:

 28> let myFavoriteLetters = String(myFavoriteLetter) + String(myFavoriteLetter)
myFavoriteLetters: String = "AA"
 29>

如果你以前使用过对字符串拼接支持不强的C或Objective-C语言,将感觉到Swift字符串拼接非常简单。要拼接字符,在C语言中必须使用函数strcat(),而在Objective-C中必须使用FoundationNSString的方法stringWithFormat:,而在Swift中只需使用加号运算符就能拼接字符和字符串,因此需要输入的代码少得多。这充分说明了Swift的简洁和优美:拼接字符串就像将两个数字相加一样。说到将数字相加,下面来看看在Swift中如何执行简单的数学运算。

1.9 数学运算符

Swift很擅长做数学运算。前面介绍过String类型可使用加号来拼接字符串,但加号并非只能用于拼接字符串,它还是加法运算的通用表示方式,而现在正是探索Swift数学运算功能的好时机。来看一些执行算术运算的数学表达式:

 29> let addition = 2 + 2
addition: Int = 4
 30> let subtraction = 4 - 3
subtraction: Int = 1
 31> let multiplication = 10 * 5
multiplication: Int = 50
 32> let division = 24 / 6
division: Int = 4
 33>

这里演示了四种基本运算:加(+)、减(-)、乘(*)、除(/)。Swift提供的结果符合预期,它给常量指定的类型(Int)也符合预期。同样,Swift根据等号右边的值推断出这些常量的类型为Int

还可使用%运算符来执行求模运算,它返回除法运算的余数:

 33> let modulo = 23 % 4
modulo: Int = 3
 34>

在Swift中,甚至可将求模运算符用于Double值:

 34> let modulo = 23.5 % 4.3
modulo: Double = 2.0000000000000009
 35>

另外,加号和减号还可用作单目运算符。在值前面加上加号意味着正数,加上减号意味着负数:

 35> var positiveNumber : Int = +33
positiveNumber: Int = 33
 36> var negativeNumber : Int = -33
negativeNumber: Int = -33
 37>

1.9.1 表达式

Swift全面支持数学表达式,包括标准的运算符优先级(按从左到右的顺序先执行乘法和除法运算,再执行加法和减法运算):

 37> let r = 3 + 5 * 9
r: Int = 48
 38> let g = (3 + 5) * 9
g: Int = 72
 39>

第37行先将5乘以9,再将结果加上3,而第38行将前两个值用括号括起来,因此先将这两个值相加,再将结果与9相乘。Swift与其他现代语言一样按规范顺序执行数学运算。

1.9.2 混用不同的数值类型

如何混用小数和整数,结果如何呢?

 39> let anotherDivision = 48 / 5.0
anotherDivision: Double = 9.5999999999999996
 40>

这里将整数48除以小数5.0。小数点提供了足够的线索,让Swift将相应数字的类型视为Double。结果常量anotherDivision的类型也被指定为Double。这里演示了Swift的类型提升概念:将Int值48与一个Double值放在同一个表达式中时,它被提升为Double类型。同样,常量也被指定为Double类型。这种规则必须牢记。

在同一个表达式中包含不同类型的数值时,总是将表达力较弱的类型提升为表达力较强的类型。由于Double类型可表示Int值,而Int类型无法表示Double值,因此将Int值提升为Double值。

1.9.3 数值表示

在Swift中,可以多种方式表示数值。本章前面使用的都是最常见、最自然的表示方式:十进制,即以10为底的计数法。下面来看看其他表示数值的方式。

1. 二进制、八进制和十六进制

如果你有编程经验,肯定遇到过以2、16甚至8为底的数字,它们分别被称为二进制、十六进制和八进制。这些进位制在软件开发中经常会出现,根据它们本身的特性使用简捷记法很有帮助:

 40> let binaryNumber = 0b110011
binaryNumber: Int = 51
 41> let octalNumber = 0o12
octalNumber: Int = 10
 42> let hexadecimalNumber = 0x32
hexadecimalNumber: Int = 50
 43>

二进制数用前缀0b表示,八进制数字用0o表示,而十六进制数用0x表示。当然,没有前缀意味着为十进制数。

2. 科学计数法

另一种表示数字的方法是科学计数法,这种计数法可简化大型小数的表示:

 43> let scientificNotation = 4.434e-10
scientificNotation: Double = 0.00000000044339999999999999
 44>

其中e表示以10为底的指数,这里为4.434×10-10

3. 大数字表示法

如果你曾坐在Mac计算机前数数字末尾有多少个0,以确定其量级,肯定会喜欢下面这种特性。Swift支持下面这种方式表示大数,让其量级一目了然:

 44>  let fiveMillion = 5_000_000
fiveMillion: Int = 5000000
 45>

下划线会被Swift忽略,但这些下划线对提高数字的可读性大有裨益。

1.10 布尔类型

Swift支持的另一种类型是Bool,即布尔类型。布尔类型的取值要么为true要么为false,通常在比较表达式中使用它们来回答类似于下面的问题:12是否大于3,或55是否等于12?在软件开发中,从结束对象列表迭代到确定一组条件语句的执行路径,经常会用到这样的逻辑比较:

 45> 100 > 50
$R0: Bool = true
 46> 1.1 >= 0.3
$R1: Bool = true
 47> 66.22 < 7
$R2: Bool = false
 48> 44 <= 1
$R3: Bool = false
 49> 5.4 == 9.3
$R4: Bool = false
 50> 6 != 7
$R5: Bool = true
 51>

这里使用了如下比较:大于、大于等于、小于、小于等于、等于、不等于。根据比较结果,返回布尔值true或false。这里比较了Int字面量和Double字面量,旨在说明这两种数值类型都是可以比较的,甚至可以对Double值和Int值进行比较。

结果

注意到这里没有使用关键字letvar将布尔表达式的结果赋给常量或变量;另外,这些条件表达式的结果各不相同,如第48行的结果所示:

$R3: Bool = false

其中的$R3是什么呢?在Swift REPL中,这被称为临时变量,它存储了结果的值,这里为false。可像声明过的变量一样引用临时变量:

 51> println($R3)
false
 52>

还可以给这些临时变量赋值,就像它们是声明过的变量一样。

如何比较字符串?

如果能够使用前述比较运算符来检查字符串是否相等,那就太好了。如果你使用过C或Objective-C,就知道检查两个字符串是否相等很麻烦。 在C语言中,需要像下面这样做:

int result = strcmp("this string", "that string")

在Objective-C中,需要像下面这样做:

NSComparisonResult result = [@"this string" compare:@ "that string"];

在Swift中,编写比较字符串的代码易如反掌,这些代码也很容易理解:

 52> "this string" == "that string"
$R6: Bool = false
 53> "b" > "a"
$R7: Bool = true
 54> "this string" == "this string"
$R8: Bool = true
 55> "that string" <= "a string"
$R9: Bool = false
 56>

结果说明了一切:Swift比较字符串的方式更自然、更具表达力。

1.11 轻松显示

前面在REPL中显示字符串时,使用的都是printprintln方法。下面重温这些方法,看看如何使用它们来显示更复杂的字符串。

方法printprintln提供的便利之一是,不费吹灰之力就能将变量的值嵌入到其他文本中。如果你熟悉C或Objective-C,就知道设置文本输出格式需要输入的代码非常多,最典型的例子是C语言中的方法printf和Objective-C中的方法NSLog()。请看下面的Objective-C代码片段:

NSString *myFavoriteCity = "New Orleans";
NSString *myFavoriteFood = "Seafood Gumbo";
NSString *myFavoriteRestaurant = "Mulates";
NSInteger yearsSinceVisit = 3;
NSLog(@"When I visited %@ %d years ago, I went to %@ and ordered %@.", myFavoriteCity, yearsSinceVisit, myFavoriteRestaurant, myFavoriteFood);

如果你能看懂这段代码,就知道它很糟糕,其中的原因有多个。首先,变量的位置与其值将显示的位置不同,这要求你以正确的顺序指定变量,否则结果将不符合预期。其次,设置两种类型不同的变量的格式时,需要使用不同格式设置代码:对于NSString变量,需要使用%@;对于NSInteger变量,需要使用%d(如果你不熟悉格式设置代码,也不用担心,因为Swift不使用它们)。

在Swift中,无需使用格式设置代码,也无需考虑格式设置代码和变量的顺序。相反,只需将变量放在要显示的位置,它们就会与其他文本一起显示出来。下面是上述Objective-C代码的Swift版本:

 56> let myFavoriteCity = "New Orleans"
myFavoriteCity: String = "New Orleans"
 57> let myFavoriteFood = "Seafood Gumbo"
myFavoriteFood: String = "Seafood Gumbo"
 58> let myFavoriteRestaurant = "Mulates"
myFavoriteRestaurant: String = "Mulates"
 59> let yearsSinceVisit = 3
yearsSinceVisit: Int = 3
 60> println("When I visited \(myFavoriteCity) \(yearsSinceVisit) years ago, I went to \(myFavoriteRestaurant) and ordered \(myFavoriteFood).")
When I visited New Orleans 3 years ago, I went to Mulates and ordered Seafood Gumbo.
 61>

60行中用于显示变量的标记非常简单,其中使用了嵌入表示法\()来引用第56~59行声明的四个常量。这种表示法非常简洁,如果将其与前述语言的处理方式进行比较,这一点尤其明显。

同样,将合并得到的字符串赋给变量与显示它一样简单:

61> let sentence = "When I visited \(myFavoriteCity) \(yearsSinceVisit) years ago, I went to \(myFavoriteRestaurant) and ordered \(myFavoriteFood)."
sentence: String = "When I visited New Orleans 3 years ago, I went to Mulates and ordered Seafood Gumbo."
62>

1.12 使用类型别名

本章前面介绍过类型,它们是Swift对变量和常量进行分类的核心。作为一种不可变的属性,类型是程序中每个数字和字符串的有机组成部分。然而,为改善源代码的可读性,有时需要使用类型别名。

类型别名是一种让Swift给类型提供其他名称的简单方式:

 62> typealias EightBits = UInt8
 63> var reg : EightBits = 0
reg: EightBits = 0
 64>

这里给Swift类型UInt8指定了别名EightBits,并在接下来的声明中使用了这个别名。甚至可以给类型别名指定别名:

 64> typealias NewBits = EightBits
 65> var reg2 : NewBits = 0
reg2: NewBits = 0
 66>

当然,NewBitsEightBits其实都是UInt8。指定类型别名并没有创建新类型,但代码的可读性更高了。虽然类型别名是一种改善代码的极佳方式,但必须慎用并提供完善的文档,在需要与其他开发人员共享代码时这尤其重要。还有什么比见到一种新类型却不知道它表示的是什么更让人困惑呢?

1.13 使用元组将数据编组

有时候,将不同的数据元素组合成更大的类型很有用。前面使用的都是单项数据:整数、字符串等。这些基本类型是Swift数据存储和操作功能的基础,但可以用有趣的方式组合它们,你将在本书中经常看到这种情况。

这里探索其中一种组合方式——元组(Tuple)。元组是由一个或多个变量、常量或字面量组成的单个实体,由放在括号内用逗号分隔的列表表示,比如像下面这样:

 66> let myDreamCar = (2014, "Mercedes-Benz", "M-Class")
myDreamCar: (Int, String, String) = {
 0 = 2014
 1 = "Mercedes-Benz"
 2 = "M-Class"
}
 67>

这里将常量myDreamCar定义成了包含三个元素的元组:一个Int字面量和两个String字面量。注意到Swift推断出了元组的每个成员的类型,就像你显式地指定了类型一样。另外,元组成员的顺序与定义时的顺序相同。

定义元组后,可对其做什么呢?显然,可以查看它。要查看元组的内容,可使用句点和索引,其中索引是从0开始的,如下所示:

 67> println(myDreamCar.0)
2006
 68> println(myDreamCar.1)
Ford
 69> println(myDreamCar.2)
Mustang
 70> println(myDreamCar)
(2006, Ford, Mustang)
 71>

如果你试图访问不存在的元组成员,Swift将显示错误消息:

 71> println(myDreamCar.3)
<REPL>:71:9: error: '(Int, String, String)' does not have a member named '3'
println(myDreamCar.3)
        ^         ~

71>

本书后面将使用元组,正如你将看到的,在很多情况下使用元组非常方便。

1.14 可选类型

你可能还记得,本章前面对String变量使用了方法toInt()来将其内容转换为Int值,以便将结果赋给另一个变量:

 17> var myConvertedInt = s.toInt()
myConvertedInt: Int? = 123
 18>

在Swift显示的类型说明中,有一个问号。这个问号表明变量myConvertedInt的类型不是Int,而是可选Int类型。

可选是什么意思呢?它实际上是一个类型修饰符,告诉Swift指定的变量或常量可以为空。空值很久前就出现在了编程语言中;在Objective-C中用nil表示,而在C/C++中用NULL表示。nilNULL的含义完全相同,都表示空值。

来看看在另一种情形下(s不为"123",而是"abc")上述代码的运行结果:

 71> let s = "abc"
s: String = "abc"
 72> var myConvertedInt = s.toInt()
myConvertedInt: Int? = nil
 73>

注意到myConvertedInt的类型依然是Int?(可选Int),但其值不再是123,而是nil。这是对的,因为无法将字母abc转换为Int。Swift通过返回nil来指出转换失败,而可选类型提供了另一条成功地给变量赋值的路径。在这里,String类的方法toInt()返回nil,指出我无法将这个字符串转换为数字。

将变量声明为可选类型很简单,只需在声明时在类型名后面加上一个问号:

 73> var v : Int?
v: Int? = nil
 74>

Swift的应答表明,变量v的类型确实是可选Int。由于声明时没有赋值,因此默认值不是0,而是nil

下面尝试给这个变量赋值:

74> v = 3
75>

最后,显示这个变量的值:不使用方法println,而只是输入变量名。Swift将把它的值赋给一个临时变量:

 75> v
$R10: Int? = 3
 76>

正如你看到的,Swift指出这个变量的值确实是3。

并非只有Int类型可以是可选的。事实上,任何类型都可声明为可选的。下面的示例声明了两个可选变量,它们的类型分别为StringCharacter

 76> var s : String? = "Valid text"
s: String? = "Valid text"
 77> var u : Character? = "a"
u: Character? = "a"
 78> u = nil
 79>

第78行将变量u的值设置成了nil,旨在表明任何被声明为可选的变量都可设置为nil

本书后面将更详细地探讨可选类型,就目前而言,你只需能够识别可选变量就够了。

1.15 小结

祝贺你学完了第1章。本章简要地介绍了Swift,其中有大量的知识需要消化,如果必要请回过头去复习。

本章介绍了如下主题:

  • 变量

  • 常量

  • 方法print

  • 类型(IntDoubleCharacterString等)

  • 数学运算符

  • 数值表示法(二进制、十六进制、科学计数法等)

  • 字符串拼接

  • 类型推断及显式声明类型

  • 类型别名

  • 元组

  • 可选类型

忘了,要从事Swift编程工作,必须掌握这些基本概念。请务必熟悉并搞懂它们,因为本书后面讨论Swift的其他特性时,需要用到本章介绍的知识。

目录