第1章 Swift简介
欢迎来到Swift这个美丽的新世界。如果你一直关注新闻,可能听说过Swift是苹果公司打造的一款全新编程语言,目的是让开发人员能够更轻松、更高效地编写iOS和Mac应用。Swift简单易学,你在不知不觉间就能编写出简单应用。
Swift提供了一些编写代码的新方式,比功能强大而著名的前身Objective-C容易理解得多。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。如果你还未加入苹果开发者计划,可访问相关网站获取完整信息。
将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及其新特性,但接下来的几章将把注意力完全放在Terminal中的REPL上(在简体中文版Mac操作系统中,Terminal被称为“终端”)。
如果你以前没有运行过Terminal应用程序,也不用担心。在Mac计算机中,它位于文件夹Applications/Utilities下。要运行它,最简单的方式是单击图标Spotlight,再输入Terminal。另一种方法是,单击Dock中的Finder图标,再选择菜单Go>Utilities(在简体中文版中为“实用工具”)。这将打开一个新的Finder窗口,其中显示了文件夹Utilities的内容,要找到应用程序Terminal,可能需要向下滚动。双击Terminal图标启动这个应用程序。
启动Terminal后,将看到一个命令行窗口。至此,差不多为探索Swift做好了准备,但在此之前,还需要在这个新打开的Terminal窗口中执行几个命令。
首先,输入下面的命令并按回车:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer/
系统将提示你输入管理员密码。按要求输入即可。必须执行这个命令,它是用来确保Xcode 6是Mac计算机运行的Xcode默认版本,以防止你安装的是以前的Xcode版本。好消息是,你只需执行一次这个命令。它指定的设置将被保存,除非你要切换到其他Xcode版本,否则不用再执行这个命令。
输入下面的命令并按回车以进入Swift REPL:
xcrun swift
根据你以前使用Xcode的情况,可能出现一个对话框要求你输入密码。如果出现该对话框,输入密码即可。很快就会出现下面的问候消息:
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?
如果你使用过其他编程语言,可能熟悉浮点数,知道它们分两种:Float和Double。Float通常长32位,而Double通常长64位(精度是Float的两倍)。除Double类型外,Swift也支持Float类型。然而,鉴于现代计算机体系结构是64位的,Swift默认使用Double类型来表示浮点数,而在本书的示例中,总是使用Double类型。
下面简单地复习一下。在前面的两种情况下,Swift都给变量指定了类型。变量 x 和 y 的类型分别是 Int 和 Double,只要不重新启动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行将变量 y 的 Double 值“转换”成了变量 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 |
Int、 Int32、 Int64 |
32或64位的整数值,用于表示较大的数字,不包含小数部分 | 3、117、-502001、10045 |
Int8、 Int16 |
8或16位的整数,用于表示较小的数字,不包含小数部分 | -11、83、122 |
UInt、 UInt32、 UInt64 |
32或64位的正整数,用于表示较大的数字,不包含小数部分 | 3、117、50、10045 |
UInt8、 UInt16 |
8或16位的正整数,用于表示较小的数字,不包含小数部分 | 44、86、255 |
Float、 Double |
可正可负的浮点数,可能包含小数部分 | 324.147、-2098.8388、16.0 |
Character |
用双引号括起的单个字符、数字或其他符号 | “A”、“!”、“*”、“5” |
String |
用双引号括起的一系列字符 | “Jambalaya”、“Crawfish Pie”、“Filet Gumbo” |
前面介绍过Int,但未介绍 Int8、 Int32 和 Int64, UInt、 UInt8、 UInt32 和 UInt64 也未介绍。可正可负的整数被称为有符号整数,其表示类型包括8、16、32和64位;只能为正的整数被称为无符号整数,也有8、16、32和64位版本。如果没有指定32或64位, Int 和 UInt 默认为64位。事实上,在开发工作中很少需要考虑类型的长度。就现在而言,请不要考虑这些细节。
1.7.1 检查上限和下限
表1-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行显示了类型 Int 和 UInt 的取值范围。你也可以自己检查表1-1列出的其他类型的可能取值范围。
1.7.2 类型转换
鉴于类型是值、常量和变量的固有特征,你可能想知道不同类型交互式需要遵循的规则。还记得吗,在本书前面的一个示例中,你尝试将一种类型的变量赋给另一种类型的变量。第一次尝试这样做时引发了错误;经过“说服”后,才让Swift同意将一个 Double 型变量赋给一个 Int 型变量。下面来重温这个示例(不用重新输入代码,只需在Terminal中向上滚动到能够看到前面输入的代码即可):
4> var x = 12
x: Int = 12
5> var y = 42.0
y: Double = 42
这些代码分别将 Int 和 Double 值赋给变量 x 和 y,然后试图将 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 的内容转换为 Int 或 Double,但不是使用类型转换。
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中必须使用 Foundation 类 NSString 的方法 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 值进行比较。
结果
注意到这里没有使用关键字 let 或 var 将布尔表达式的结果赋给常量或变量;另外,这些条件表达式的结果各不相同,如第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中显示字符串时,使用的都是 print 和 println 方法。下面重温这些方法,看看如何使用它们来显示更复杂的字符串。
方法 print 和 println 提供的便利之一是,不费吹灰之力就能将变量的值嵌入到其他文本中。如果你熟悉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>
当然, NewBits 和 EightBits 其实都是 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 表示。 nil 和 NULL 的含义完全相同,都表示空值。
来看看在另一种情形下( 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 类型可以是可选的。事实上,任何类型都可声明为可选的。下面的示例声明了两个可选变量,它们的类型分别为 String 和 Character:
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 - 类型(
Int、Double、Character、String等) - 数学运算符
- 数值表示法(二进制、十六进制、科学计数法等)
- 字符串拼接
- 类型推断及显式声明类型
- 类型别名
- 元组
- 可选类型
要从事Swift编程工作,必须掌握这些基本概念。请务必熟悉并搞懂它们,因为本书后面讨论Swift的其他特性时,需要用到本章介绍的知识。
上一节我们讨论了元组的访问方式。如果你尝试使用数字下标(如 .3)去访问一个具有命名元素的元组成员,Swift 编译器会报错,提示该元组没有名为 '3' 的成员。正确的做法是使用你为元素定义的名称进行访问。
元组在多种场景下都非常有用。接下来,我们将探讨Swift中另一个重要的概念:可选类型。
1.14 可选类型
回顾之前的内容,我们曾使用 String 的 toInt() 方法,尝试将其内容转换为 Int 值:
var myConvertedInt = s.toInt() // 假设 s 为 "123"
Swift 会显示 myConvertedInt 的类型为 Int?。这里的问号表明该变量的类型是 可选 Int。
那么,可选类型究竟意味着什么?它是一个类型修饰符,告诉 Swift 这个变量或常量可以包含一个有效值,也可以不包含任何值(即空值)。空值在编程中很常见,在 Objective-C 中用 nil 表示,在 C/C++ 中则用 NULL 表示。
为了理解可选类型的必要性,让我们看一个转换失败的例子。如果字符串 s 的内容是 "abc" 而非 "123":
let s = "abc"
var myConvertedInt = s.toInt()
此时,myConvertedInt 的类型依然是 Int?,但其值变成了 nil。这正确地表明了转换失败,因为无法将 “abc” 解析为一个整数。toInt() 方法通过返回 nil 来报告失败,而可选类型为变量提供了一种安全地表示这种“值缺失”状态的方式。
声明一个可选类型的变量很简单,只需在类型名称后加上问号:
var v: Int? // 此时 v 的默认值为 nil,而不是 0
我们可以像普通变量一样为其赋值:
v = 3
之后,这个变量的值就是 3。重要的是,任何类型都可以被声明为可选的,例如 String? 或 Character?,并且它们都可以被设置为 nil。
var optionalString: String? = "Valid text"
var optionalChar: Character? = "a"
optionalChar = nil // 这是完全合法的
可选类型是Swift安全特性的核心之一,它强制程序员显式地处理值可能缺失的情况。在后续章节中,我们将更深入地探索如何安全地解包和使用可选值。目前,你只需要能够识别并理解可选类型的基本概念即可。
1.15 小结
恭喜你完成第一章的学习。本章快速介绍了 Swift 的基础知识,内容相当密集。如果有任何不清晰的地方,建议你随时回顾复习。
本章涵盖的核心主题包括:
- 变量与常量:如何声明和使用它们。
- 基础类型:如
Int、Double、Character、String等。 - 输出:使用
print函数。 - 运算符:基本的数学运算符。
- 数值表示法:包括二进制、十六进制和科学计数法。
- 字符串操作:例如字符串拼接。
- 类型推断与显式声明:Swift如何推断类型,以及你如何明确指定类型。
- 类型别名:使用
typealias为现有类型创建别名。 - 元组:将多个值组合成一个复合值。
- 可选类型:理解值可能缺失的概念及其表示方法。
牢固掌握这些基础知识至关重要,因为它们是构建更复杂Swift程序的基石。在后续章节中,我们会不断运用这些概念。