第3章 Swift语言基础
Swift是Apple公司在WWDC2014所发布的一门编程语言,用来编写OS X和iOS应用程序。苹果公司在设计Swift语言时,就有意将其和Objective-C共存,Objective-C是Apple操作系统在导入Swift前使用的编程语言。在本章的内容中,将带领大家初步认识Swift这门神奇的开发语言,为读者步入本书后面知识的学习打下基础。
3.1 Swift概述
Swift是一种为开发iOS和OS X应用程序而推出的全新编程语言,是建立在C语言和Objective-C语言基础之上的,并且没有C语言的兼容性限制。Swift采用安全模型的编程架构模式,并且使整个编程过程变得更容易、更灵活、更有趣。另外,Swift完全支持市面中的主流框架:Cocoa和Cocoa Touch框架,这为开发人员重新构建软件和提高开发效率带来了巨大的帮助。在本节的内容中,将带领大家一起探寻Swift的诞生历程。
3.1.1 Swift的创造者
苹果Swift语言的创造者是苹果开发者工具部门总监Chris Lattner(1978年出生),Chris Lattner是 LLVM 项目的主要发起人与作者之一,Clang 编译器的作者。Chris Lattner开发了 LLVM,一种用于优化编译器的基础框架,能将高级语言转换为机器语言。LLVM极大提高了高级语言的效率,Chris Lattner也因此获得了首届SIGPLAN奖。
2005年,Chris加入LLVM开发团队,正式成为苹果的一名员工。在苹果的 9 年间,他由一名架构师一路升职为苹果开发者工具部门总监。目前,Chris Lattner主要负责 Xcode项目,这也为Swift的开发提供了灵感。
Chris Lattner从2010年7月才开始开发Swift语言,当时它在苹果内部属于机密项目,只有很少人知道这一语言的存在。Chris Lattner个人博客上称,Swift 的底层架构大多是他自己开发完成的。2011 年,其他工程师开始参与项目开发,Swift 也逐渐获得苹果内部重视,直到2013年成为苹果主推的开发工具。
Swift 的开发结合了众多工程师的心血,包括语言专家、编译器优化专家等,苹果其他团队也为改进产品提供了很大帮助。同时Swift也借鉴了其他语言的优点,如Objective-C、Rust、Ruby等。
Swift语言的核心吸引力在于Xcode Playgrounds 功能和REPL,它们使开发过程具有更好交互性,也更容易上手。Playgrounds在很大程度上受到了Bret Victor的理念和其他互动系统的启发。同样,具有实时预览功能的 Swift 使编程变得简单,学习起来也更加容易,目前已经引起了开发者的极大兴趣。这有助于苹果吸引更多的开发者,甚至将改变计算机科学的教学方式。图3-1是Chris Lattner在WWDC14大会上对Swift进行演示。
图3-1 Chris Lattner在WWDC14大会上对Swift进行演示
3.1.2 Swift的优势
在WWDC14大会中,苹果公司推出的一款全新的开发语言Swift。在演示过程中,苹果展示了如何能让开发人员更快进行代码编写及显示结果的“Swift Playground”,在左侧输入代码的同时,可以在右侧实时显示结果。苹果公司表示Swift是基于Cocoa和Cocoa Touch而专门设计的。Swift不仅可以用于基本的应用程序编写,比如各种社交网络App,同时还可以使用更先进的“Metal”3D游戏图形优化工作。由于Swift可以与Objective-C兼容使用,因此,开发人员可以在开发过程中进行无缝切换。
(1)易学。
作为一项苹果独立发布的支持型开发语言,Swift语言的语法内容混合了Objective-C、JS和Python,其语法简单、使用方便、易学,大大降低了开发者入门的门槛。同时Swift语言可以与Objective-C混合使用,对于用惯了高难度Objective-C语言的开发者来说,Swift语言更加易学。
(2)功能强大。
Swift允许开发者通过更简洁的代码来实现更多的内容。在WWDC2014发布会上,苹果演示了如何只通过一行简单的代码,完成一个完整图片列表加载的过程。另外,Swift还可以让开发人员一边编写程序,一边预览自己的应用程序,从而快速测试应用在某些特殊情况下的反应。
(3)提升性能。
对开发者来说Swift语言可以提升性能,同时降低开发难度,没有开发者不喜欢这样的编程语言。
(4)简洁、精良、高效。
Swift是一种非常简洁的语言。与Python类似,不必编写大量代码即可实现强大的功能,并且也有利于提高应用开发速度。Swift可以更快捷有效地编译出高质量的应用程序。
(5)执行速度快。
Swift的执行速度比Objective-C应用更快,这样会在游戏中看见更引人入胜的画面(需要苹果新的Metal界面的帮助),而其他应用也会有更好的响应性。与此同时,消费者不用购买新手机即可体验到这些效果。
(6)全面融合。
苹果对全新的Swift语言的代码进行了大量简化,在更快、更安全、更好的交互、更现代的同时,开发者们可以在同一款软件中同时用Objective-C、Swift、C 3种语言,这样便实现了3类开发人员的完美融合。
(7)测试工作更加便捷。
方便快捷地测试所编写应用将帮助开发者更快地开发出复杂应用。以往,对规模较大的应用来说,编译和测试过程极为冗繁。如果Swift能在这一方面带来较大的改进,那么应用开发者将可以更快地发布经过更彻底测试的应用。
当然,Swift还有一些不足之处。其中Swift最大的问题在于,要求使用者学习一门全新的语言。程序员通常喜欢掌握最新、最优秀的语言,但关于如何指导人们编写iPhone应用,目前已形成了完整的产业。在苹果发布Swift之后,所有一切都要被推翻重来。
3.2 数据类型和常量
Swift语言的基本数据类型是int,例如,声明为int类型的变量只能用于保存整型值,也就是说没有小数位的值。其实除了int类型之外,在Swift中还有另外3种基本数据类型,分别是float、double和char,具体说明如下所示。
float:用于存储浮点数(即包含小数位的值)。
double:和float类型一样,但是前者的精度约是后者精度的两倍。
char:可以存储单个字符,例如字母a、数字字符100,或者一个分号“;”。
在Swift程序中,任何数字、单个字符或者字符串通常被称为常量。例如,数字88表示一个常量整数值。字符串@“Programming in Swift”表示一个常量字符串对象。在Swift程序中,完全由常量值组成的表达式被称为常量表达式。例如下面的表达式就是一个常量表达式,因为,此表达式的每一项都是常量值:
128 + 1 - 2
如果将i声明为整型变量,那么下面的表达式就不是一个常量表达式:
128 + 1 – i
在Swift中定义了多个简单(或基本)的数据类型,例如int表示整数类型,这就是一种简单的数据类型,而不是复杂的对象。
3.2.1 int类型
在Swift程序中,整数常量由一个或多个数字的序列组成。序列前的负号表示该值是一个负数,例如值88、−10和100都是合法的整数常量。Swift规定,在数字中间不能插入空格,并且不能用逗号来表示大于999的值。所以数值“12,00”就是一个非法的整数常量,如果写成“1200”就是正确的。
如果整型常量以0和字母x(无论是小写字母还是大写字母)开头,那么这个值都将用十六进制(以16为基数)计数法来表示。紧跟在字母x后的是十六进制值的数字,它可以由0到9之间的数字和a到f(或A到F)之间的字母组成。字母表示的数字分别为10到15。假如要给名为RGBColor的整型常量指派十六进制的值FFEF0D,则可以使用如下代码实现:
RGBColor = 0xFFEF0D;
在上述代码中,符号“%x”用十六进制格式显示一个值,该值不带前导的0x,并用a到f之间的小写字符表示十六进制数字。要使用前导0x显示这个值,需要使用格式字符%#x的帮助。
3.2.2 float类型
在Swift程序中,float类型变量可以存储小数位的值。由此可见,通过查看是否包含小数点的方法可以区分出是否是一个浮点常量。在Swift程序中,不但可以省略小数点之前的数字,而且也可以省略之后的数字,但是不能将它们全部省略。
另外,在Swift程序中也能使用科学计数法来表示浮点常量。例如“1.5e4”就是使用这种计数法来表示的浮点值,它表示值1.5×10−4。位于字母e前的值称为尾数,而之后的值称为指数。指数前面可以放置正号或负号,指数表示将与尾数相乘的10的幂。因此,在常量2.85e-3中,2.85是尾数值,而−3是指数值。该常量表示值2.85×10−3,或0.00285。另外,在Swift程序中,不但可用大写字母书写用于分隔尾数和指数的字母e,而且也可以用小写字母来书写。
3.2.3 double类型
在Swift程序中,类型double与类型float类似。Swift规定,当在float变量中所提供的值域不能满足要求时,需要使用double变量来实现需求。声明为double类型的变量可以存储的位数,大概是float变量所存储的两倍多。在现实应用中,大多数计算机使用64位来表示double值。除非另有特殊说明,否则Swift编译器将全部浮点常量当作double值来对待。要想清楚地表示float常量,需要在数字的尾部添加字符f或F,例如:
12.4f
要想显示double的值,可以使用格式符号%f、%e或%g来辅助实现,它们与显示float值所用的格式符号是相同的。
3.2.4 char类型
在Swift程序中,char类型变量的功能是存储单个字符,只要将字符放到一对单引号中就能得到字符常量。例如‘a’、‘;’和‘0’都是合法的字符常量。其中‘a’表示字母a,‘;’表示分号,‘0’表示字符0(并不等同于数字0)。
3.2.5 字符常量
在Swift程序中,字符常量是用单引号括起来的一个字符,例如下面列出的都是合法字符常量:
'a'
'b'
'='
'+'
'?'
Swift的字符常量具有以下3个特点。
(1)字符常量只能用单引号括起来,不能用双引号或其他括号。
(2)字符常量只能是单个字符,不能是字符串,转义字符除外。
(3)字符可以是字符集中任意字符。但数字被定义为字符型之后就不能参与数值运算。如'5'和5是不同的。“5”是字符常量,不能参与运算。
3.3 变量和常量
Swift语言中的基本数据类型,按其取值可以分为常量和变量两种。在程序执行过程中,其值不发生改变的量称为常量,其值可变的量称为变量。两者可以和数据类型结合起来进行分类,例如可以分为整型常量、整型变量、浮点常量、浮点变量、字符常量、字符变量、枚举常量、枚举变量。
3.3.1 常量详解
在执行程序的过程中,其值不发生改变的量称为常量。在Swift语言中,使用关键字“let”来定义常量,如下所示的演示代码:
let mm = 70
let name = guanxijing
let height = 170.0
在上述代码中定义了3个常量,常量名分别是“mm”、“name”和“height”。
在Swift程序中,常量的值无需在编译时指定,但是至少要赋值一次。这表示可以使用常量来命名一个值,只需进行一次确定工作,就可以将这个常量用在多个地方。
如果初始化值没有提供足够的信息(或没有初始化值),可以在变量名后写类型,并且以冒号分隔。例如下面的演示代码:
let imlicitInteger = 50
let imlicitDouble = 50.0
let explicitDouble: Double = 50
在Swift程序中,常量值永远不会隐含转换到其他类型。如果需要转换一个值到另外不同的类型,需要事先明确构造一个所需类型的实例,例如下面的演示代码:
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
在Swift程序中,可以使用简单的方法在字符串中以小括号来写一个值,或者用反斜线“\”放在小括号之前,例如下面的演示代码:
let apples = 3
let oranges = 5 //by gashero
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
3.3.2 变量详解
在Swift程序中,使用关键字“var”来定义变量,例如下面的演示代码:
var myVariable = 42
var name = "guan"
因为Swift程序中的变量和常量必须与赋值时拥有相同的类型,所以无需严格定义变量的类型,只需提供一个值就可以创建常量或变量,并让编译器推断其类型。也就是说,Swift支持类型推导(TypeInference)功能,所以上面的代码不需指定类型。例如在上面例子中,编译器会推断myVariable是一个整数类型,因为其初始化值就是个整数。如果要为上述变量指定一个类型,则可以通过如下代码实现:
var myVariable : Double= 42
在Swift程序中,使用如下所示的形式进行字符串格式化:
\(item)
例如下面的演示代码:
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
另外,在Swift程序中使用方括号“[]”创建一个数组和字典,接下来就可以通过方括号中的索引或键值来访问数组和字典中的元素,例如下面的演示代码:
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
var occupations = [ "Malcolm": "Captain", "Kaylee": "Mechanic", ]
occupations["Jayne"] = "Public Relations"
在Swift程序中,创建一个空的数组或字典的初始化格式如下所示:
let emptyArray = String[]()
let emptyDictionary = Dictionary<String, Float>()
如果无法推断数组或字典的类型信息,可以写为空的数组格式“[]”或空的字典格式“[:]”。
另外,为了简化代码的编写工作量,可以在同一行语句中声明多个常量或变量,在变量之间以逗号进行分隔,例如下面的演示代码:
var x = 0.0, y = 0.0, z = 0.0
3.4 字符串和字符
在Swift程序中,String是一个有序的字符集合,例如“hello、world”、“albatross”。Swift字符串通过String类型来表示,也可以表示为Character类型值的集合。在Swift程序中,通过String和Character 类型提供了一个快速的、兼容Unicode的方式来处理代码中的文本信息。
在Swift程序中,创建和操作字符串的方法与在C中的操作方式相似,轻量并且易读。字符串连接操作只需要简单地通过“+”号将两个字符串相连即可。与Swift中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。
尽管Swift的语法简易,但是,String类型是一种快速、现代化的字符串实现。每一个字符串都是由独立编码的 Unicode 字符组成,并提供了用于访问这些字符在不同的Unicode表示的支持。在Swift程序中,String 也可以用于在常量、变量、字面量和表达式中进行字符串插值,这将更加方便地实现展示、存储和打印的字符串工作。
在Swift应用程序中,String 类型与 Foundation NSString 类进行了无缝桥接。如果开发者想利用 Cocoa 或 Cocoa Touch 中的 Foundation 框架实现功能,整个 NSString API 都可以调用创建的任意 String 类型的值,并且额外还可以在任意 API 中使用本节介绍的 String 特性。另外,也可以在任意要求传入NSString实例作为参数的API中使用String类型的值进行替换。
3.4.1 字符串字面量
在Swift应用程序中,可以在编写的代码中包含一段预定义的字符串值作为字符串字面量。字符串字面量是由双引号包裹着的具有固定顺序的文本字符集。Swift中的字符串字面量可以用于为常量和变量提供初始值,例如下面的演示代码:
let someString = "Some string literal value"
在上述代码中,变量someString通过字符串字面量进行初始化,所以Swift可以推断出变量someString的类型为String。
在Swift应用程序中,字符串字面量可以包含以下特殊字符。
转移特殊字符 \0 (空字符)、\\(反斜线)、\t (水平制表符)、\n (换行符)、\r (回车符)、\" (双引号)、\' (单引号)。
单字节Unicode标量,写成\xnn,其中nn为两位十六进制数。
双字节Unicode标量,写成\unnnn,其中nnnn为4位十六进制数。
四字节Unicode标量,写成\Unnnnnnnn,其中nnnnnnnn为8位十六进制数。
例如在下面的代码中,演示了各种特殊字符的使用实例:
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imagination is more important than knowledge" - Einstein
let dollarSign = "\x24" // $, Unicode scalar U+0024
let blackHeart = "\u2665" // ♥, Unicode scalar U+2665
let sparklingHeart = "\U0001F496" // , Unicode scalar U+1F496
在上述代码中,常量wiseWords包含了两个转移特殊字符(双括号),常量dollarSign、blackHeart 和sparklingHeart演示了3种不同格式的Unicode标量。
3.4.2 初始化空字符串
为了在Swift应用程序中构造一个很长的字符串,可以创建一个空字符串作为初始值,也可以将空的字符串字面量赋值给变量,也可以初始化一个新的 String实例,例如下面的演示代码:
var emptyString = "" // empty string literal
var anotherEmptyString = String() // initializer syntax
在上述代码中,因为这两个字符串都为空,所以两者等价。通过如下所示的演示代码,可以通过检查其 Boolean 类型的 isEmpty 属性来判断该字符串是否为空:
if emptyString.isEmpty {
println("Nothing to see here")
}
// 打印输出 "Nothing to see here"
3.4.3 字符串可变性
在Swift应用程序中,可以通过将一个特定字符串分配给一个变量的方式来对其进行修改,或者分配给一个常量来保证其不会被修改,例如下面的演示代码:
var variableString = "Horse"
variableString += " and carriage"
// variableString 现在为 "Horse and carriage"
let constantString = "Highlander"
constantString += " and another Highlander"
上述代码会输出一个编译错误(compile-time error),提示我们常量不可以被修改。
其实在Objective-C和Cocoa中,可以通过选择两个不同的类(NSString和NSMutableString)来指定该字符串是否可以被修改。验证Swift程序中的字符串是否可以修改,是通过定义的是变量还是常量来决定的,这样实现了多种类型可变性操作的统一。
3.4.4 值类型字符串
在Swift应用程序中,String类型是一个值类型。如果创建了一个新的字符串,那么当其进行常量、变量赋值操作或在函数/方法中传递时,会进行值复制。在任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值。值类型在 Structures and Enumerations Are Value Types 中进行了说明。
其 Cocoa 中的 NSString 不同,当在 Cocoa 中创建了一个NSString实例,并将其传递给一个函数/方法,或者赋值给一个变量,您永远都是传递或赋值同一个NSString实例的一个引用。除非特别要求其进行值复制,否则字符串不会进行赋值新副本操作。
Swift 默认字符串复制的方式保证了在函数/方法中传递的是字符串的值,其明确指出无论该值来自何处,都是它独自拥有的,可以放心传递的字符串本身的值而不会被更改。
在实际编译时,Swift编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您始终可以将字符串作为值类型的同时获得极高的性能。
Swift程序的 String类型表示特定序列的字符值的集合,每一个字符值代表一个 Unicode字符,可以利用 “for-in”循环来遍历字符串中的每一个字符,例如下面的演示代码:
for character in "Dog! " {
println(character)
}
执行上述代码后会输出:
D
o
g
!
另外,通过标明一个 Character 类型注解并通过字符字面量进行赋值,可以建立一个独立的字符常量或变量,例如下面的演示代码:
let yenSign: Character = "¥"
3.4.5 计算字符数量
在Swift应用程序中通过调用全局函数countElements,并将字符串作为参数进行传递的方式可以获取该字符串的字符数量,例如下面的演示代码:
let unusualMenagerie = "Koala , Snail , Penguin , Dromedary "
println("unusualMenagerie has \(countElements(unusualMenagerie)) characters")
// prints "unusualMenagerie has 40 characters"
不同的 Unicode 字符以及相同 Unicode 字符的不同表示方式,因为可能需要不同数量的内存空间来存储,所以,Swift 中的字符在一个字符串中并不一定占用相同的内存空间。由此可见,字符串的长度不得不通过迭代字符串中每一个字符的长度来进行计算。如果正在处理一个长字符串,则需要注意函数countElements必须遍历字符串中的字符以精准计算字符串的长度。
另外需要注意的是,通过 countElements 返回的字符数量并不总是与包含相同字符的 NSString 的 length 属性相同。NSString 的属性length是基于利用 UTF-16 表示的十六位代码单元数字,而不是基于 Unicode 字符。为了解决这个问题,NSString 的属性length在被 Swift的 String 访问时会成为 utf16count。
3.4.6 连接字符串和字符
在Swift应用程序中,字符串和字符的值可以通过加法运算符“+”相加在一起,并创建一个新的字符串值,例如下面的演示代码:
let string1 = "hello"
let string2 = " there"
let character1: Character = "!"
let character2: Character = "?"
let stringPlusCharacter = string1 + character1 // 等于 "hello!"
let stringPlusString = string1 + string2 // 等于 "hello there"
let characterPlusString = character1 + string1 // 等于 "!hello"
let characterPlusCharacter = character1 + character2 // 等于 "!?"
另外,也可以通过加法赋值运算符“+=”将一个字符串或者字符添加到一个已经存在字符串变量上,例如下面的演示代码:
var instruction = "look over"
instruction += string2
// instruction 现在等于 "look over there"
var welcome = "good morning"
welcome += character1
// welcome 现在等于 "good morning!"
注意:不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
3.4.7 字符串插值
在Swift应用程序中,字符串插值是一种全新的构建字符串的方式,可以在其中包含常量、变量、字面量和表达式。其中插入的字符串字面量中的每一项,都会被包裹在以反斜线为前缀的圆括号中,例如下面的演示代码:
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"
在上面的演示代码中,multiplier作为 \(multiplier) 被插入到一个字符串字面量中。当创建字符串执行插值计算时此占位符会被替换为multiplier实际的值。multiplier 的值也作为字符串中后面表达式的一部分。该表达式计算 Double(multiplier) * 2.5 的值并将结果 (7.5) 插入到字符串中。在这个例子中,表达式写为 \(Double(multiplier) * 2.5) 并包含在字符串字面量中。
注意:插值字符串中写在括号中的表达式不能包含非转义双引号“"”和反斜杠“\”,并且不能包含回车或换行符。
3.4.8 比较字符串
在Swift应用程序中提供了3种方式来比较字符串的值,分别是字符串相等、前缀相等和后缀相等。
(1)字符串相等。
如果两个字符串以同一顺序包含完全相同的字符,则认为两者字符串相等,例如下面的演示代码:
let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
println("These two strings are considered equal")
}
执行上述代码后会输出:
"These two strings are considered equal"
(2)前缀/后缀相等。
通过调用字符串的 hasPrefix/hasSuffix 方法来检查字符串是否拥有特定前缀/后缀。两个方法均需要以字符串作为参数传入并传出 Boolean 值。两个方法均执行基本字符串和前缀/后缀字符串之间逐个字符的比较操作。例如,在下面的演示代码中,以一个字符串数组表示莎士比亚话剧罗密欧与朱丽叶中前两场的场景位置:
let romeoAndJuliet = [
"Act 1 Scene 1: Verona, A public place",
"Act 1 Scene 2: Capulet's mansion",
"Act 1 Scene 3: A room in Capulet's mansion",
"Act 1 Scene 4: A street outside Capulet's mansion",
"Act 1 Scene 5: The Great Hall in Capulet's mansion",
"Act 2 Scene 1: Outside Capulet's mansion",
"Act 2 Scene 2: Capulet's orchard",
"Act 2 Scene 3: Outside Friar Lawrence's cell",
"Act 2 Scene 4: A street in Verona",
"Act 2 Scene 5: Capulet's mansion",
"Act 2 Scene 6: Friar Lawrence's cell"
]
此时可以利用 hasPrefix 方法来计算话剧中第一幕的场景数,演示代码如下所示:
var act1SceneCount = 0
for scene in romeoAndJuliet {
if scene.hasPrefix("Act 1 ") {
++act1SceneCount
}
}
println("There are \(act1SceneCount) scenes in Act 1")
执行上述代码后会输出:
"There are 5 scenes in Act 1"
(3)大写和小写字符串。
可以通过字符串的 uppercaseString 和 lowercaseString 属性来访问一个字符串的大写/小写版本,例如下面的演示代码:
let normal = "Could you help me, please?"
let shouty = normal.uppercaseString
// shouty 值为 "COULD YOU HELP ME, PLEASE?"
let whispered = normal.lowercaseString
// whispered 值为 "could you help me, please?"
3.4.9 Unicode
Unicode 是文本编码和表示的国际标准,通过Unicode可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。
Swift语言中的字符串和字符类型是完全兼容 Unicode 的,它支持如下所述的一系列不同的 Unicode编码。
(1)Unicode的术语。
Unicode 中每一个字符都可以被解释为一个或多个unicode标量。字符的unicode标量是一个唯一的21位数字(和名称),例如U+0061表示小写的拉丁字母A ("a")。
当Unicode字符串被写进文本文件或其他存储结构当中,这些unicode 标量将会按照Unicode定义的集中格式之一进行编码。其包括UTF-8(以8位代码单元进行编码)和UTF-16(以16位代码单元进行编码)。
(2)Unicode 表示字符串。
Swift 提供了几种不同的方式来访问字符串的Unicode表示。例如可以利用for-in来对字符串进行遍历,从而以Unicode字符的方式访问每一个字符值。该过程在Working with Characters中进行了描述。
另外,能够以如下3种Unicode兼容的方式访问字符串的值。
UTF-8代码单元集合(利用字符串的utf8属性进行访问)。
UTF-16代码单元集合(利用字符串的utf16属性进行访问)。
21位的Unicode标量值集合(利用字符串的unicodeScalars属性进行访问)。
例如在下面的演示代码中,由Dog!和“”(Unicode 标量为U+1F436)组成的字符串中的每一个字符代表着一种不同的表示:
let dogString = "Dog!"
(3)UTF-8。
可以通过遍历字符串的 utf8 属性来访问它的UTF-8表示,其为 UTF8View 类型的属性,UTF8View 是无符号8位(UInt8)值的集合,每一个UIn8都是一个字符的UTF-8表示,例如下面的演示代码:
for codeUnit in dogString.utf8 {
print("\(codeUnit) ")
}
print("\n")
执行上述代码后会输出:
68 111 103 33 240 159 144 182
在上述演示代码中,前4个10进制代码单元值(68, 111, 103, 33)代表了字符D o g和!,它们的UTF-8表示与ASCII表示相同。后4个代码单元值(240, 159, 144, 182)是狗脸表情的4位UTF-8表示。
(4)UTF-16。
可以通过遍历字符串的utf16属性来访问它的UTF-16表示。其为UTF16View类型的属性,UTF16View是无符号16位(UInt16)值的集合,每一个UInt16都是一个字符的UTF-16表示,例如下面的演示代码:
for codeUnit in dogString.utf16 {
print("\(codeUnit) ")
}
print("\n")
执行上述代码后会输出:
68 111 103 33 55357 56374
同样,前4个代码单元值(68, 111, 103, 33)代表了字符D o g和!,它们的UTF-16代码单元和 UTF-8完全相同。第五和第六个代码单元值(55357 and 56374)是狗脸表情字符的UTF-16表示。第一个值为U+D83D(十进制值为55357),第二个值为U+DC36(十进制值为56374)。
(5)Unicode标量(Scalars)。
可以通过遍历字符串的unicodeScalars属性来访问它的Unicode标量表示。其为UnicodeScalarView类型的属性,UnicodeScalarView是UnicodeScalar的集合。UnicodeScalar是21位的Unicode代码点。每一个UnicodeScalar拥有一个值属性,可以返回对应的21位数值,用UInt32 来表示,例如下面的演示代码:
for scalar in dogString.unicodeScalars {
print("\(scalar.value) ")
}
print("\n")
执行上述代码后会输出:
68 111 103 33 128054
同样,前4个代码单元值(68, 111, 103, 33)代表了字符Dog和!。第五位数值,128054,是一个十六进制1F436的十进制表示。其等同于狗脸表情的Unicode标量U+1F436。
作为查询字符值属性的一种替代方法,每个UnicodeScalar值也可以用来构建一个新的字符串值,比如在字符串插值中使用下面的代码:
for scalar in dogString.unicodeScalars {
println("\(scalar) ")
}
执行上述代码后会输出:
// D
// o
// g
// !
//
3.5 流程控制
在Swift程序中的语句是顺序执行的,除非由一个for、while、do-while、if、switch语句,或者是一个函数调用将流程导向到其他地方去做其他的事情。在Swift程序中,主要包含如下所示的流程控制语句的类型。
一条if语句能够根据一个表达式的真值来有条件地执行代码。
for、while和do-while语句用于构建循环。在循环中,重复地执行相同的语句或一组语句,直到满足一个条件为止。
switch语句根据一个整数表达式的算术值,来选择一组语句执行。
函数调用跳入到函数体中的代码。当该函数返回时,程序从函数调用之后的位置开始执行。
上面列出的控制语句将在本书后面的内容中进行详细介绍,在本章将首先讲解循环语句的基本知识。循环语句是指可以重复执行的一系列代码,Swift程序中的循环语句主要由以下3种语句组成:
for语句;
while语句;
do语句。
Swift的条件语句包含if和switch,循环语句包含for-in、for、while和do-while,循环/判断条件不需要括号,但循环/判断体(body)必需使用括号,例如下面的演示代码:
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
在Swift程序中,结合if和let,可以方便地处理可控变量(nullable variable)。对于空值,需要在类型声明后添加“?”,这样以显式标明该类型可以为空,例如下面的演示代码:
var optionalString: String? = "Hello"
optionalString == nil
var optionalName: String? = "John Appleseed"
var gretting = "Hello!"
if let name = optionalName {
gretting = "Hello, \(name)"
}
3.5.1 for循环(1)
for循环可以根据设置,重复执行一个代码块多次。Swift中提供了两种for循环方式。
for-in循环:对于数据范围、序列、集合等中的每一个元素,都执行一次;
for-condition-increment:一直执行,直到一个特定的条件满足,每一次循环执行,都会增加一次计数。
for-in循环。
例如下面的演示代码能够打印数得出了5的倍数序列的前5项。
for index in 1...5 {
println("\(index) times 5 is \(index * 5)")
}
//下面是输出的执行效果
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
在上述代码中,迭代的项目是一个数字序列,从1到5的闭区间,通过使用(…)来表示序列。index被赋值为1,然后执行循环体中的代码。在这种情况下,循环只有一条语句,也就是打印5的index倍数。在这条语句执行完毕后,index的值被更新为序列中的下一个数值2,println函数再次被调用,一直循环直到这个序列的结尾。
如果不需要序列中的每一个值,可以使用“_”来忽略它,这样仅仅只是使用循环体本身,例如下面的演示代码:
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
println("\(base) to the power of \(power) is \(answer)")
执行后输出:
"3 to the power of 10 is 59049"
通过上述代码计算了一个数的特定次方(在这个例子中是3的10次方)。连续的乘法从1(实际上是3的0次方)开始,依次累乘以3,由于使用的是半闭区间,从0开始到9的左闭右开区间,所以是执行10次。在循环的时候不需要知道实际执行到第一次了,而是要保证执行了正确的次数,因此,这里不需要index的值。
在上面的例子中,index在每一次循环开始前都已经被赋值,因此不需要在每次使用前对它进行定义。每次它都隐式地被定义,就像是使用了let关键词一样。注意index是一个常量。
在Swift程序中,for-in除了遍历数组也可以用来遍历字典:
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
largest
3.5.2 for循环(2)
Swift同样支持C语言样式的for循环,它也包括了一个条件语句和一个增量语句,具体格式如下所示:
for initialization; condition; increment {
statements
}
分号在这里用来分隔for循环的3个结构,和C语言一样,但是不需要用括号来包裹它们。上述for循环的执行过程是。
(1)当进入循环的时候,初始化语句首先被执行,设定好循环需要的变量或常量。
(2)测试条件语句,看是否满足继续循环的条件,只有在条件语句是true的时候才会继续执行,如果是false则会停止循环。
(3)在所有的循环体语句执行完毕后,增量语句执行,可能是对计数器的增加或者是减少,或者是其他的一些语句。然后返回步骤(2)继续执行。
例如下面的演示代码:
for var index = 0; index < 3; ++index {
println("index is \(index)")
}
//执行后输出下面的结果
// index is 0
// index is 1
// index is 2
for循环方式还可以被描述为如下所示的形式:
initialization
while condition {
statements
increment
}
在初始化语句中被定义(比如var index = 0)的常量和变量,只在for循环语句范围内有效。如果想要在循环执行之后继续使用,需要在循环开始之前就定义好,例如下面的演示代码:
var index: Int
for index = 0; index < 3; ++index {
println("index is \(index)")
}
//执行后输出下面的结果
// index is 0
// index is 1
// index is 2
println("The loop statements were executed \(index) times")
//执行后输出下面的结果
// prints "The loop statements were executed 3 times"
在此需要注意的是,在循环执行完毕之后index的值是3,而不是2。因为是在index增1之后,条件语句index < 3返回false,循环才终止,而这时,index已经为3了。
3.5.3 while循环
while循环执行一系列代码块,直到某个条件为false为止。这种循环最常用于循环的次数不确定的情况。Swift提供了两种while循环方式。
while循环:在每次循环开始前测试循环条件是否成立。
do-while循环:在每次循环之后测试循环条件是否成立。
(1)while循环。
while循环由一个条件语句开始,如果条件语句为true,一直执行,直到条件语句变为false,下面是一个while循环的一般形式:
while condition {
statements
}
(2)Do-while循环。
在do-while循环中,循环体中的语句会先被执行一次,然后才开始检测循环条件是否满足,下面是do-while循环的一般形式:
do {
statements
} while condition
例如下面的代码演示了while循环和do-while循环的用法:
var n = 2
while n < 100 {
n = n * 2
}
n
var m = 2
do {
m = m * 2
} while m < 100
m
3.6 条件语句
通常情况下我们都需要根据不同条件来执行不同语句。比如当错误发生的时候,执行一些错误信息的语句,告诉编程人员这个值是太大了还是太小了等。这里就需要用到条件语句。Swift语言提供了两种条件分支语句的方式,分别是if语句和switch语句。一般if语句比较常用,但是只能检测少量的条件情况。switch语句用于大量的条件可能发生时的条件语句。
3.6.1 if语句
在最基本的if语句中,条件语句只有一个,如果条件为true时,执行if语句块中的语句:
var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.")
}
执行上述代码后输出:
It's very cold. Consider wearing a scarf.
上面这个例子检测温度是不是比32华氏度(32华氏度是水的冰点,和摄氏度不一样)低,如果低的话就会输出一行语句。如果不低,则不会输出。if语句块是用大括号包含的部分。
当条件语句有多种可能时,就会用到else语句,当if为false时,else语句开始执行:
temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.")
} else {
println("It's not that cold. Wear a t-shirt.")
}
执行上述代码后输出:
It's not that cold. Wear a t-shirt.
在这种情况下,两个分支的其中一个一定会被执行。同样也可以有多个分支,多次使用if和else,例如下面的演示代码:
temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
println("It's really warm. Don't forget to wear sunscreen.")
} else {
println("It's not that cold. Wear a t-shirt.")
}
执行上述代码后会输出:
It's really warm. Don't forget to wear sunscreen.
在上述代码中出现了多个if出现,用来判断温度是太低还是太高,最后一个else表示的是温度不高不低的时候。
在Swift程序中可以省略掉else,例如下面的演示代码:
temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32 {
println("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
println("It's really warm. Don't forget to wear sunscreen.")
}
在上述代码中,温度不高不低的时候不会输入任何信息。
3.6.2 switch语句
在Swift程序中,switch语句考察一个值的多种可能性,将它与多个case相比较,从而决定执行哪一个分支的代码。switch语句和if语句不同的是,它还可以提供多种情况同时匹配时,执行多个语句块。
switch语句的一般结构是:
switch some value to consider {
case value 1:
respond to value 1
case value 2,
value 3:
respond to value 2 or 3
default:
otherwise, do something else
}
每个switch语句包含有多个case语句块,除了直接比较值以外,Swift还提供了多种更加复杂的模式匹配的方式来选择语句执行的分支。在switch语句中,每一个case分支都会被匹配和检测到,如果需要有一种情况包括所有case没有提到的条件,那么可以使用default关键词。注意,default关键词必须在所有case的最后。
例如在下面的演示代码中,使用switch语句来判断一个字符的类型:
let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
println("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
println("\(someCharacter) is a consonant")
default:
println("\(someCharacter) is not a vowel or a consonant")
}
执行上述代码后会输出:
e is a vowel
在上述代码中,首先看这个字符是不是元音字母,再检测是不是辅音字母。其他的情况都用default来匹配即可。
与C和Objective-C不同,Swift中的switch语句不会因为在case语句的结尾没有break就跳转到下一个case语句执行。switch语句只会执行匹配上的case里的语句,然后就会直接停止。这样可以让switch语句更加安全,因为很多时候编程人员都会忘记写break。
每一个case中都需要有可以执行的语句,例如下面的演示代码就是不正确的:
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a":
case "A":
println("The letter A")
default:
println("Not the letter A")
}
与C语言不同,switch语句不会同时匹配a和A,它会直接报错。一个case中可以有多个条件,用逗号“,”分隔即可:
switch some value to consider {
case value 1,
value 2:
statements
}
switch语句的case中可以匹配一个数值范围。
3.7 函数
函数是执行特定任务的代码自包含块。给定一个函数名称标识,当执行其任务时就可以用这个标识来进行“调用”。Swift的统一的功能语法足够灵活来表达任何东西,无论是甚至没有参数名称的简单的C风格的函数表达式,还是需要为每个本地参数和外部参数设置复杂名称的Objective-C语言风格的函数。参数提供默认值,以简化函数调用,并通过设置输入输出参数,在函数执行完成时修改传递的变量。Swift中的每个函数都有一个类型,包括函数的参数类型和返回类型。您可以方便地使用此类型像任何其他类型一样,这使得它很容易将函数作为参数传递给其他函数,甚至从函数中返回函数类型。函数也可以写在其他函数中,用来封装一个嵌套函数用以范围内有用的功能。
3.7.1 函数的声明与调用
当定义一个函数时,可以为其定义一个或多个命名,定义类型值作为函数的输入(称为参数),当该函数完成时将传回输出定义的类型(称为作为它的返回类型)。
每一个函数都有一个函数名,用来描述了函数执行的任务。要使用一个函数的功能时,你通过使用它的名称进行“调用”,并通过它的输入值(称为参数)来匹配函数的参数类型。一个函数的提供的参数必须始终以相同的顺序来作为函数参数列表。
例如在下面的演示代码中,被调用的函数greetingForPerson需要一个人的名字作为输入并返回一句问候给那个人:
func sayHello(personName: String) -> String {
let greeting = "Hello, " + personName + "!"
return greeting
}
所有这些信息都汇总到函数的定义中,并以func关键字为前缀。您指定的函数的返回类型是以箭头→(一个连字符后跟一个右尖括号)以及随后类型的名称作为返回的。该定义描述了函数的作用是什么,它期望接收什么,以及当它完成返回的结果是什么。该定义很容易让该函数可以让你在代码的其他地方以清晰、明确的方式来调用,例如下面的演示代码:
println(sayHello("Anna"))
// prints "Hello, Anna!"
println(sayHello("Brian"))
// prints "Hello, Brian!"
在上述代码中,通过括号内String类型参数值调用sayHello的函数,如sayHello("Anna")。由于该函数返回一个字符串值,sayHello的可以被包裹在一个println函数调用中来打印字符串,看看它的返回值。
在sayHello的函数体开始,定义了一个新的名为greeting的String常量,并将其设置加上personName个人姓名组成一句简单的问候消息。然后这个问候函数以关键字return来传回。只要问候函数被调用时,函数执行完毕时就会返回问候语的当前值。可以通过不同的输入值多次调用sayHello的函数。上面的演示代码显示了如果它以“Anna”为输入值,以“Brian”为输入值会发生什么。函数的返回在每种情况下都是量身定制的问候。
为了简化这个函数的主体,结合消息创建和return语句用一行来表示,演示代码如下所示:
func sayHello(personName: String) -> String {
return "Hello again, " + personName + "!"
}
println(sayHello("Anna"))
执行上述代码后会输出:
Hello again, Anna!"
3.7.2 函数的参数和返回值
在Swift程序中,函数的参数和返回值是非常具有灵活性的。你可以定义任何东西,无论是一个简单的仅有一个未命名的参数的函数,还是那种具有丰富的参数名称和不同的参数选项的复杂函数。
(1)多输入参数。
函数可以有多个输入参数,把它们写到函数的括号内,并用逗号加以分隔。例如下面的函数设置了一个开始和结束索引的一个半开区间,用来计算在范围内有多少元素包含:
func halfOpenRangeLength(start: Int, end: Int) -> Int {
return end - start
}
println(halfOpenRangeLength(1, 10))
执行上述代码后会输出:
9
(2)无参函数。
函数并没有要求一定要定义的输入参数。例如下面就是一个没有输入参数的函数,任何时候调用时它总是返回相同的字符串消息:
func sayHelloWorld() -> String {
return "hello, world"
}
println(sayHelloWorld())
执行上述代码后会输出:
hello, world
上述函数的定义在函数的名称后还需要括号,即使它不带任何参数。当函数被调用时函数名称也要跟着一对空括号。
(3)没有返回值的函数。
函数也不需要定义一个返回类型,例如下面是一个版本的sayHello的函数,称为waveGoodbye,它会输出自己的字符串值而不是函数返回:
func sayGoodbye(personName: String) {
println("Goodbye, \(personName)!")
}
sayGoodbye("Dave")
执行上述代码后会输出:
Goodbye, Dave!
因为它并不需要返回一个值,该函数的定义不包括返回箭头和返回类型。
其实sayGoodbye功能确实还返回一个值,即使没有返回值定义。函数没有定义返回类型但返回了一个void返回类型的特殊值。它是一个值是空的元组,实际上零个元素的元组,可以写为()。当一个函数调用时它的返回值可以忽略不计:
func printAndCount(stringToPrint: String) -> Int {
println(stringToPrint)
return countElements(stringToPrint)
}
func printWithoutCounting(stringToPrint: String) {
printAndCount(stringToPrint)
}
printAndCount("hello, world")
// 打印输出"hello, world" and returns a value of 12
printWithoutCounting("hello, world")
// 打印输出 "hello, world" but does not return a value
在上述演示代码中,第一个函数printAndCount打印了一个字符串,然后并以Int类型返回它的字符数。第二个函数printWithoutCounting调用的第一个函数,但忽略它的返回值。当第二个函数被调用时,字符串消息由第一个函数打印了回来,却没有使用其返回值。
注意:返回值可以忽略不计,但对一个函数来说,它的返回值即便不使用还是一定会返回的。在函数体底部返回时与定义的返回类型的函数不能相容时,如果试图这样做将导致一个编译时错误。
(4)多返回值函数。
可以使用一个元组类型作为函数的返回类型返回一个有多个值组成的一个复合作为返回值。例如下面的演示代码定义了一个名为count函数,用它计算字符串中基于标准的美式英语中设定使用的元音、辅音以及字符的数量:
func count(string: String) -> (vowels: Int, consonants: Int, others: Int) {
var vowels = 0, consonants = 0, others = 0
for character in string {
switch String(character).lowercaseString {
case "a", "e", "i", "o", "u":
++vowels
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
++consonants
default:
++others
}
}
return (vowels, consonants, others)
}
可以使用此计数函数来对任意字符串进行字符计数,并检索统计总数的元组3个指定Int值:
let total = count("some arbitrary string!")
println("\(total.vowels) vowels and \(total.consonants) consonants")
// prints "6 vowels and 13 consonants"
在此需要注意的是,在这一点上元组的成员不需要被命名在该该函数返回的元组中,因为它们的名字已经被指定为函数的返回类型的一部分。
3.7.3 函数参数名
在本节前面的演示代码中,都为所有函数的参数定义了参数名称:
func someFunction(parameterName: Int) {
// function body goes here, and can use parameterName
// to refer to the argument value for that parameter
}
然而,这些参数名仅能在函数本身的主体内使用,在调用函数时,不能使用。这些类型的参数名称被称为本地的参数,因为它们只适用于函数体中使用。
(1)外部参数名。
有时当我们调用一个函数将每个参数进行命名是非常有用的,以表明你传递给函数的每个参数的目的。如果希望用户函数调用你的函数时提供参数名称,除了设置本地的参数名称,也要为每个参数定义外部参数名称。此时写一个外部参数名称在它所支持的本地参数名称之前,之间用一个空格来分隔,例如下面的演示代码:
func someFunction(externalParameterName localParameterName: Int) {
// function body goes here, and can use localParameterName
// to refer to the argument value for that parameter
}
如果为参数提供一个外部参数名称,调用该函数时外部名称必须始终被使用。作为一个例子,考虑下面的函数,它通过插入它们之间的第三个”joiner”字符串来连接两个字符串:
func join(s1: String, s2: String, joiner: String) -> String {
return s1 + joiner + s2
}
当调用这个函数,传递给函数的3个字符串的目的就不是很清楚了:
join("hello", "world", ", ")
// returns "hello, world"
为了使这些字符串值的目的更为清晰,为每个join函数参数定义外部参数名称:
func join(string s1: String, toString s2: String, withJoiner joiner: String)
-> String {
return s1 + joiner + s2
}
在上述版本的join函数中,第一个参数有一个外部名称string和一个本地名称s1;第二个参数有一个外部名称toString和一个本地名称s2;第三个参数有一个外部名称withJoiner和一个本地名称joiner。
现在可以使用这些外部参数名称清楚明确地调用该函数:
join(string: "hello", toString: "world", withJoiner: ", ")
// returns "hello, world"
由此可见,使用外部参数名称使join函数的第二个版本功能更富有表现力,用户习惯使用sentence-like的方式,同时还提供了一个可读的、意图明确的函数体。
(2)外部参数名称速记。
如果想为一个函数参数提供一个外部参数名,然而本地参数名已经使用了一个合适的名称了,你不需要为该参数写相同的两次名称。取而代之的是,写一次名字,并用一个hash符号(#)作为名称的前缀。这告诉Swift使用该名称同时作为本地参数名称和外部参数名称。
下面的演示代码定义了一个名为containsCharacter的函数,定义了两个参数的外部参数名称,并通过放置一个散列标志在它们本地参数名称之前:
func containsCharacter(#string: String, #characterToFind: Character) -> Bool {
for character in string {
if character == characterToFind {
return true
}
}
return false
}
这个函数选择的参数名称清晰的、函数体极具可读性,使该函数被调用时没有歧义:
let containsAVee = containsCharacter(string: "aardvark", characterToFind: "v")
// containsAVee equals true, because "aardvark" contains a "v"
(3)参数的默认值。
可以为任何参数设定默认值来作为函数的定义的一部分。如果默认值已经定义,调用函数时就可以省略该参数的传值。
在使用时将使用默认值的参数放在函数的参数列表的末尾,这样确保了所有调用函数的非默认参数使用相同的顺序,并明确地表示在每种情况下相同的函数调用。例如,在下面的join函数中,为参数joiner设置了默认值:
func join(string s1: String, toString s2: String,
withJoiner joiner: String = " ") -> String {
return s1 + joiner + s2
}
如果在join函数被调用时提供给joiner一个字符串值,该字符串是用来连接两个字符串,就与以前一样:
join(string: "hello", toString: "world", withJoiner: "-")
// returns "hello-world"
但是,如果当函数被调用时提供了joiner的没有值,就会使用单个空格(" ")的默认值:
join(string: "hello", toString: "world")
// returns "hello world"
(4)有默认值的外部名称参数。
在大多数情况下,为所有参数提供一个外部带有默认值的参数的名称是非常有用的(因此要求)。如果当函数被调用时提供的值是参数必须具有明确的目的。
为了使这个过程更容易,当你自己没有提供外部名称时,Swift自动为所有参数定义了缺省的参数外部名称。自动外部名称与本地名称相同,就好像你在你的代码中的本地名称之前写了一个hash符号。
例如在下面的join函数中,它不为任何参数提供外部名称,但仍然提供了joiner参数的默认值:
func join(s1: String, s2: String, joiner: String = " ") -> String {
return s1 + joiner + s2
}
在这种情况下,Swift自动为一个具有默认值的参数提供了外部参数名称。调用函数时,为使得参数的目的明确,因此必须提供外部名称:
join("hello", "world", joiner: "-")
// returns "hello-world"
(5)可变参数。
一个可变参数的参数接受零个或多个指定类型的值。当函数被调用时,您可以使用一个可变参数的参数来指定该参数可以传递不同数量的输入值。写可变参数的参数时,需要参数的类型名称后加上点字符(…)。
在传递一个可变参数的参数的值时,函数体中是以提供适当类型的数组的形式存在。例如,一个可变参数的名称为numbers和类型为Double…在函数体内就作为名为numbers,类型为Double[]的常量数组。
例如下面的代码演示了计算任意长度的数字的算术平均值(也称为平均)的方法:
func arithmeticMean(numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8, 19)
// returns 10.0, which is the arithmetic mean of these three numbers
(6)常量参数和变量参数。
函数参数的默认值都是常量。试图改变一个函数参数的值会让这个函数体内部产生一个编译时错误。这意味着不能错误地改变参数的值。但是,有时函数有一个参数的值的变量副本是非常有用的。您可以通过指定一个或多个参数作为变量参数,而不是避免在函数内部为自己定义一个新的变量。变量参数可以是变量而不是常量,并给函数中新修改的参数的值提供一个副本。
在参数名称前用关键字var定义变量参数:
func alignRight(var string: String, count: Int, pad: Character) -> String {
let amountToPad = count - countElements(string)
for _ in 1...amountToPad {
string = pad + string
}
return string
}
let originalString = "hello"
let paddedString = alignRight(originalString, 10, "-")
// paddedString is equal to "-----hello"
// originalString is still equal to "hello"
在上述演示代码中,定义了一个新函数叫做alignRight,它对准一个输入字符串,以一个较长的输出字符串。在左侧的空间中填充规定的字符。在该示例中,字符串“hello”被转换为字符串“——hello”。上述alignRight函数把输入参数的字符串定义成了一个变量参数。这意味着字符串现在可以作为一个局部变量,用传入的字符串值初始化,并且可以在函数体中进行相应操作。函数首先找出有多少字符需要被添加到左边,让字符串以右对齐在整个字符串中。这个值存储在本地常量amountToPad中。该函数然后将填充字符的amountToPad个字符复制到现有的字符串的左边,并返回结果。整个过程使用字符串变量参数进行字符串操作。
(7)输入-输出参数。
可变参数是指只能在函数本身内改变。如果你想有一个函数来修改参数的值,并且想让这些变化要坚持在函数调用结束后,你就可以定义输入-输出参数来代替。通过在其参数定义的开始添加inout关键字用来标明输入-输出参数。一个输入-输出参数都有一个传递给函数的值,由函数修改后,并从函数返回来替换原来的值。
3.8 实战演练——使用Xcode创建Swift程序
当苹果公司推出Swift编程语言时,建议使用Xcode 6来开发Swift程序。在本节的内容中,将详细讲解使用Xcode 6创建Swift程序的方法。
(1)打开Xcode 6,单击“Create a new Xcode Project”创建一个工程文件,如图3-2所示。
图3-2 创建一个工程文件
(2)在弹出的界面中,在左侧栏目中选择“Application”,在右侧选择“Command Line Tool”,然后单击“Next”按钮,如图3-3所示。
图3-3 创建一个“Command Line Tool”工程
(3)在弹出的界面中设置各个选项值,在“Language”选项中设置编程语言为“Swift”,然后单击“Next”按钮,如图3-4所示。
图3-4 设置编程语言为“Swift”
(4)在弹出的界面中设置当前工程的保存路径,如图3-5所示。
图3-5 设置保存路径
(5)单击“Create”按钮,将自动生成一个用Swift语言编写的iOS工程。在工程文件main.swift中会自动生成一个“Hello, World!”语句,如图3-6所示。
图3-6 自动生成的Swift代码
文件main.swift的代码是自动生成的,具体代码如下所示:
//
// main.swift
// exSwift
//
// Created by admin on 14-6-7.
// Copyright (c) 2014年 apple. All rights reserved.
//
import Foundation
println("Hello, World!")
单击图3-6左上角的按钮运行工程,会在Xcode 6下方的控制台中输出运行结果,如图3-7所示。
图3-7 输出执行结果
由此可见,通过使用Xcode 6可以节约Swift代码的编写工作量,提高了开发效率。