Swift十二讲 第三章 数组字典函数和闭包 (draft)
1.数组
- 数组的定义和一些简单的例子
数组是同一类型的东西的有序集合。可以有整数数组,也可以有按钮类的数组。只要是同一类型,都可以。数组的元素由其位置来指定。数组的位置是从0开始的。数组可以是常值也可以是变值。分别用let和var来声明。除非是空数组,不然必须初始化。
下面是几个声明数组的例子:
let a = [1,2,3] //整数数组,Swift可以自己推断出来类型
let m=a[0] //m被赋值为a的第一个元素,等于1
var b = ["x","y","z"]
var c = ["xx","yy"]
var d = b + c
//数组可以做加法。不管数组是什么类型,加起来就是后一个附加于前一个的后面。不同类型的数组则不能相加。
var xx = [String](count: 2, repeatedValue: "x")
数组是值类型。传递的时候不是传递的地址或者索引,而是确实制造了一个拷贝。这点和Objective C的NSArray不同。
- 数组的内置算法和属性
数组有很多内置的算法和属性。下面举例说明一些笔者认为最有用的部分。这些内容往往牵涉到闭包(closure)的使用,读者也可以跳过回头再看。
var a = [10,2,3]
println("\(a.sort { $0 < $1})") //输出[2,3,10]
println(a) //输出[2,3,10]; a被改变了
println("\(a.sorted{ $1 < $0})") //输出[10,3,2]
println(a) //sorted不改变原数组,输出还是[2,3,10]
sort算法执行后,原数组被改变。sorted算法执行后,返回一个排过序的数组。但原来的数组不变。
println("\(a.reverse())") //输出[2,3,10],原数组不变。
reverse顾名思义就是生成一个逆序数组。
println("\(a.filter {$0 > 2 })") //输出[3,10],原数组不变
filter返回满足一定条件的数组元素形成的新数组。filter的条件由一个closure定义。
println("\(a.map {$0 > 0 ? -$0 : 1 })")
//输出 [-2,-3,-10]
map返回数组的每个元素被map这个函数操作后的结果。函数由一个closure定义。
println("\(a.reduce(0) {$0 + $1 })")
//输出为15。
reduce返回一个值,这个值由reduce后面的closure给出。上例的意思是求数组所有元素之和。
数组一些有用的属性如下,意思都很直接易懂:
a.first
a.last //数组的第一个和最末一个元素
a.capacity //数组最多可以存多少个元素而无需后台重新分配内存
a.count //数组有多少个元素
a.isEmpty //数组是空的吗?
可以用下面代码替换数组的一部分元素:
a[1...2] = [2,3] //数组的第二第三个元素被替换为[2,3]
可以用下面代码去掉数组的单个指定元素:
a.removeAtIndex(1)
println(a) //数组的第二个元素被去掉。
可以用下面代码确保数组被分配了足够的内存而不用后台重新调动。
a.reserveCapacity(10) // 预定了够放10个元素的内存。
2.字典
字典的类型的形式定义为Dictionary <KeyType:ValueType>。可以简写为[Keytype:ValueType]。字典其实就是两个数组放在一起,第一个数组是索引,第二个是值。因此,第一个数组不能有重复的值。例如下面句子会报错:
let aa = [1:2, 1:3]
Playground会告诉你:
“fatal error: dictionary literal contains duplicate keys”
另外,既然Key和Value都类似于数组,Key的元素必须是同类型的。Value也必须同类型。
字典的初始化可以指定类型,也可以指定一些初始值,然后编译器会做类型推断。例如:
var a = ["狗有几条腿":4,"人有几条腿":2]
var b: [String:String] = ["狗":"白色","兔子":"灰色"]
你还可以不指定初始值,只是给出其最小容量。或者定义一个空字典。
var c = [String:String]()
var d = [String:String] (minimumCapacity:2)
字典的value部分可以用key来索引到:
println(a["狗有几条腿"]!) //输出为4
可以定义个一个数组,其值为字典的value部分或者key部分。
var ax = [String](a.keys)
println(ax)
var ay = [Int](a.values)
println(ay)
//注意这里必须指定类型;得到的数组的次序未必就是字典的原来顺序。
//字典是以key为索引的,其元素的位置无意义。
字典也有.count,.isEmpty等类似于数组的属性。
3.定义在数组或者字典上的循环
定义在数组上的循环的格式为:
for item in my_Array
{
//在这里item被当作常量使用。
//第一次进入循环,item的值是my_Array的第一个值,第二次入循环是数组的第二个值。等等。
//item不能被修改。
}
例如:
var a=["方","圆","三角"]
for i in a
{
println(i)
} //依次输出方圆三角
定义在字典上的循环的格式为:
for (key, value) in my_Dict
{
//在这里(key,value)被当作常量使用。
//第一次进入循环,的值是my_Dict的第一个值,第二次入循环是数组的第二个值。等等。
//(key,value)不能被修改。
}
你可以直接定义一个定义在字典的keys上的或者values上的循环
for my_key in my_Dict.keys
{
//my_key是常量,不能修改。每次入循环,自动被赋予下一个keys的值
}
for my_value in my_Dict.values
{
//my_value是常量,不能修改。每次入循环,自动被赋予下一个values的值
}
4.函数
Swift的函数和闭包定义和用法是非常丰富的。就算是编程专家,也很难一时掌握好。我们要做的是,化繁为简。只重点阐述用起来肯定不会有问题的,强大的有用的那些部分。
-
Swift的函数不需要像C那样,先声明,后定义。直接写函数体,然后调用就行了。写法为:
func 函数名(外部参数名 内部参数名:类型,...等等更多参数) ->返值的类型
{
//函数内部要做的事
}
调用函数:
函数名(外部参数名:参数值)
- 如果外部参数名和内部参数名相同,用#然后只写一个名字就好。
如果函数定义的时候省略外部参数名,那么调用时可以直接写值。如下例子:
var ii=24
func ad3(x:Int)->Int
{return x+30}
println(ad3(ii)) //输出54
- 外部参数名之前,可以写var/let/inout三选一。如果什么都不写,那这个参数被默认为let,也就是常值。在函数体内部不能被改变。
- inout 是个指针或者索引。
套用上面模版,一些例子如下:
var i = 32
var ii = 24
func ad1(var # x:Int)->Int{
x = x+5
return x}
let b = ad1(x: ii)
println(b) //输出29
println(ii) //输出24
func ad2(inout i:Int)->Int{
i=i+20
return i+20}
var a = ad2(&i)
println(i) //输出52
println(a) //输出72
如上例所示。如果想要在函数体之内改变函数体之外一个变量的值,只能用inout。
- 函数可以用变长度参数值。变长度参数值类似于数组的用法。例如:
func ad3(x: Int...)->Int
{return x[1]+30}
println(ad3(30,-1)) //输出29
- 假设我们有一个函数A,它的类型为T。然后我们定义了一个变量,类型为T。那么我们可以把A的函数名赋值给A。你也可以把一个函数当作另一个函数的返回值或者参数。这大略相当于一个更简洁安全的函数指针。函数的名字可以像变量那样传来传去。但传递的只是函数的索引。
如下例:
func ad1(var # x:Int)->Int{
x = x+5
return x}
func ad4(x: Int, # myFunc: (Int)->Int)->Int
{
println("结果="+"\(myFunc(x))")
return 0
}
//输出 "结果=7"
//函数ad4的一个参数是类型为(int)->int的函数。
//函数ad1符合这个类型,所以可以传进去执行。
5.闭包
这里只简单介绍下闭包。一来因为目前用处并不广泛。二来Swift说不定会改语法或者扩展语法。我建议读者自行试验和写一些常用的闭包,存起来日后用。不要使用没有仔细分析和试验过的闭包。
-
粗略地来说,闭包可以被看作一个匿名的函数。
也就是把函数定义的函数名,变量名都去掉。然后加上in关键字表示函数体。闭包通常按如下格式定义:{(参数)->返回值 in
执行什么什么}
例如:
let animals = ["狗", "猫", "兔"]
let myexample = animals.map({
(x: String) -> String in
"\(x)长大了"+"长大了"
})
println(myexample)
//输出为
//[狗长大了长大了, 猫长大了长大了, 兔长大了长大了]
- 可以看出,闭包的使用是非常符合人的直观思维的。但和传统C类型的计算机语言区别很大。如上述例子。我们想要把一个数组按照一定的规则变换成另一个数组。那就应该把转换规则写清楚就行了。规则怎么写?如上例所示的写法,其实是和传统函数类似的。但是下面这点是非常不同的。
再比如你想把一个数组的每个元素都平方下:
var xx = [1,2,3]
let b = xx.map({$0 * $0})
println(b) //输出[1,4,9]
//是不是像matlab一样简单?
- 函数和闭包可以多层定义。也就是在一个函数/闭包内可以定义另外一个函数/闭包。内层的函数/闭包可以调用外层出现过的任何变量,也可以调用外层函数/闭包的参数值。
最后关于所谓的函数式编程吐两句槽。Lambda Calculus是丘奇在古代的伟大发明。学会函数式编程,可以增广见闻,或许在某些场合有用,也或许某天函数式编程会成为主流。但这个东西就和学会用对数表算乘法区别不大。并没有特别神秘之处。发明东西难。学会东西容易。切忌因为会个特殊的语法糖,而时时去用这个语法糖来彰显自己的不同之处,那样会造成项目灾难。
(当然,如果你真的发明了一个类似于函数编程之类的东西,别忘了告诉我。)