第3章 新语言,新特性——Swift的基础语法
Swift是基于iOS 和OS X 应用开发的一门新语言。然而,如果读者有 C 或者 Objective-C 开发经验,会发现 Swift 的很多内容与C或者Objective-C相似。例如,Int是整型,Double和Float是浮点型,Bool是布尔型。另外,Swift也使用变量来进行存储,并通过变量名来关联值。在本节中,将详细讲解Swift语言的基础语法知识,为读者步入本书后面知识的学习打下基础。
3.1 分号
与其他编程语言不同,Swift 语言并不强制要求在每条语句的结尾处使用分号“;”。当然,开发人员可以按照自己的习惯添加分号。但在Swift程序中的同一行内写多条独立的语句时,必须要用分号进行分隔,例如,如下所示的演示代码。
let cat = "aaa"; println(cat) // 输出 ""
Swift语言比较特殊,不需要一个main函数作为入口。例如,通过如下代码即可实现简单的赋值处理。
var a=1 a=2 let b=1
3.2 空白
在Swift语言中,空白(whitespace)有如下所示的两个用途。
□ 分隔源文件中的标记。
□ 区分运算符属于前缀还是后缀。
在Swift语言中,除了上述两个用途外,在其他情况下空白都会被忽略。例如,以下的字符会被当作空白。
□ 空格(space)(U+0020)。
□ 换行符(line feed)(U+000A)。
□ 回车符(carriage return)(U+000D)。
□ 水平 tab(horizontal tab)(U+0009)。
□ 垂直 tab(vertical tab)(U+000B)。
□ 换页符(form feed)(U+000C)。
□ 空(null)(U+0000)。
3.3 标识符和关键字
在Swift程序中,标识符和关键字是的最基本构成元素之一。在本节的内容中,将详细讲解Swift标识符和关键字的基本知识。
3.3.1 标识符
所谓标识符,是指为变量、函数和类以及其他对象所起的名称。但是这些名称不能随意命名,因为在Swift系统中已经预定义了很多标识符,这些预定义的标识符不能被用来定义其他意义。
在Swift语言中,标识符(Identifiers)可以由以下的字符开始。
□ 大写或小写的字母A到Z。
□ 下划线“_”、基本多语言面(Basic Multilingual Plane)中的 Unicode 非组合字符。
□ 基本多语言面以外的非专用区(Private Use Area)字符。
在上述首字符之后,标识符允许使用数字和Unicode字符组合。
在Swift语言中,如果想使用保留字(reserved word)作为标识符,需要在其前后增加反引号“`”。例如,虽然class不是合法的标识符,但是可以使用'class'来实现。反引号不属于标识符的一部分,'x'和x表示同一标识符。
注意
有关基本多语言面(Basic Multilingual Plane)的知识,请读者参阅维基百科中的“Unicode字符平面映射”。另外,在Swift的闭包(closure)中,如果没有明确指定参数名称,参数将被隐式命名为$0、$1、$2...这些命名在闭包作用域内是合法的标识符。
在Swift语言中,标识符需要遵循如下所示的命名规则。
(1)所有标识符必须由一个字母“a~z”、“A~Z”或下划线“_”开头。
(2)标识符的其他部分可以用字母、下划线或数字“0~9”组成。
(3)大小写字母表示不同意义,即代表不同的标识符,如前面的cout和Cout。
(4)在定义标识符时,虽然语法上允许用下划线开头,但是,我们最好避免定义用下划线开头的标识符,因为编译器常常定义一些下划线开头的标识符。
(5)Swift没有限制一个标识符中字符的个数,但是大多数的编译器都会有限制。在定义标识符时,通常无需担心标识符中字符数会不会超过编译器的限制,因为编译器限制的数字很大,如255。
(6)标识符应当直观且可以拼读,可以望文知意。标识符最好采用英文单词或其组合,便于记忆和阅读,切忌使用汉语拼音来命名。程序中的英文单词一般不会太复杂,用词应当准确。例如不要把CurrentValue写成NowValue。
(7)命名规则尽量与所采用的操作系统或开发工具的风格保持一致。例如 Windows 应用程序的标识符通常采用“大小写”混排的方式,如AddChild。而Unix应用程序的标识符通常采用“小写加下划线”的方式,如add_child。别把这两类风格混在一起用。
(8)程序中不要出现仅靠大小写区分的相似的标识符,例如:
int x, X;
(9)程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但是这样会使人产生误解。
3.3.2 关键字
在Swift程序中,不允许将被保留的关键字(keywords)作为标识符,除非被反引号转义。在Swift语言中,常用的关键字如下所示。
□ 用作声明的关键字:class、deinit、enum、extension、func、import、init、let、protocol、static、struct、subscript、typealias、var。
□ 用作语句的关键字:break、case、continue、default、do、else、fallthrough、if、in、for、return、switch、where、while。
□ 用作表达和类型的关键字: as、dynamicType、is、new、super、self、Self、Type、__COLUMN__、__FILE__、__FUNCTION__、__LINE__。
□ 特定上下文中被保留的关键字: associativity、didSet、get、infix、inout、left、mutating、none、nonmutating、operator、override、postfix、 precedence、prefix、right、set、unowned、unowned(safe)、unowned(unsafe)、weak、willSet,这些关键字在特定上下文之外可以被用于标识符。
3.4 常量和变量
Swift 语言中的基本数据类型,按其取值可以分为常量和变量两种。在程序执行过程中,其值不发生改变的量被称为常量,其值可变的量被称为变量。两者可以和数据类型结合起来进行分类,例如,可以分为整型常量、整型变量、浮点常量、浮点变量、字符常量、字符变量、枚举常量、枚举变量。在 C 语言程序中,常量是可以不经说明而直接引用的,而变量则必须先定义后使用。在本节的内容中,将对常量和变量的知识进行深入理解。
3.4.1 声明常量和变量
在Swift语言中,在使用常量和变量前必须声明,使用关键字“let”来声明常量,使用关键字“var”来声明变量。
在Swift语言中,使用关键字let声明常量,具体语法格式如下所示。
let name = value
(1)name:表示常量的名字,在命名时建议遵循如下所示的规范。
□ 变量名首字母必须为字母(小写a-z,大写A-Z)或下划线“_”开始。
□ 常量名之间不能包含空格。
(2)value:常量的值。
例如在下面的演示代码中,展示了用常量和变量来记录用户尝试登录次数的方法。
let mm = 10 var nn = 0
在上述代码中,声明一个名为“mm”的新常量,并且将其赋值为10。然后,声明一个名为“nn”的变量,并且将其值初始化为0。这样,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试登录次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。
实例文件main.swift的具体实现代码如下所示。
import Foundation let friendlyWelcome = "我爱巴西世界杯!" let aa = 10 let name = "fan pei xi" println(friendlyWelcome) println(aa) println(name)
本实例执行后的效果如图3-1所示。
图3-1 执行效果
3.4.2 声明变量
在Swift语言中,其值可以改变的量被称为变量。一个变量应该有一个名字,在内存中占据一定的存储单元。变量定义必须放在变量使用之前,一般放在函数体的开头部分。可以在Swift程序的同一行代码中声明多个常量或者多个变量,之间用逗号“,”隔开。例如下面的演示代码。
var x = 0.0, y = 0.0, z = 0.0 var m = 10, n= 2134, q = 12345
在Swift语言中,如果在代码中有不需要改变的值,则需要使用关键字“let”将其声明为常量,将需要改变的值声明为变量。
在Swift程序中,根据声明变量类型和值的不同有着不同的声明形式,例如,存储和计算变量及属性、存储变量和属性监视,以及静态变量属性,所使用的声明形式取决于变量所声明的范围和你打算声明的变量类型。
(1)声明存储型变量和存储型属性。
在Swift语言中,通过如下所示的格式声明一个存储型变量或存储型变量属性。
var name: type = expression
在上述格式中,“name”表示声明的变量名字,“type”表示声明的变量的类型,“expression”表示具体的变量值描述。在 Swift 语言中,可以使用上述格式在全局、函数、类和结构体的声明(context)中声明一个变量,具体说明如下所示。
□ 当以上述形式在全局或者一个函数内声明变量时,声明的变量代表是一个存储型变量。
□ 当在类或者结构体中被声明时,声明的变量代表是一个存储型变量属性。
□ 初始化的表达式不可以在协议(protocol)定义中出现,在其他情况下的初始化表达式是可选的。如果没有初始化表达式,那么在定义变量时必须显示地声明变量类型(:type)。
□ 正如名字一样,存储型变量的值或存储型变量属性存储在内存中。
实例文件main.swift的具体实现代码如下所示。
import Foundation var r :Float=5 var pi :Float=3.14 var circ :Float=pi*2*r var area :Float=pi*r*r println(r) //显示结果 println(circ) //显示结果 println(area) //显示结果
本实例执行后的效果如图3-2所示。
图3-2 执行效果
(2)计算型变量和计算型属性。
在Swift语言中,通过如下所示的格式声明一个存储型变量或存储型属性。
var variable name: type { get { statements } set(setter name) { statements } }
在Swift程序中,可以使用上述格式在全局、函数体、类、结构体、枚举和扩展中声明一个变量。具体说明如下所示。
□ 当变量以这种形式在全局或者一个函数内被声明时,声明的变量代表一个计算型变量。
□ 当在类、结构体、枚举、扩展的上下文中被声明时,声明的变量代表一个计算型变量属性。
□ getter用来读取变量值,setter用来写入变量值。setter子句是可选择的,而getter是必需的。在实际应用中可以将这些语句全部省略,只是简单地直接返回请求值。但是setter语句和getter语句必须成对出现,如果提供了一个setter语句,也必需提供一个getter语句。
□ setter的名字和圆括号内的语句是可选的。如果设置了一个setter名,它就会作为setter的参数被使用。
□ 同存储型变量和存储型属性不同,计算型属性和计算型变量的值不存储在内存中。
(3)存储型变量监视器和属性监视器。
在Swift语言中,可以用willset和didset监视器来声明一个存储型变量或属性。声明一个包含监视器的存储型变量或属性的语法格式如下所示。
var variable name: type = expression { willSet(setter name) { statements } didSet(setter name) { statements } }
在Swift程序中,可以使用上述格式在全局、函数体、类、结构体、枚举、扩展中声明一个变量。具体说明如下所示。
□ 当变量以上述格式在全局或者一个函数内被声明时,监视器代表一个存储型变量监视器;
□ 当在类、结构体、枚举、扩展中被声明变量时,监视器代表属性监视器。
□ 可以为适合的监视器添加任何存储型属性,也可以通过重写子类属性的方式为适合的监视器添加任何继承的属性(无论是存储型还是计算型的)。
□ 初始化表达式在类或者结构体的声明中是可选的,但是在其他地方是必需的。无论在什么地方声明,所有包含监视器的变量声明都必须有类型注释(Type Annotation)。
□ 当改变变量或属性的值时,willset和didset监视器提供了一个监视方法(适当的回应)。监视器不会在变量或属性第一次初始化时运行,只有在值被外部初始化语句改变时才会被运行。
□ willset监视器只有在变量或属性值被改变之前运行。新的值作为一个常量经过willset监视器,因此不可以在willset语句中改变它。didset监视器在变量或属性值被改变后立即运行。和willset监视器相反,为了防止仍然需要获得旧的数据,旧变量值或者属性会经过didset监视器。这表示如果在变量或属性自身的didiset监视器语句中设置了一个值,设置的新值会取代在willset监视器中经过的那个值。
□ 在willset和didset语句中,setter名和圆括号的语句是可选的。如果设置了一个setter名,它就会作为willset和didset的参数被使用。如果不设置setter名,willset监视器初始名为newvalue, didset监视器初始名为oldvalue。
□ 当提供一个willset语句时,didset语句是可选的。同样道理,当提供了一个didset语句时, willset语句是可选的。
(4)类和静态变量属性。
在Swift语言中,使用class关键字用来声明类的计算型属性,使用static关键字用来声明类的静态变量属性。
3.4.3 输出常量和变量
在Swift语言中,可以用函数println来输出当前常量或变量的值。例如,如下所示的演示代码。
var friendlyWelcome = "Hello!" friendlyWelcome = "mm!" println(friendlyWelcome) // 输出 "mm!"
在Swift语言中,println是一个用来输出的全局函数,输出的内容会在最后换行。如果用Xcode, println 将会输出内容到“console”面板上。另一种函数叫 print,唯一区别是在输出内容最后不会换行。
例如在下面的演示代码中,println函数会输出传入的String值。
println("This is a string") // 输出 "This is a string"
与 Cocoa中的函数NSLog类似,函数println可以输出更复杂的信息,这些信息可以包含当前常量和变量的值。
在Swift语言中,使用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中。Swift 会用当前常量或变量的值替换这些占位符,将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义。例如,如下所示的演示代码。
var friendlyWelcome = "Hello!" friendlyWelcome = "mm!" println("The current value of friendlyWelcome is \(friendlyWelcome)") // 输出 "The current value of friendlyWelcome is mm!
注意
字符串插值所有可用的选项,请参考字符串插值。
实例文件main.swift的具体实现代码如下所示。
import Foundation var r,pi,area,circ:Float r=5 pi=3.14 circ=pi*2*r area=pi*r*r
println(r) //显示结果 println(circ) //显示结果 println(area) //显示结果
本实例执行后的效果如图3-3所示。
图3-3 执行效果
3.4.4 标注类型
在Swift语言中,在声明常量或者变量时可以加上类型标注(Type annotation),用以说明常量或者变量中要存储的值的类型。当在程序中添加类型标注时,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
例如在下面的代码中,为变量welcomeMessage标注了类型,表示这个变量可以存储String类型的值。例如,如下所示的演示代码。
var welcomeMessage: String
声明中的冒号代表着“是...类型”,所以这行代码可以被理解为。
□ 声明一个类型为String,名字为welcomeMessage的变量。
□ 类型为String的意思是可以存储任意String类型的值。
此时变量welcomeMessage可以被设置成任意字符串,例如,如下所示的演示代码。
welcomeMessage = "Hello"
在Swift编程应用中,一般很少需要用到类型标注。如果在声明常量或者变量时赋了一个初始值,Swift 可以推断出这个常量或者变量的类型,具体方法请参考本书后面的类型安全和类型推断章节的内容。在上面的演示代码中,因为没有给变量 welcomeMessage 赋初始值,所以变量welcomeMessage的类型是通过一个类型标注指定的,而不是通过初始值推断的。
实例文件main.swift的具体实现代码如下所示。
import Foundation var r=5 var pi=3.14 var aa="I love you" println(r) //显示结果 println(pi) //显示结果 println(aa) //显示结果
本实例执行后的效果如图3-4所示。
图3-4 执行效果
3.4.5 常量和变量的命名规则
在Swift语言中,可以用任何喜欢的字符作为常量和变量名,这其中也包括Unicode字符。例如,如下所示的演示代码。
let π = 3.14159 let 你好 = "你好世界" let □□ = "dogcow"
在Swift语言中,常量与变量名不能包含如下所示的元素。
□ 数学符号。
□ 箭头。
□ 保留的(或者非法的)Unicode 码位。
□ 连线。
□ 制表符。
□ 不能以数字开头,但是可以在常量与变量名的其他地方包含数字。
一旦将常量或者变量声明为确定的类型,那么就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。另外,也不能将常量与变量进行互转。
注意
如果需要使用与Swift保留关键字相同的名称作为常量或者变量名,必须使用反引号“`”将关键字包围的方式将其作为名字使用。尽管如此,建议读者避免使用关键字作为常量或变量名,除非别无选择。
在Swift语言中,可以修改现有的变量值为其他同类型的值,例如在下面的演示代码中,将变量friendlyWelcome的值从“Hello!”改为了“mm!”。
var friendlyWelcome = "Hello!" friendlyWelcome = "mm!" // friendlyWelcome 现在的值是 "mm!"
实例文件main.swift的具体实现代码如下所示。
var r=5 var pi=3.14 var aa="I love you" r=10 pi=3.1415 println(r) //显示结果 println(pi) //显示结果 println(aa) //显示结果
本实例执行后的效果如图3-5所示。
图3-5 执行效果
与变量不同,常量的值一旦被确定就不能更改了。如果对常量尝试上述修改操作,就会发生编译报错的情形。例如,如下所示的演示代码。
let languageName = "Swift" languageName = "C++" // 此时会发生报编译错误,因为languageName 不可改变
3.5 注释
在通常情况下,注释是说明你的代码做些什么,而不是怎么做的。在Swift代码中,将非执行文本写为注释提示或者笔记的方式以方便将来进行阅读。Swift 编译器将会在编译代码时自动忽略掉注释部分。在本节的内容中,将详细讲解Swift注释的基本知识。
3.5.1 注释的规则
在Swift语言中,通过使用注释可以帮助阅读程序,通常用于概括算法、确认变量的用途或者阐明难以理解的代码段。注释并不会增加可执行程序的大小,编译器会忽略所有注释。Swift 中有两种类型的注释,分别是单行注释和成对注释。单行注释以双斜线“//”开头,行中处于双斜杠右边的内容是注释,被编译器忽略。例如,如下所示的演示代码。
// 这是一个注释
另一种定界符:注释对(/**/),是从C语言继承过来的。这种注释以“/*”开头,以“*/”结尾,编译器把落入注释对“/**/”之间的内容作为注释。例如,如下所示的演示代码。
/* 这是一个, 多行注释 */
与C语言多行注释不同,Swift 的多行注释可以嵌套在其他的多行注释中。例如可以先生成一个多行注释块,然后在这个注释块之中再嵌套成第二个多行注释。在终止注释时,先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记。例如,如下所示的演示代码。
/* 这是第一个多行注释的开头 /* 这是第二个被嵌套的多行注释 */ 这是第一个多行注释的结尾 */
通过运用嵌套多行注释,你可以快速方便地注释掉一大段代码,即使这段代码之中已经含有了多行注释块。
实例文件main.swift的具体实现代码如下所示。
import Foundation //定义了一个变量r,初始值是5 var r=5 /* 在下面的代码中,“var pi=3.14”表示定义了一个变量pi,初始值是3.14 var aa="I love you" 表示定义了一个变量aa,初始值是"I love you" "r=10"表示修改了变量r的值为10 pi=3.1415表示修改了变量pi的值为3.1415 */ var pi=3.14 var aa="I love you" r=10 pi=3.1415 println(r) //显示结果 println(pi) //显示结果 println(aa) //显示结果
本实例执行后的效果如图3-6所示。
图3-6 执行效果
3.5.2 使用注释的注意事项
在Swift语言中,可以在任何允许有制表符、空格或换行符的地方放置注释对。注释对可跨越程序的多行,但不是一定要如此。当注释跨越多行时,最好能直观地指明每一行都是注释的一部分。我们的风格是在注释的每一行以星号开始,指明整个范围是多行注释的一部分。
在Swift程序中通常混用两种注释形式。注释对一般用于多行解释,而双斜线注释则常用于半行或单行的标记。太多的注释混入程序代码可能会使代码难以理解,通常最好是将一个注释块放在所解释代码的上方。
当改变代码时,注释应与代码保持一致。程序员即使知道系统其他形式的文档已经过期,还是会信任注释,认为它会是正确的。错误的注释比没有注释更糟,因为它会误导后来者。
在Swift程序中使用注释时,必须遵循如下所示的原则。
□ 禁止乱用注释。
□ 注释必须和被注释内容一致,不能描述和其无关的内容。
□ 注释要放在被注释内容的上方或被注释语句的后面。
□ 函数头部需要注释,主要包含文件名、作者信息、功能信息和版本信息。
□ 注释对不可嵌套:注释总是以“/*”开始并以“/*”结束。这意味着,一个注释对不能出现在另一个注释对中。由注释对嵌套导致的编译器错误信息容易使人迷惑。
3.6 数据类型
在Swift语言中,数据类型是根据被定义变量的性质、表示形式、占据存储空间的多少和构造特点来划分的。Swift 语言中的数据类型可分为:基本数据类型、构造数据类型、指针类型和空类型共4大类,上述各类型的具体结构如图3-7所示。
图3-7 Swift语言数据类型结构
Swift 语言存在两种类型,分别是命名型类型和复合型类型。命名型类型是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。例如,一个用户定义的类MyClass的实例拥有类型 MyClass。除了用户定义的命名型类型,Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。
(1)命名型类型。
在Swift语言中,通常被其他语言认为是基本或初级的数据型类型(Data types)就是命名型类型,例如表示数字、字符和字符串,在Swift 标准库中使用结构体来定义和实现它们。因为是命名型类型,所以可以通过声明扩展的方式来增加它们的行为,以适应我们程序的需求。
(2)复合型类型。
在Swift语言中,复合型类型是没有名字的类型,它由Swift本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其他复合型类型。例如,元组类型(Int, (Int, Int))包含两个元素,其中第一个是命名型类型Int,第二个是另一个复合型类型(Int, Int)。
在本节的内容中,将详细讨论 Swift 语言本身定义的类型,并讲解 Swift 程序中的类型推断行为。
3.6.1 数据类型的分类
在Swift语言中,可以将数据类型分为如下所示的几种类型。
(1)基本数据类型。
基本数据类型最主要的特点是,其值不可以再分解为其他类型。也就是说,基本数据类型是自我说明的。
(2)构造数据类型。
构造数据类型是在基本类型基础上产生的复合数据类型。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。在 C语言中,有以下3种构造类型。
□ 数组类型。
□ 结构体类型。
□ 共用体(联合)类型。
(3)指针类型。
指针是一种特殊的类型,同时又是具有重要作用的数据类型。其值用来表示某个变量在内存储器中的地址。虽然指针变量的取值类似于整型量,但这是两个类型完全不同的量,因此不能混为一谈。
3.6.2 类型安全和类型推断
Swift是一门类型安全(type safe)的语言,此类语言可以让开发者清楚地知道代码要处理的值的类型。如果在代码中需要一个String,绝对不可能不小心传进去一个Int。因为Swift语言是类型安全的,所以会在编译代码时进行类型检查(Type Checks),并把不匹配的类型标记为错误,这样可以在开发时尽早发现并修复错误。
在Swift语言中,当需要处理不同类型的值时,通过类型检查可以避免很多错误。但是类型检查并不是意味着每当声明常量和变量时都需要显式指定类型,如果没有显式指定类型,Swift 会使用类型推断(type inference)来选择合适的类型。通过类型推断机制,编译器可以在编译代码的时候自动推断出表达式的类型。具体的实现原理很简单,只要检查在代码中的具体赋值即可。
在Swift语言中,因为具有类型推断机制,所以和 C或者Objective-C相比,在Swift程序中很难见到声明类型的代码。尽管常量和变量虽然需要明确类型,但是大部分工作并不需要开发者来完成。
在Swift语言中,类型推断在声明常量或者变量并赋初值时变得非常有用。当在声明常量或者变量的时候,赋给它们一个字面量(literal value或literal)即可触发类型推断。此处的字面量是直接出现在代码中的值,如42和3.14159。
例如,如果你给一个新常量赋值42并且没有标明类型,Swift 可以推断出常量类型是Int,因为你给它赋的初始值看起来像一个整数:
let meaningOfLife = 42 // meaningOfLife 会被推测为 Int 类型
同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是Double:
let pi = 3.14159 // pi 会被推测为 Double 类型
当推断浮点数的类型时,Swift 总是会选择Double而不是Float。
如果表达式中同时出现了整数和浮点数,会被推断为Double类型:
let anotherPi = 3 + 0.14159 // anotherPi 会被推测为 Double 类型
3.6.3 类型注解
在Swift语言中,类型注解显式地指定一个变量或表达式的值。类型注解从冒号“:”开始,终于类型。比如下面实现了两个类型注解的例子。
let someTuple: (Double, Double) = (3.14159, 2.71828) func someFunction(a: Int){ /* ... */ }
在上述代码中,第一个例子中的表达式someTuple的类型被指定为(Double, Double),第二个例子中的函数someFunction的参数a的类型被指定为Int。
在Swift语言中,类型注解可以在类型之前包含一个类型特性(type attributes)的可选列表。
3.6.4 类型标识符
在Swift语言中,类型标识符用于引用命名型类型或者是命名型/复合型类型的别名。在大多数情况下,类型标识符引用的是同名的命名型类型,例如,类型标识符Int引用命名型类型Int。同样道理,类型标识符Dictionary<String, Int>引用的是命名型类型Dictionary<String, Int>。
在如下所示的两种情况下,类型标识符引用的不是同名的类型。
(1)类型标识符引用的是命名型/复合型类型的类型别名。例如,在下面的例子中,类型标识符使用Point来引用元组(Int, Int)。
typealias Point = (Int, Int) let origin: Point = (0, 0)
(2)类型标识符使用点“.”语法来表示在其他模块(modules),或其他类型嵌套内声明的命名型类型。例如在下面例子中,类型标识符引用在 ExampleModule 模块中声明的命名型类型MyType。
var someValue: ExampleModule.MyType
3.6.5 元组类型
在Swift语言中,元组类型使用逗号隔开,并使用括号括起来的0个或多个类型组成的列表。在Swift语言中,可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符和:组成。关键字void是空元组类型()的别名。如果括号内只有一个元素,那么该类型就是括号内元素的类型。例如,(Int)的类型是 Int 而不是(Int)。所以,只有当元组类型包含两个元素以上时才可以标记元组元素。
3.6.6 函数类型
在Swift语言中,函数类型表示一个函数、方法或闭包的类型,由一个参数类型和返回值类型组成,中间用箭头→隔开,例如:
parameter type -> return type
由于参数类型和返回值类型可以是元组类型,所以函数类型可以让函数与方法支持多参数与多返回值。
在Swift语言中,可以对函数类型应用带有参数类型()并返回表达式类型的auto_closure属性。一个自动闭包函数捕获特定表达式上的隐式闭包而非表达式本身。例如在下面的例子中,使用auto_closure属性定义了一个很简单的assert函数。
func simpleAssert(condition: @auto_closure () -> Bool, message: String){ if !condition(){ println(message) } } let testNumber = 5 simpleAssert(testNumber % 2 == 0, "testNumber isn't an even number.") // prints "testNumber isn't an even number."
函数类型可以拥有一个可变长参数作为参数类型中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字和...组成,如Int...。可变长参数被认为是一个包含了基础类型元素的数组。即Int...就是Int[]。
为了在Swift程序中指定一个in-out参数,可以在参数类型前加inout前缀。但是不可以对可变长参数或返回值类型使用inout。
在Swift语言中,柯里化函数(curried function)的类型相当于一个嵌套函数类型。例如,下面的柯里化函数addTwoNumber()()的类型是Int→Int→Int:
func addTwoNumbers(a: Int)(b: Int) -> Int{ return a + b } addTwoNumbers(4)(5) // returns 9
在Swift语言中,柯里化函数的函数类型从右向左组成一组。例如,函数类型Int→Int→Int可以被理解为Int→(Int→Int)。也就是说,一个函数传入一个Int然后输出作为另一个函数的输入,然后又返回一个Int。例如,可以使用如下嵌套函数代码来重写柯里化函数addTwoNumbers()()。
func addTwoNumbers(a: Int) -> (Int -> Int){ func addTheSecondNumber(b: Int) -> Int{ return a + b } return addTheSecondNumber } addTwoNumbers(4)(5) // Returns 9
3.6.7 数组类型
在Swift语言中,使用类型名紧接中括号[]来简化标准库中定义的命名型类型Array<T>。换句话说,下面两个声明代码是等价的。
let someArray: String[] = ["Alex", "Brian", "Dave"] let someArray: Array<String> = ["Alex", "Brian", "Dave"]
在上述两种情况下,常量someArray都被声明为字符串数组。数组的元素也可以通过[]获取访问:someArray[0]是指第0个元素“Alex”。上面的例子同时显示,可以使用[]作为初始值构造数组,空的[]则用来构造指定类型的空数组。
var emptyArray: Double[] = []
在 Swift 语言中,也可以使用链接起来的多个[]集合来构造多维数组。例如,在如下所示的演示代码中,使用3个[]集合构造了三维整型数组。
var array3D: Int[][][] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
当在Swift程序中访问一个多维数组的元素时,最左边的下标指向最外层数组的相应位置元素。接下来往右的下标指向第一层嵌入的相应位置元素,依次类推。这就意味着,在上面的演示代码中, array3D[0]是指[[1, 2], [3, 4]],array3D[0][1]是指[3, 4],array3D[0][1][1]则是指值4。
3.6.8 可选类型
在Swift语言中,通过定义后缀“?”的方式来作为标准库中定义的命名型类型Optional<T>的简写形式。也就是说,如下两个声明语句是等价的。
var optionalInteger: Int? var optionalInteger: Optional<Int>
在上述两种情况下,变量optionalInteger都被声明为可选整型类型。注意,在类型和“?”之间没有空格。
在Swift语言中,类型Optional<T>是一个枚举,有两种形式,分别是None和Some(T),用于代表可能出现或可能不出现的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。当声明一个可选类型时,确保使用括号给“?”提供合适的作用范围。例如,声明一个整型的可选数组,需要写成如下形式:
(Int[])?
如果写成如下形式的话就会出错。
Int[]?
在Swift语言中,如果在声明或定义可选变量或特性的时候没有提供初始值,其值则会自动赋成缺省值nil。
在Swift语言中,因为可选类型完全符合LogicValue协议,所以,可以出现在布尔值环境下。此时,如果一个可选类型T?实例包含有类型为T的值(也就是说值为Optional.Some(T)),那么此可选类型就为true,否则为false。
在Swift语言中,如果一个可选类型的实例包含一个值,那么就可以使用后缀操作符“!”来获取该值,例如下面的演示代码。
optionalInteger = 42 optionalInteger! // 42
使用“!”操作符获取值为nil的可选项会导致运行错误(runtime error)。
在Swift语言中,也可以使用可选链和可选绑定来选择性地执行可选表达式上的操作。如果值为nil,则不会执行任何操作,因此也就没有运行错误产生。
3.6.9 隐式解析可选类型
在 Swift 语言中,通过定义后缀“!”的方式作为标准库中命名类型 ImplicitlyUnwrapped Optional<T>的简写形式。也就是说,下面的两个声明代码是等价的。
var implicitlyUnwrappedString: String! var implicitlyUnwrappedString: ImplicitlyUnwrappedOptional<String>
在上述两种情况下,变量implicitlyUnwrappedString被声明为一个隐式解析可选类型的字符串。在此需要注意,类型与“!”之间没有空格。
在Swift语言中,可以在使用可选的地方同样使用隐式解析可选。例如,可以将隐式解析可选的值赋给变量、常量和可选特性,反之亦然。
在Swift语言中,通过可选能在声明隐式解析可选变量或特性的时候就不用指定初始值,因为它有缺省值nil。
在Swift语言中,由于隐式解析可选的值会在使用时自动解析,所以没必要使用操作符“!”来解析它。也就是说,如果使用值为nil的隐式解析可选,就会导致运行错误。
在Swift语言中,使用可选链会选择性地执行隐式解析可选表达式上的某一个操作。如果值为nil,就不会执行任何操作,因此,也不会产生运行错误。
3.6.10 协议合成类型
在Swift语言中,协议合成类型是一种符合每个协议的指定协议列表类型。在现实应用中,协议合成类型可能会用在类型注解和泛型参数中。
在Swift语言中,协议合成类型的语法形式如下所示。
protocol<Protocol 1, Procotol 2>
在Swift语言中,协议合成类型允许指定一个值,其类型可以适配多个协议的条件,而且不需要定义一个新的命名型协议来继承其他想要适配的各个协议。例如,协议合成类型protocol<Protocol A, Protocol B, Protocol C>等效于一个从Protocol A、Protocol B、Protocol C继承而来的新协议Protocol D,很显然,这样做有效率,甚至不需引入一个新名字。
在Swift语言中,协议合成列表中的每项必须是协议名或协议合成类型的类型别名。如果列表为空,则会指定一个空协议合成列表,这样每个类型都能适配。
3.6.11 元类型
在Swift语言中,元类型是指所有类型的类型,包括类、结构体、枚举和协议。类、结构体或枚举类型的元类型是相应的类型名紧跟“.Type”。协议类型的元类型并不是运行时适配该协议的具体类型,而是该协议名字紧跟“.Protocol”。例如,类SomeClass的元类型就是SomeClass.Type,协议SomeProtocol的元类型就是SomeProtocal.Protocol。
在Swift语言中,可以使用后缀self表达式来获取类型。例如,SomeClass.self返回SomeClass本身,而不是SomeClass的一个实例。同样,SomeProtocol.self返回SomeProtocol本身,而不是运行时适配 SomeProtocol 的某个类型的实例。另外,还可以对类型的实例使用 dynamicType 表达式来获取该实例在运行阶段的类型,例如下面的演示代码。
class SomeBaseClass { class func printClassName() { println("SomeBaseClass") } } class SomeSubClass: SomeBaseClass { override class func printClassName() { println("SomeSubClass") } } let someInstance: SomeBaseClass = SomeSubClass() // someInstance is of type SomeBaseClass at compile time, but // someInstance is of type SomeSubClass at runtime someInstance.dynamicType.printClassName() // prints "SomeSubClass
3.6.12 类型继承子句
在Swift语言中,类型继承子句被用来指定一个命名型类型,继承哪个类且适配哪些协议。类型继承子句开始于冒号“:”,紧跟由逗号“,”隔开的类型标识符列表。
在Swift语言中,类可以继承单个超类,适配任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要适配的任意数量的协议。如果一个类不是从其他类继承而来,那么列表可以以协议开头。
在Swift语言中,其他命名型类型可能只继承或适配一个协议列表。协议类型可能继承于其他任意数量的协议。当一个协议类型继承于其他协议时,其他协议的条件集合会被集成在一起,然后其他从当前协议继承的任意类型必须适配所有这些条件。
在Swift语言中,枚举定义中的类型继承子句可以是一个协议列表,或是指定原始值的枚举,一个单独的指定原始值类型的命名型类型,使用类型继承子句来指定原始值类型的枚举定义的例子。
3.6.13 类型推断
在Swift语言中,通过使用类型推断的方式,允许开发者可以忽略很多变量和表达式的类型或部分类型。例如下面的代码。
var x: Int = 0
可以完全忽略类型而简写成。
var x = 0
编译器会正确地推断出x的类型Int。类似地,当完整的类型可以从上下文推断出来时,也可以忽略类型的一部分。例如,如果写为下面的形式。
let dict: Dictionary = ["A": 1]
编译器也能推断出dict的类型是Dictionary<String, Int>。
在上面的两个例子中,类型信息从表达式树(expression tree)的叶子节点传向根节点。也就是说,var x: Int = 0中x的类型首先根据0的类型进行推断,然后将该类型信息传递到根节点(变量x)。
在Swift语言中,类型信息也可以反方向流动——从根节点传向叶子节点。例如,在下面的例子中,常量eFloat上的显式类型注解(:Float)导致数字字面量2.71828的类型是Float,而不是Double。
let e = 2.71828 // The type of e is inferred to be Double. let eFloat: Float = 2.71828 // The type of eFloat is Float.
在Swift语言中,类型推断工作是在单独的表达式或语句水平上进行。这说明,所有用于推断类型的信息,必须可以从表达式或其某个子表达式的类型检查中获取。
3.7 最基本的数值类型
数据是人们记录概念和事物的符号表示,例如,记录人的姓名用汉字表示,记录人的年龄用十进制数字表示,记录人的体重用十进制数字和小数点表示等,由此得到的姓名、年龄和体重都是数据。根据数据的性质不同,可以把数据分为不同的类型。在日常开发应用中,数据主要被分为数值和文字(即非数值)两大类,数值又细分为整数和小数两类。
这里的数值型是指能够数学运算的数据类型,可以分为整型、浮点型和双精度型。整型数字可以用十进制、八进制、十六进制3种进制表示。根据整型字长的不同,又可以分为短整型、整型和长整型。
3.7.1 整数
整数(integers)就是像-3、-2、-1、0、1、2、3 等之类的数。整数的全体构成整数集,整数集是一个数环。在整数系中,零和正整数统称为自然数。-1、-2、-3、…、-n、…(n为非零自然数)为负整数。则正整数、零与负整数构成整数系。
在Swift语言中,整数就是没有小数部分的数字,比如42和-23。整数可以是有符号(正、负、零)或者无符号(正、零)。Swift 提供了8、16、32和64位的有符号和无符号整数类型。这些整数类型和C语言的命名方式很像,比如8位无符号整数类型是UInt8,32位有符号整数类型是Int32。就像Swift的其他类型一样,整数类型采用大写命名法。
(1)整数范围。
在Swift语言中,可以通过访问不同整数类型的min和max属性的方式,来获取对应类型的最大值和最小值。例如,如下所示的演示代码。
let minValue = UInt8.min // minValue 为 0,是 UInt8 类型的最小值 let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型的最大值
(2)Int。
一般来说,不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型Int,长度与当前平台的原生字长相同。
□ 在32位平台上,Int和Int32长度相同。
□ 在64位平台上,Int和Int64长度相同。
除非需要特定长度的整数,一般来说使用Int就够了,这可以提高代码一致性和可复用性。即使是在32位平台上,Int可以存储的整数范围也可以达到-2147483648~2147483647,大多数时候这已经足够大了。
(3)Uint。
在Swift语言中,提供了一个特殊的无符号类型UInt,长度与当前平台的原生字长相同。
□ 在32位平台上,UInt和UInt32长度相同。
□ 在64位平台上,UInt和UInt64长度相同。
注意
建议读者尽量不要使用 UInt,除非真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用Int,即使你要存储的值已知是非负的。统一使用Int可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,请参考类型安全和类型推断。
实例文件main.swift的具体实现代码如下所示。
import Foundation var r,pi,aa:Int r=5 pi=123451 aa=123 println(r) //显示结果 println(pi) //显示结果 println(aa) //显示结果
本实例执行后的效果如图3-8所示。
图3-8 执行效果
3.7.2 浮点数
浮点数就是实数,有两种表示方式:十进制形式(如123,123.0)和指数形式(如123e3,E前必须有数字,后面必须是整数)。
在Swift语言中,浮点数是有小数部分的数字,比如3.14159,0.1和-273.15。在Swift程序中,浮点类型比整数类型表示的范围更大,可以存储比 Int 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型。
□ Double表示64位浮点数。当需要存储很大或者高精度的浮点数时请使用此类型。
□ Float表示32位浮点数。精度要求不高的话可以使用此类型。
Double精确度很高,至少有15位数字,而Float最少只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围。
实例文件main.swift的具体实现代码如下所示。
import Foundation var r,pi,aa:Float r=5.0 pi=123451.123 aa=123.0 println(r) //显示结果 println(pi) //显示结果 println(aa) //显示结果
本实例执行后的效果如图3-9所示。
图3-9 执行效果
3.8 字面量
在Swift语言中,字面量是一个表示整型、浮点型数字或文本类型的值。例如下面的演示代码。
42 // 整型字面量 3.14159 // 浮点型字面量 "Hello, world!" // 文本型字面量
在本节的内容中,将详细讲解Swift字面量的基本知识。
3.8.1 数值型字面量
在Swift语言中,整数字面量可以被写作为如下所示的形式。
□ 一个十进制数,没有前缀。
□ 一个二进制数,前缀是0b。
□ 一个八进制数,前缀是0o。
□ 一个十六进制数,前缀是0x。
例如在下面的演示代码中,所有整数字面量的十进制值都是17。
let decimalInteger = 17 let binaryInteger = 0b10001 // 二进制的17 let octalInteger = 0o21 // 八进制的17 let hexadecimalInteger = 0x11 // 十六进制的17
浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是0x)。小数点两边必须至少有一个十进制数字(或者是十六进制的数字)。浮点字面量还有一个可选的指数(exponent),在十进制浮点数中通过大写或者小写的e来指定,在十六进制浮点数中通过大写或者小写的p来指定。
如果一个十进制数的指数为exp,那这个数相当于基数和10^exp的乘积。
□ 1.25e2 表示 1.25 × 10^2,等于 125.0。
□ 1.25e-2 表示 1.25 × 10^-2,等于 0.0125。
如果一个十六进制数的指数为exp,那这个数相当于基数和2^exp的乘积。
□ 0xFp2 表示 15 × 2^2,等于 60.0。
□ 0xFp-2 表示 15 × 2^-2,等于 3.75。
下面的这些浮点字面量都等于十进制的12.1875。
let decimalDouble = 12.1875 let exponentDouble = 1.21875e1 let hexadecimalDouble = 0xC.3p0
数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零,并且包含下划线,并不会影响字面量。
实例文件main.swift的具体实现代码如下所示。
import Foundation let paddedDouble = 000123.456 let oneMillion = 1_000_000 let justOverOneMillion = 1_000_000.000_000_1 println(paddedDouble) //显示结果 println(oneMillion) //显示结果 println(justOverOneMillion) //显示结果
本实例执行后的效果如图3-10所示。
图3-10 执行效果
3.8.2 整型字面量
整型字面量(integer literals)表示未指定精度整型数的值。在Swift语言中,整型字面量默认用十进制表示,可以加前缀来指定其他的进制,具体说明如下所示。
□ 二进制字面量加“0b”。
□ 八进制字面量加“0o”。
□ 十六进制字面量加“0x”。
在Swift语言中,整型字面量的具体规则如下所示。
(1)十进制字面量包含数字0至9。
(2)二进制字面量只包含0或1。
(3)八进制字面量包含数字0至7。
(4)十六进制字面量包含数字0至9以及字母A至F(大小写均可)。
(5)负整数的字面量的写法是在数字前加减号“-”,例如“-42”。
(6)允许使用下划线“_”来增加数字的可读性,下划线不会影响字面量的值。整型字面量也可以在数字前加 0,同样不会影响字面量的值。例如下面的演示代码。
1000_000 // 等于 1000000 005 // 等于 5
(7)除非特殊指定,整型字面量的默认类型为 Swift 标准库类型中的 Int。另外,在Swift标准库中还定义了其他不同长度以及是否带符号的整数类型。
实例文件main.swift的具体实现代码如下所示。
import Foundation let aa = 17 //十进制17 let zz = 0b10001 // 二进制17 let cc = 0o21 //八进制17 let dd = 0x11 //十六进制17 println(aa) //显示结果 println(zz) //显示结果 println(cc) //显示结果 println(dd) //显示结果
本实例执行后的效果如图3-11所示。
图3-11 执行效果
3.8.3 浮点型字面量
浮点型字面量(floating-point literals)表示未指定精度浮点数的值。在Swift语言中,浮点型字面量默认用十进制表示(无前缀),也可以用十六进制表示(加前缀 0x)。
在Swift语言中,浮点型字面量的具体使用规则如下所示。
(1)十进制浮点型字面量(Decimal Floating-point Literals):由十进制数字串后跟小数部分或指数部分(或两者皆有)组成。十进制小数部分由小数点“.”后跟十进制数字串组成。指数部分由大写或小写字母 e 后跟十进制数字串组成,这串数字表示e之前的数量乘以 10 的几次方。例如,1.25e2表示1.25×10^2,也就是125.0;同样,1.25e-2表示1.25×10^-2,也就是 0.0125。
(2)十六进制浮点型字面量(Hexadecimal Floating-point Literals):由前缀“0x”后跟可选的十六进制小数部分以及十六进制指数部分组成。十六进制小数部分由小数点后跟十六进制数字串组成,指数部分由大写或小写字母“p”后跟十进制数字串组成,这串数字表示“p”之前的数量乘以2的几次方。例如下面的演示代码。
0xFp2 //表示 15 × 2^2,也就是 60; 0xFp-2//表示 15 × 2^-2,也就是 3.75。
(3)与整型字面量不同,负的浮点型字面量由一元运算符减号“-”和浮点型字面量组成,例如“-42.0”代表是一个表达式,而不是一个浮点整型字面量。
(4)允许使用下划线“_”来增强可读性,下划线不会影响字面量的值。浮点型字面量也可以在数字前加 0,同样不会影响字面量的值。例如下面的演示代码。
10_000.56 // 等于 10000.56
005000.76 // 等于 5000.76
(5)除非特殊指定,浮点型字面量的默认类型为 Swift标准库类型中的Double,表示64位浮点数。Swift标准库也定义Float类型,表示32位浮点数。
3.8.4 文本型字面量
在Swift语言中,文本型字面量(string literal)由双引号中的字符串组成,具体形式如下所示。
"characters"
在Swift语言中,文本型字面量的具体使用规则如下所示。
(1)文本型字面量中不能包含未转义的双引号“"”、未转义的反斜线“\”、回车符(Carriage Return)或换行符(Line Feed)。
(2)可以在文本型字面量中使用的转义特殊符号,具体说明如下所示。
□ 空字符(Null Character)\0。
□ 反斜线(Backslash)\\。
□ 水平 Tab (Horizontal Tab)\t。
□ 换行符(Line Feed)\n。
□ 回车符(Carriage Return)\r。
□ 双引号(Double Quote)\"。
□ 单引号(Single Quote)\'。
(3)可以用以下方式来表示一个字符,后跟的数字表示一个Unicode码点。
□ \x 后跟两位十六进制数字。
□ \u 后跟4位十六进制数字。
□ \U 后跟8位十六进制数字。
(4)文本型字面量允许在反斜线小括号“\()”中插入表达式的值。插入表达式(interpolated expression)不能包含未转义的双引号“"”、反斜线“\”、回车符或者换行符。表达式值的类型必须在 String 类中有对应的初始化方法。例如,下面代码中的所有文本型字面量的值相同。
"1 2 3" "1 2 \(3)" "1 2 \(1 + 2)" var x = 3; "1 2 \(x)"
(5)文本型字面量的默认类型为String,组成字符串的字符类型为 Character。
实例文件main.swift的具体实现代码如下所示。
import Foundation var r=5 var pi=3.14 var aa="I love you"
r=10 pi=3.1415 var dd="123" var ee="12\(3)" println("r\n\n") //显示结果 println(pi) //显示结果 println(aa) //显示结果 println("\"pi\"") //显示结果 println(dd) //显示结果 println(ee) //显示结果
本实例执行后的效果如图3-12所示。
图3-12 执行效果
3.9 数值型类型转换
通常来讲,即使代码中的整数常量和变量已知非负,也请使用Int类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用,并且可以匹配整数类字面量的类型推断。只有在必要的时候才使用其他整数类型,例如,要处理外部的长度明确的数据或者为了优化性能、内存占用等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。
3.9.1 整数转换
在Swift语言中,不同整数类型的变量和常量可以存储不同范围的数字。Int8类型的常量或者变量可以存储的数字范围是-128~127,而UInt8类型的常量或者变量能存储的数字范围是0~255。如果数字超出了常量或者变量可存储的范围,编译的时候会报错。例如,如下所示的演示代码。
let cannotBeNegative: UInt8 = -1 // UInt8 类型不能存储负数,所以会报错 let tooBig: Int8 = Int8.max + 1 // Int8 类型不能存储超过最大值的数,所以会报错
由于每种整数类型都可以存储不同范围的值,所以,必须根据不同情况选择性使用数值型类型转换。这种选择性使用的方式,可以预防隐式转换的错误,并让你的代码中的类型转换意图变得清晰。
在Swift语言中,要将一种数字类型转换成另一种,需要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。例如在下面的例子中,常量twoThousand是UInt16类型,然而常量one是UInt8类型。它们不能直接相加,因为它们的类型不同。所以要调用UInt16(one)来创建一个新的UInt16数字,并用one的值来初始化,然后使用这个新数字来计算。
let twoThousand: UInt16 = 2_000 let one: UInt8 = 1 let twoThousandAndOne = twoThousand + UInt16(one)
现在两个数字的类型都是UInt16,可以进行相加。目标常量twoThousandAndOne的类型被推断为UInt16,因为它是两个UInt16值的和。
SomeType(ofInitialValue)是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部, UInt16有一个构造器,可以接受一个UInt8类型的值,所以,这个构造器可以用现有的UInt8来创建一个新的UInt16。注意,并不能传入任意类型的值,只能传入UInt16内部有对应构造器的值。
3.9.2 整数和浮点数转换
整数和浮点数的转换必须显式指定类型,例如,如下所示的演示代码。
let three = 3 let pointOneFourOneFiveNine = 0.14159 let pi = Double(three) + pointOneFourOneFiveNine // pi 等于 3.14159,所以被推测为 Double 类型
这个例子中,常量three的值被用来创建一个Double类型的值,所以,加号两边的数类型须相同。如果不进行转换,两者无法相加。
浮点数到整数的反向转换同样行,整数类型可以用Double或者Float类型来初始化。例如,如下所示的演示代码。
let integerPi = Int(pi) // integerPi 等于 3,所以被推测为 Int 类型
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 4.75 会变成 4,-3.9会变成-3。
实例文件main.swift的具体实现代码如下所示。
import Foundation var a,b,c,pi : Float var d,e,f:Int pi=3.14 a=1.1 b=0.12 c=5 d=Int(pi)*Int(a) println(d)
本实例执行后的效果如图3-13所示。
图3-13 执行效果
注意
结合数字类常量和变量不同于结合数字类字面量。字面量 3 可以直接和字面量0.14159相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。
3.10 类型别名
在Swift语言中,类型别名(type aliases)就是给现有类型定义另一个名字。可以使用typealias关键字来定义类型别名。当想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设正在处理特定长度的外部资源的数据,例如,如下所示的演示代码。
typealias AudioSample = UInt16
在Swift语言中,当定义了一个类型别名之后,可以在任何使用原始名的地方使用别名。例如如下所示的演示代码。
var maxAmplitudeFound = AudioSample.min // maxAmplitudeFound 现在是 0
在上述演示代码中,AudioSample被定义为UInt16的一个别名。因为它是别名,AudioSample.min实际上是UInt16.min,所以会给maxAmplitudeFound赋一个初值0。
实例文件main.swift的具体实现代码如下所示。
import Foundation typealias tt = UInt32 var a,b,c,pi : Float var d,e,f:tt pi=3.14 a=1.1 b=0.12 c=5 d=tt(pi)*tt(a) println(a) println(b) println(d)
本实例执行后的效果如图3-14所示。
图3-14 执行效果
3.11 布尔值
在Swift语言中,基本的布尔(Boolean)类型叫做Bool。布尔值指逻辑上的(Logical),因为它们只能是真或者假。在Swift程序中有两个布尔常量,分别是true和false。例如下面的演示代码。
let orangesAreOrange = true let turnipsAreDelicious = false
orangesAreOrange和turnipsAreDelicious的类型会被推断为Bool,因为它们的初始值是布尔字面量。就像之前提到的Int和Double一样,如果创建变量的时候给它们赋值true或false,那不需要将常量或者变量声明为Bool类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让 Swift 代码更加简洁并且可读性更高。
在Swift语言中,布尔值在编写的条件语句时非常有用。例如,如下所示的演示代码。
if turnipsAreDelicious { println("Mmm, tasty turnips!") } else { println("Eww, turnips are horrible.") } // 输出 "Eww, turnips are horrible."
条件语句,例如if,请参考控制流。
在Swift语言中,如果你在需要使用Bool类型的地方使用了非布尔值,Swift 的类型安全机制会报错。例如下面的例子会报告一个编译时错误。
let i = 1 if i { // 这个例子不会通过编译,会报错 }
然而,下面的演示例子是合法的。
let i = 1 if i == 1 { // 这个例子会编译成功 }
i == 1的比较结果是Bool类型,所以,第二个例子可以通过类型检查。类似i == 1这样的比较,请参考基本操作符。
和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误,并保证这块代码的意图总是清晰的。
3.12 元组
在Swift语言中,元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。下面这个例子中,(404, "Not Found")是一个描述 HTTP 状态码(HTTP status code)的元组。HTTP 状态码是当你请求网页的时候 Web 服务器返回的一个特殊值。如果请求的网页不存在,就会返回如下所示的404 Not Found状态码。
let http404Error = (404, "Not Found") // http404Error 的类型是 (Int, String),值是 (404, "Not Found")
(404, "Not Found")元组把一个Int值和一个String值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为(Int, String)的元组”。
在Swift语言中,可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为(Int, Int, Int)或者(String, Bool)或者其他任何想要的组合的元组。
在 Swift 语言中,可以将一个元组的内容分解(decompose)成单独的常量和变量,然后就可以正常使用它们了。例如,如下所示的演示代码。
let (statusCode, statusMessage) = http404Error println("The status code is \(statusCode)") // 输出 "The status code is 404" println("The status message is \(statusMessage)") // 输出 "The status message is Not Found"
在 Swift 语言中,如果只需要一部分元组值,分解的时候可以把要忽略的部分用下划线“_”标记。例如,如下所示的演示代码。
let (justTheStatusCode, _) = http404Error println("The status code is \(justTheStatusCode)") // 输出 "The status code is 404"
在Swift语言中,还可以通过下标来访问元组中的单个元素,下标从零开始。例如,如下所示的演示代码。的演示代码。
println("The status code is \(http404Error.0)") // 输出 "The status code is 404" println("The status message is \(http404Error.1)") // 输出 "The status message is Not Found"
在Swift语言中,可以在定义元组的时候给单个元素命名,例如,如下所示的演示代码。
let http200Status = (statusCode: 200, description: "OK")
在Swift语言中,给元组中的元素命名后,可以通过名字来获取这些元素的值。例如,如下所示的演示代码。示的演示代码。
println("The status code is \(http200Status.statusCode)")
// 输出 "The status code is 200" println("The status message is \(http200Status.description)") // 输出 "The status message is OK"
在Swift语言中,当元组作为函数返回值时非常有用,例如,一个用来获取网页的函数可能会返回一个(Int, String)元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考函数参数与返回值。
实例文件main.swift的具体实现代码如下所示。
import Foundation let first = (10000, "1月份") let (gongzi1, yiyue) = first let second = (20000, "2月份") let (gongzi2, eryue) = second println("今年的收入,yiyue: \(gongzi1)") // 输出 "The status code is 404" println("今年的收入,eryue: \(gongzi2)") // 输出 "The status message is Not Found"
本实例执行后的效果如图3-15所示。
图3-15 执行效果
注意
元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。请参考类和结构体。
3.13 可选类型
在Swift语言中,使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示。
□ 有值,等于x。
□ 或者。
□ 没有值。
注意
C和Objective-C中并没有可选类型这个概念。最接近的是Objective-C中的一个特性,一个方法或者返回一个对象,或者返回nil,nil表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(如 NSNotFound)来暗示值缺失。这种方法是,假设方法的调用者知道并记得对特殊值进行判断。然而,Swift的可选类型可以让你暗示任意类型的值缺失,并不需要一个特殊值。
举一个例子,在Swift 的String类型中有一个叫做toInt的方法,其功能是将一个String值转换成一个 Int 值。然而,并不是所有的字符串都可以转换成一个整数。字符串“123”可以被转换成数字123,但是字符串“hello,world”不行。
下面的演示代码使用toInt方法来尝试将一个String转换成Int。
let possibleNumber = "123" let convertedNumber = possibleNumber.toInt() // convertedNumber 被推测为类型 "Int?",或者类型 "optional Int"
因为 toInt 方法可能会失败,所以,它返回一个可选类型(optional)Int,而不是一个 Int。一个可选的Int被写作Int?而不是Int。问号暗示包含的值是可选类型,也就是说可能包含Int值也可能不包含值(不能包含其他任何值,如Bool值或者String值,只能是Int或者什么都没有)。
3.13.1 if 语句以及强制解析
在Swift语言中,可以使用if语句来判断一个可选是否包含值。如果可选类型有值,结果是true;如果没有值,结果是false。
在Swift语言中,当确定可选类型确实包含值之后,可以在可选的名字后面加一个感叹号“!”来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping)。
实例文件main.swift的具体实现代码如下所示。
import Foundation let possibleNumber = "123" let convertedNumber = possibleNumber.toInt() if convertedNumber { println("\(possibleNumber) has an integer value of \(convertedNumber!)") } else { println("\(possibleNumber) could not be converted to an integer") }
本实例执行后的效果如图3-16所示。
图3-16 执行效果
注意
可使用“!”来获取一个不存在的可选值会导致运行时错误。使用“!”来强制解析值之前,一定要确定可选包含一个非nil的值。
3.13.2 可选绑定
在Swift语言中,使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在if和while语句中来对可选类型的值进行判断,并把值赋给一个常量或者变量。if和while语句请参考控制流。
例如,在如下所示的if语句中写一个可选绑定。
if let constantName = someOptional { statements }
在Swift语言中,可以像上面这样使用可选绑定的方式来重写possibleNumber这个例子,例如,如下所示的演示代码。
实例文件main.swift的具体实现代码如下所示。
import Foundation let possibleNumber = "123" let convertedNumber = possibleNumber.toInt() if let actualNumber = possibleNumber.toInt() { println("\(possibleNumber) has an integer value of \(actualNumber)") } else { println("\(possibleNumber) could not be converted to an integer") }
本实例执行后的效果如图3-17所示。
图3-17 执行效果
上述代码可以被理解为:如果possibleNumber.toInt返回的可选Int包含一个值,创建一个叫做actualNumber的新常量,并将可选包含的值赋给它。
在Swift语言中,如果转换成功,常量actualNumber可以在if语句的第一个分支中使用。它已经被可选类型包含的值初始化过,所以,不需要再使用“!”后缀来获取它的值。在这个例子中, actualNumber只被用来输出转换结果。
在 Swift 语言中,可以在可选绑定中使用常量和变量。如果想在 if 语句的第一个分支中操作actualNumber的值,可以改成if var actualNumber,这样可选类型包含的值就会被赋给一个变量而非常量。
3.13.3 nil
在Swift语言中,可以给可选变量赋值为nil来表示它没有值,例如,如下所示的演示代码。
var serverResponseCode: Int? = 404 // serverResponseCode 包含一个可选的 Int 值 404 serverResponseCode = nil // serverResponseCode 现在不包含值
在Swift语言中,nil不能用于非可选的常量和变量,如果代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
在Swift语言中,如果声明一个可选常量或者变量但是没有赋值,它们会自动被设置为nil。例如,如下所示的演示代码。
var surveyAnswer: String? // surveyAnswer 被自动设置为 nil
注意
Swift的nil和Objective-C中的nil并不一样。在Objective-C中,nil是一个指向不存在对象的指针。在 Swift 中,nil 不是指针,它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为nil,不只是对象类型。
3.13.4 隐式解析可选类型
在Swift语言中,可选类型暗示了常量或者变量可以“没有值”。可选可以通过if语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。
在Swift语言中,在程序架构中第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(String?)改成感叹号(String!)来声明一个隐式解析可选类型。
在Swift语言中,当可选类型被第一次赋值之后,就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考类实例之间的循环强引用。
在Swift语言中,一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。例如,在下面的例子中,展示了可选类型String和隐式解析可选类型String之间的区别。
let possibleString: String? = "An optional string." println(possibleString!) // 需要惊叹号来获取值 // 输出 "An optional string." let assumedString: String! = "An implicitly unwrapped optional string." println(assumedString) // 不需要感叹号 // 输出 "An implicitly unwrapped optional string."
在Swift语言中,可以把隐式解析可选类型当做一个可以自动解析的可选类型,开发者要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。
注意
如果在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和在没有值的普通可选类型后面加一个惊叹号“!”一样。
在Swift语言中,可以把隐式解析可选类型当做普通可选类型来判断它是否包含值,例如,如下所示的演示代码。
if assumedString { println(assumedString) } // 输出 "An implicitly unwrapped optional string."
在Swift语言中,也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值,例如,如下所示的演示代码。
if let definiteString = assumedString { println(definiteString) } // 输出 "An implicitly unwrapped optional string."
注意
如果一个变量之后可能变成nil的话,请不要使用隐式解析可选类型。如果需要在变量的生命周期中判断是否是nil的话,请使用普通可选类型。
3.14 断言
在Swift语言中,可选类型可以让我们判断值是否存在,可以在代码中优雅地处理值缺失的情况。但是在某些情况下,如果值缺失或者值并不满足特定的条件,代码可能并不需要继续执行。此时可以在代码中触发一个断言(assertion)来结束代码运行,并通过调试来找到值缺失的原因。
3.14.1 使用断言进行调试
在Swift语言中,断言会在运行时判断一个逻辑条件是否为true。从字面意思来说,“断言”一个条件是否为真。你可以使用断言来保证在运行其他代码之前,某些重要的条件已经被满足。如果条件判断为true,代码运行会继续进行;如果条件判断为false,代码运行停止,你的应用被终止。
在 Swift 语言中,如果在代码调试环境下触发了一个断言,比如在 Xcode 中构建并运行一个应用,则可以清楚地看到不合法的状态发生在哪里,并检查断言被触发时你的应用的状态。此外,断言允许开发者附加一条调试信息。
在Swift语言中,可以使用全局assert函数来写一个断言,例如,向assert函数传入一个结果为true或者false的表达式以及一条信息,当表达式为false的时候这条信息会被显示。
let age = -3 assert(age >= 0, "A person's age cannot be less than zero") // 因为 age < 0,所以断言会触发
在上述例子中,只有age >= 0为true的时候代码运行才会继续,也就是说,当age的值非负的时候。如果age的值是负数,就像代码中那样,age >= 0为false,断言被触发,结束应用。
在Swift语言中,断言信息不能使用字符串插值。断言信息可以省略,就像如下所示的演示代码。
assert(age >= 0)
3.14.2 何时使用断言
当条件可能为假时使用断言,但是最终一定要保证条件为真,这样你的代码才能继续运行。断言的适用情景。
□ 整数类型的下标索引被传入一个自定义下标脚本实现,但是下标索引值可能太小或者太大。
□ 需要给函数传入一个值,但是非法的值可能导致函数不能正常执行。
□ 一个可选值现在是nil,但是后面的代码运行需要一个非nil值。
断言可能导致应用程序终止运行,所以,开发者需要仔细设计代码,避免出现非法条件。然而,在发布应用程序之前,有时候可能会出现非法条件,这时使用断言可以快速发现问题。
实例文件main.swift的具体实现代码如下所示。
import Cocoa var str = "Hello, playground" //声明多个变量或常量的话中间用 "," 隔开;注意:要在同一行 var x = 0.0 , y = 1.0 , z = 2.0 let x1 = 0.0 , y1 = 1.0 , z1 = 2.0 //指定类型声明变量或常量;注意:声明常量的时候要指定值 var a:String a = "Hello" var b:String = "Hello" let a1:String = "Hello" //参数的命名规则,可以几乎用任何你想用的名字去命名参数;注意:不能用数字命名,不能"-"等数学符号等
let 我 = "Hello" let = "Hello" //打印常量或变量 注意:\(t) t是想打印的任何变量和常量 println("\(我),\(),\(a1)") //在一句话的结尾,Swift是不允许写分号的,注意:一行中写多条语句的时候是需要用分号的! println("\(我),\(),\(a1)");println("\(我)") //强制类型转换必须显示转换 let three = 3 let pointOneFourOneFiveNine = 0.14159 let pi = Double(three) + pointOneFourOneFiveNine let integerPi = Int(pi) //typealias的应用,将类型指定为想要的名字 typealias AudioSample = UInt16 //Bool 类型,注意:ture和false来指明 let orangesAreOrange = true let turnipsAreDelicious = false /* 元组(Tuples) 1:你可以定义任何类型、任何数目 2:如果你只想要元组的一部分,你用"_"当占位符 3:你可以通过元组的.0,..来取得对应的值 4:你可以元组中的元素命名,读取的时候直接用名字读取 5:当然你可以用元组来作为一个函数的返回值 */ let http404Error = (404, "Not Found") let (statusCode, statusMessage) = http404Error let (statusCode1,_) = http404Error println("\(statusCode1),\(http404Error.0),\(http404Error.1)") let http200Status = (statusCode: 200, description: "OK") println("\(http200Status.statusCode),\(http200Status.description)") /* 1:在swift中有?keyword,这个的意思是,值可能有可能没有。Int和Int?是两个不同的概念 2:在swift中有!keyword,这个的意思是,值确定有。Int和Int!是两个不同的概念。nil不能赋值给“!”声明的变量或常量 3:在swift中nil可以赋值给任何变量和常量,不仅仅是对象才可以 */ var nonOptional: Int? = nil if var asdf = nonOptional{ println("存在") }else{ println("不存在") }
本实例执行后的效果如图3-18所示。
图3-18 执行效果
实例文件main.swift的具体实现代码如下所示。
import Foundation ///////////////////////////////// + ////////////////////////////// // make Int + Double = Double derectly @infix func +(lhsParam: Int, rhsParam: Double) -> Double { return Double(lhsParam) + rhsParam } // make Double + Int = Double derectly @infix func +(lhsParam: Double, rhsParam: Int) -> Double { return lhsParam + Double(rhsParam) } // make Int + Float = Float derectly @infix func +(lhsParam: Int, rhsParam: Float) -> Float { return Float(lhsParam) + rhsParam } // make Float + Int = Float derectly @infix func +(lhsParam: Float, rhsParam: Int) -> Float { return lhsParam + Float(rhsParam) } // make Double + Float = Double derectly @infix func +(lhsParam: Double, rhsParam: Float) -> Double { return lhsParam + Double(rhsParam) } // make Float + Double = Double derectly @infix func +(lhsParam: Float, rhsParam: Double) -> Double { return Double(lhsParam) + rhsParam } ///////////////////////////////// - ////////////////////////////// // make Int - Double = Double derectly @infix func -(lhsParam: Int, rhsParam: Double) -> Double { return Double(lhsParam) - rhsParam } // make Double - Int = Double derectly
@infix func -(lhsParam: Double, rhsParam: Int) -> Double { return lhsParam - Double(rhsParam) } // make Int - Float = Float derectly @infix func -(lhsParam: Int, rhsParam: Float) -> Float { return Float(lhsParam) - rhsParam } // make Float - Int = Float derectly @infix func -(lhsParam: Float, rhsParam: Int) -> Float { return lhsParam - Float(rhsParam) } // make Double - Float = Double derectly @infix func -(lhsParam: Double, rhsParam: Float) -> Double { return lhsParam - Double(rhsParam) } // make Float - Double = Double derectly @infix func -(lhsParam: Float, rhsParam: Double) -> Double { return Float(lhsParam) - rhsParam } ///////////////////////////////// * ////////////////////////////// // make Int * Double = Double derectly @infix func *(lhsParam: Int, rhsParam: Double) -> Double { return Double(lhsParam) * rhsParam } // make Double * Int = Double derectly @infix func *(lhsParam: Double, rhsParam: Int) -> Double { return lhsParam * Double(rhsParam) } // make Int * Float = Float derectly @infix func *(lhsParam: Int, rhsParam: Float) -> Float { return Float(lhsParam) * rhsParam } // make Float * Int = Float derectly @infix func *(lhsParam: Float, rhsParam: Int) -> Float { return lhsParam * Float(rhsParam) } // make Double * Float = Double derectly @infix func *(lhsParam: Double, rhsParam: Float) -> Double { return lhsParam * Double(rhsParam) } // make Float * Double = Double derectly @infix func *(lhsParam: Float, rhsParam: Double) -> Double { return Float(lhsParam) * rhsParam } ///////////////////////////////// / //////////////////////////////
// make Int / Double = Double derectly @infix func /(lhsParam: Int, rhsParam: Double) -> Double { return Double(lhsParam) / rhsParam } // make Double * Int = Double derectly @infix func /(lhsParam: Double, rhsParam: Int) -> Double { return lhsParam / Double(rhsParam) } // make Int / Float = Float derectly @infix func /(lhsParam: Int, rhsParam: Float) -> Float { return Float(lhsParam) / rhsParam } // make Float / Int = Float derectly @infix func /(lhsParam: Float, rhsParam: Int) -> Float { return lhsParam / Float(rhsParam) } // make Double / Float = Double derectly @infix func /(lhsParam: Double, rhsParam: Float) -> Double { return lhsParam / Double(rhsParam) } // make Float / Double = Double derectly @infix func /(lhsParam: Float, rhsParam: Double) -> Double { return Float(lhsParam) / rhsParam } println("test begin!") println(1 + 2.0) println(2.0 + 1) println(Float(1.0) + 2.0) println(1 - 2.0) println(1.0 - 2) println(1 * 2.0) println(1 / 2.0) println(1.0 / 2) println(1 / 2.0) println(1 % 2.0) println(1.0 % 2)
本实例执行后的效果如图3-19所示。
图3-19 执行效果