structTimesTable { let multiplier: Int subscript(index: Int) -> Int { return multiplier * index } } let threeTimesTable =TimesTable(multiplier: 3) print("six times three is \(threeTimesTable[6])") // 打印“six times three is 18”
structPoint { var x =0.0, y =0.0 funcisToTheRightOf(x: Double) -> Bool { returnself.x > x } } let somePoint =Point(x: 4.0, y: 5.0) if somePoint.isToTheRightOf(x: 1.0) { print("This point is to the right of the line where x == 1.0") } // 打印“This point is to the right of the line where x == 1.0”
structPoint { var x =0.0, y =0.0 mutatingfuncmoveBy(xdeltaX: Double, ydeltaY: Double) { x += deltaX y += deltaY } } var somePoint =Point(x: 1.0, y: 1.0) somePoint.moveBy(x: 2.0, y: 3.0) print("The point is now at (\(somePoint.x), \(somePoint.y))") // 打印“The point is now at (3.0, 4.0)”
上面的 Point 结构体定义了一个可变方法 moveBy(x:y :) 来移动 Point 实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了 mutating 关键字,从而允许修改属性。
注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,详情参见 常量结构体的存储属性:
classPlayer { var tracker =LevelTracker() let playerName: String funccomplete(level: Int) { LevelTracker.unlock(level +1) tracker.advance(to: level +1) } init(name: String) { playerName = name } }
Player 类创建一个新的 LevelTracker 实例来监测这个用户的进度。它提供了 complete(level:) 方法,一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了 advance(to:) 返回的布尔值,因为之前调用 LevelTracker.unlock(_:) 时就知道了这个等级已经被解锁了)。
你还可以为一个新的玩家创建一个 Player 的实例,然后看这个玩家完成等级一时发生了什么:
1 2 3 4
var player =Player(name: "Argyrios") player.complete(level: 1) print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)") // 打印“highest unlocked level is now 2”
player =Player(name: "Beto") if player.tracker.advance(to: 6) { print("player is now on level 6") } else { print("level 6 has not yet been unlocked") } // 打印“level 6 has not yet been unlocked”
structFixedLengthRange { var firstValue: Int let length: Int } var rangeOfThreeItems =FixedLengthRange(firstValue: 0, length: 3) // 该区间表示整数 0,1,2 rangeOfThreeItems.firstValue =6 // 该区间现在表示整数 6,7,8
structPoint { var x =0.0, y =0.0 } structSize { var width =0.0, height =0.0 } structRect { var origin =Point() var size =Size() var center: Point { get { let centerX = origin.x + (size.width /2) let centerY = origin.y + (size.height /2) returnPoint(x: centerX, y: centerY) } set(newCenter) { origin.x = newCenter.x - (size.width /2) origin.y = newCenter.y - (size.height /2) } } } var square =Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0)) let initialSquareCenter = square.center square.center =Point(x: 15.0, y: 15.0) print("square.origin is now at (\(square.origin.x), \(square.origin.y))") // 打印“square.origin is now at (10.0, 10.0)”
这个例子定义了 3 个结构体来描述几何形状:
Point 封装了一个 (x, y) 的坐标
Size 封装了一个 width 和一个 height
Rect 表示一个有原点和尺寸的矩形
Rect 也提供了一个名为 center 的计算属性。一个 Rect 的中心点可以从 origin(原点)和 size(大小)算出,所以不需要将中心点以 Point 类型的值来保存。Rect 的计算属性 center 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。
上述例子中创建了一个名为 square 的 Rect 实例,初始值原点是 (0, 0),宽度高度都是 10。如下图中蓝色正方形所示。
square 的 center 属性可以通过点运算符(square.center)来访问,这会调用该属性的 getter 来获取它的值。跟直接返回已经存在的值不同,getter 实际上通过计算然后返回一个新的 Point 来表示 square 的中心点。如代码所示,它正确返回了中心点 (5, 5)。
center 属性之后被设置了一个新的值 (15, 15),表示向右上方移动正方形到如下图橙色正方形所示的位置。设置属性 center 的值会调用它的 setter 来修改属性 origin 的 x 和 y 的值,从而实现移动正方形到新的位置。
简化 Setter 声明
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue。下面是使用了简化 setter 声明的 Rect 结构体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
structAlternativeRect { var origin =Point() var size =Size() var center: Point { get { let centerX = origin.x + (size.width /2) let centerY = origin.y + (size.height /2) returnPoint(x: centerX, y: centerY) } set { origin.x = newValue.x - (size.width /2) origin.y = newValue.y - (size.height /2) } } }
简化 Getter 声明
如果整个 getter 是单一表达式,getter 会隐式地返回这个表达式结果。下面是另一个版本的 Rect 结构体,用到了简化的 getter 和 setter 声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
structCompactRect { var origin =Point() var size =Size() var center: Point { get { Point(x: origin.x + (size.width /2), y: origin.y + (size.height /2)) } set { origin.x = newValue.x - (size.width /2) origin.y = newValue.y - (size.height /2) } } }
每当你定义一个新的结构体或者类时,你都是定义了一个新的 Swift 类型。请使用 UpperCamelCase 这种方式来命名类型(如这里的 SomeClass 和 SomeStructure),以便符合标准 Swift 类型的大写命名风格(如 String,Int 和 Bool)。请使用 lowerCamelCase 这种方式来命名属性和方法(如 framerate 和 incrementCount),以便和类型名区分。
以下是定义结构体和定义类的示例:
1 2 3 4 5 6 7 8 9 10
structResolution { var width =0 var height =0 } classVideoMode { var resolution =Resolution() var interlaced =false var frameRate =0.0 var name: String? }
print("The width of someVideoMode is \(someVideoMode.resolution.width)") // 打印 "The width of someVideoMode is 0"
你也可以使用点语法为可变属性赋值:
1 2 3
someVideoMode.resolution.width =1280 print("The width of someVideoMode is now \(someVideoMode.resolution.width)") // 打印 "The width of someVideoMode is now 1280"
print("cinema is now \(cinema.width) pixels wide") // 打印 "cinema is now 2048 pixels wide"
然而,初始的 hd 实例中 width 属性还是 1920:
1 2
print("hd is still \(hd.width) pixels wide") // 打印 "hd is still 1920 pixels wide"
将 hd 赋值给 cinema 时,hd 中所存储的值会拷贝到新的 cinema 实例中。结果就是两个完全独立的实例包含了相同的数值。由于两者相互独立,因此将 cinema 的 width 修改为 2048 并不会影响 hd 中的 width 的值,如下图所示:
枚举也遵循相同的行为准则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
enumCompassPoint { case north, south, east, west mutatingfuncturnNorth() { self= .north } } var currentDirection =CompassPoint.west let rememberedDirection = currentDirection currentDirection.turnNorth()
print("The current direction is \(currentDirection)") print("The remembered direction is \(rememberedDirection)") // 打印 "The current direction is north" // 打印 "The remembered direction is west"
if tenEighty === alsoTenEighty { print("tenEighty and alsoTenEighty refer to the same VideoMode instance.") } // 打印 "tenEighty and alsoTenEighty refer to the same VideoMode instance."
在 Swift 中,枚举类型是一等(first-class)类型。它们采用了很多在传统上只被类(class)所支持的特性,例如计算属性(computed properties),用于提供枚举值的附加信息,实例方法(instance methods),用于提供和枚举值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始实现的基础上扩展它们的功能;还可以遵循协议(protocols)来提供标准的功能。
directionToHead = .south switch directionToHead { case .north: print("Lots of planets have a north") case .south: print("Watch out for penguins") case .east: print("Where the sun rises") case .west: print("Where the skies are blue") } // 打印“Watch out for penguins”
你可以这样理解这段代码:
“判断 directionToHead 的值。当它等于 .north,打印 “Lots of planets have a north”。当它等于 .south,打印 “Watch out for penguins”。”
let somePlanet =Planet.earth switch somePlanet { case .earth: print("Mostly harmless") default: print("Not a safe place for humans") } // 打印“Mostly harmless”
你可以使用一个 switch 语句来检查不同的条形码类型,和之前使用 Switch 语句来匹配枚举值的例子一样。然而,这一次,关联值可以被提取出来作为 switch 语句的一部分。你可以在 switch 的 case 分支代码中提取每个关联值作为一个常量(用 let 前缀)或者作为一个变量(用 var 前缀)来使用:
1 2 3 4 5 6 7
switch productBarcode { case .upc(let numberSystem, let manufacturer, let product, let check): print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).") case .qrCode(let productCode): print("QR code: \(productCode).") } // 打印“QR code: ABCDEFGHIJKLMNOP.”
如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个 let 或者 var:
let positionToFind =11 iflet somePlanet =Planet(rawValue: positionToFind) { switch somePlanet { case .earth: print("Mostly harmless") default: print("Not a safe place for humans") } } else { print("There isn't a planet at position \(positionToFind)") } // 打印“There isn't a planet at position 11”
indirectenumArithmeticExpression { case number(Int) case addition(ArithmeticExpression, ArithmeticExpression) case multiplication(ArithmeticExpression, ArithmeticExpression) }
let five =ArithmeticExpression.number(5) let four =ArithmeticExpression.number(4) let sum =ArithmeticExpression.addition(five, four) let product =ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
let strings = numbers.map { (number) -> Stringin var number = number var output ="" repeat { output = digitNames[number %10]!+ output number /=10 } while number >0 return output } // strings 常量被推断为字符串类型数组,即 [String] // 其值为 ["OneSix", "FiveEight", "FiveOneZero"]
map(_:) 为数组中每一个元素调用了一次闭包表达式。你不需要指定闭包的输入参数 number 的类型,因为可以通过要映射的数组类型进行推断。
在该例中,局部变量 number 的值由闭包中的 number 参数获得,因此可以在闭包函数体内对其进行修改,(闭包或者函数的参数总是常量),闭包表达式指定了返回类型为 String,以表明存储映射值的新数组类型为 String。
下例中定义了一个名为 minMax(array:) 的函数,作用是在一个 Int 类型的数组中找出最小值与最大值。
1 2 3 4 5 6 7 8 9 10 11 12
funcminMax(array: [Int]) -> (min: Int, max: Int) { var currentMin = array[0] var currentMax = array[0] for value in array[1..<array.count] { if value < currentMin { currentMin = value } elseif value > currentMax { currentMax = value } } return (currentMin, currentMax) }
minMax(array:) 函数返回一个包含两个 Int 值的元组,这些值被标记为 min 和 max ,以便查询函数的返回值时可以通过名字访问它们。
funcminMax(array: [Int]) -> (min: Int, max: Int)? { if array.isEmpty { returnnil } var currentMin = array[0] var currentMax = array[0] for value in array[1..<array.count] { if value < currentMin { currentMin = value } elseif value > currentMax { currentMax = value } } return (currentMin, currentMax) }
你可以使用可选绑定来检查 minMax(array:) 函数返回的是一个存在的元组值还是 nil:
1 2 3 4
iflet bounds = minMax(array: [8, -6, 2, 109, 3, 71]) { print("min is \(bounds.min) and max is \(bounds.max)") } // 打印“min is -6 and max is 109”
funcgreet(person: String, fromhometown: String) -> String { return"Hello \(person)! Glad you could visit from \(hometown)." } print(greet(person: "Bill", from: "Cupertino")) // 打印“Hello Bill! Glad you could visit from Cupertino.”
var someInt =3 var anotherInt =107 swapTwoInts(&someInt, &anotherInt) print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") // 打印“someInt is now 107, and anotherInt is now 3”
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] for (animalName, legCount) in numberOfLegs { print("\(animalName)s have \(legCount) legs") } // cats have 4 legs // ants have 6 legs // spiders have 8 legs
for index in1...5 { print("\(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
上面的例子中,index 是一个每次循环遍历开始时被自动赋值的常量。这种情况下,index 在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用 let 关键字声明。
如果你不需要区间序列内每一项的值,你可以使用下划线(_)替代变量名来忽略这个值:
1 2 3 4 5 6 7 8
let base =3 let power =10 var answer =1 for_in1...power { answer *= base } print("\(base) to the power of \(power) is \(answer)") // 输出“3 to the power of 10 is 59049”
Swift 提供两种类型的条件语句:if 语句和 switch 语句。通常,当条件较为简单且可能的情况很少时,使用 if 语句。而 switch 语句更适用于条件较复杂、有更多排列组合的时候。并且 switch 在需要用到模式匹配(pattern-matching)的情况下会更有用。
If
if 语句最简单的形式就是只包含一个条件,只有该条件为 true 时,才执行相关代码:
1 2 3 4 5
var temperatureInFahrenheit =30 if temperatureInFahrenheit <=32 { print("It's very cold. Consider wearing a scarf.") } // 输出“It's very cold. Consider wearing a scarf.”
上面的例子会判断温度是否小于等于 32 华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行 if 块后面的代码。
temperatureInFahrenheit =40 if temperatureInFahrenheit <=32 { print("It's very cold. Consider wearing a scarf.") } else { print("It's not that cold. Wear a t-shirt.") } // 输出“It's not that cold. Wear a t-shirt.”
temperatureInFahrenheit =90 if temperatureInFahrenheit <=32 { print("It's very cold. Consider wearing a scarf.") } elseif temperatureInFahrenheit >=86 { print("It's really warm. Don't forget to wear sunscreen.") } else { print("It's not that cold. Wear a t-shirt.") } // 输出“It's really warm. Don't forget to wear sunscreen.”
在上面的例子中,额外的 if 语句用于判断是不是特别热。而最后的 else 语句被保留了下来,用于打印既不冷也不热时的消息。
实际上,当不需要完整判断情况的时候,最后的 else 语句是可选的:
1 2 3 4 5 6
temperatureInFahrenheit =72 if temperatureInFahrenheit <=32 { print("It's very cold. Consider wearing a scarf.") } elseif temperatureInFahrenheit >=86 { print("It's really warm. Don't forget to wear sunscreen.") }
在这个例子中,由于既不冷也不热,所以不会触发 if 或 else if 分支,也就不会打印任何消息。
Switch
switch 语句会尝试把某个值与若干个模式(pattern)进行匹配。根据第一个匹配成功的模式,switch 语句会执行对应的代码。当有可能的情况较多时,通常用 switch 语句替换 if 语句。
switch 语句最简单的形式就是把某个值与一个或若干个相同类型的值作比较:
1 2 3 4 5 6 7 8 9
switchsome 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 构成,每个由 case 关键字开始。为了匹配某些更特定的值,Swift 提供了几种方法来进行更复杂的模式匹配,这些模式将在本节的稍后部分提到。
与 if 语句类似,每一个 case 都是代码执行的一条分支。switch 语句会决定哪一条分支应该被执行,这个流程被称作根据给定的值切换(switching)。
switch 语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(default)分支来涵盖其它所有没有对应的值,这个默认分支必须在 switch 语句的最后面。
下面的例子使用 switch 语句来匹配一个名为 someCharacter 的小写字符:
1 2 3 4 5 6 7 8 9 10
let someCharacter: Character="z" switch someCharacter { case"a": print("The first letter of the alphabet") case"z": print("The last letter of the alphabet") default: print("Some other character") } // 输出“The last letter of the alphabet”
在这个例子中,第一个 case 分支用于匹配第一个英文字母 a,第二个 case 分支用于匹配最后一个字母 z。因为 switch 语句必须有一个 case 分支用于覆盖所有可能的字符,而不仅仅是所有的英文字母,所以 switch 语句使用 default 分支来匹配除了 a 和 z 外的所有值,这个分支保证了 swith 语句的完备性。
不存在隐式的贯穿
与 C 和 Objective-C 中的 switch 语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止 switch 语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用 break 语句。这使得 switch 语句更安全、更易用,也避免了漏写 break 语句导致多个语言被执行的错误。
注意
虽然在 Swift 中 break 不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用 break 跳出,详情请参见 Switch 语句中的 break。
每一个 case 分支都必须包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的:
1 2 3 4 5 6 7 8 9
let anotherCharacter: Character="a" switch anotherCharacter { case"a": // 无效,这个分支下面没有语句 case"A": print("The letter A") default: print("Not the letter A") } // 这段代码会报编译错误
不像 C 语言里的 switch 语句,在 Swift 中,switch 语句不会一起匹配 "a" 和 "A"。相反的,上面的代码会引起编译期错误:case "a": 不包含任何可执行语句——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。
为了让单个 case 同时匹配 a 和 A,可以将这个两个值组合成一个复合匹配,并且用逗号分开:
1 2 3 4 5 6 7 8
let anotherCharacter: Character="a" switch anotherCharacter { case"a", "A": print("The letter A") default: print("Not the letter A") } // 输出“The letter A”
let somePoint = (1, 1) switch somePoint { case (0, 0): print("\(somePoint) is at the origin") case (_, 0): print("\(somePoint) is on the x-axis") case (0, _): print("\(somePoint) is on the y-axis") case (-2...2, -2...2): print("\(somePoint) is inside the box") default: print("\(somePoint) is outside of the box") } // 输出“(1, 1) is inside the box”
在上面的例子中,switch 语句会判断某个点是否是原点 (0, 0),是否在红色的 x 轴上,是否在橘黄色的 y 轴上,是否在一个以原点为中心的4x4的蓝色矩形里,或者在这个矩形外面。
不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点 (0, 0)可以匹配所有四个 case。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点 (0, 0)会首先匹配 case (0, 0),因此剩下的能够匹配的分支都会被忽视掉。
值绑定(Value Bindings)
case 分支允许将匹配的值声明为临时常量或变量,并且在 case 分支体内使用 —— 这种行为被称为值绑定(value binding),因为匹配的值在 case 分支体内,与临时的常量或变量绑定。
下面的例子将下图中的点 (x, y),使用 (Int, Int) 类型的元组表示,然后分类表示:
1 2 3 4 5 6 7 8 9 10
let anotherPoint = (2, 0) switch anotherPoint { case (let x, 0): print("on the x-axis with an x value of \(x)") case (0, let y): print("on the y-axis with a y value of \(y)") caselet (x, y): print("somewhere else at (\(x), \(y))") } // 输出“on the x-axis with an x value of 2”
在上面的例子中,switch 语句会判断某个点是否在红色的 x 轴上,是否在橘黄色的 y 轴上,或者不在坐标轴上。
这三个 case 都声明了常量 x 和 y 的占位符,用于临时获取元组 anotherPoint 的一个或两个值。第一个 case ——case (let x, 0) 将匹配一个纵坐标为 0 的点,并把这个点的横坐标赋给临时的常量 x。类似的,第二个 case ——case (0, let y) 将匹配一个横坐标为 0 的点,并把这个点的纵坐标赋给临时的常量 y。
一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里使用。在这个例子中,它们用于打印给定点的类型。
请注意,这个 switch 语句不包含默认分支。这是因为最后一个 case ——case let(x, y) 声明了一个可以匹配余下所有值的元组。这使得 switch 语句已经完备了,因此不需要再书写默认分支。
Where
case 分支的模式可以使用 where 语句来判断额外的条件。
下面的例子把下图中的点 (x, y)进行了分类:
1 2 3 4 5 6 7 8 9 10
let yetAnotherPoint = (1, -1) switch yetAnotherPoint { caselet (x, y) where x == y: print("(\(x), \(y)) is on the line x == y") caselet (x, y) where x ==-y: print("(\(x), \(y)) is on the line x == -y") caselet (x, y): print("(\(x), \(y)) is just some arbitrary point") } // 输出“(1, -1) is on the line x == -y”
在上面的例子中,switch 语句会判断某个点是否在绿色的对角线 x == y 上,是否在紫色的对角线 x == -y 上,或者不在对角线上。
这三个 case 都声明了常量 x 和 y 的占位符,用于临时获取元组 yetAnotherPoint 的两个值。这两个常量被用作 where 语句的一部分,从而创建一个动态的过滤器(filter)。当且仅当 where 语句的条件为 true 时,匹配到的 case 分支才会被执行。
就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,switch 语句就已经完备了,因此不需要再书写默认分支。
复合型 Cases
当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个 case 后面,并且用逗号隔开。当 case 后面的任意一种模式匹配的时候,这条分支就会被匹配。并且,如果匹配列表过长,还可以分行书写:
1 2 3 4 5 6 7 8 9 10 11
let someCharacter: Character="e" switch someCharacter { case"a", "e", "i", "o", "u": print("\(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": print("\(someCharacter) is a consonant") default: print("\(someCharacter) is not a vowel or a consonant") } // 输出“e is a vowel”
这个 switch 语句中的第一个 case,匹配了英语中的五个小写元音字母。相似的,第二个 case 匹配了英语中所有的小写辅音字母。最终,default 分支匹配了其它所有字符。
let stillAnotherPoint = (9, 0) switch stillAnotherPoint { case (let distance, 0), (0, let distance): print("On an axis, \(distance) from the origin") default: print("Not on an axis") } // 输出“On an axis, 9 from the origin”
上面的 case 有两个模式:(let distance, 0) 匹配了在 x 轴上的值,(0, let distance) 匹配了在 y 轴上的值。两个模式都绑定了 distance,并且 distance 在两种模式下,都是整型——这意味着分支体内的代码,只要 case 匹配,都可以获取到 distance 值。
在上面的例子中,想要把 Character 所有的的可能性都枚举出来是不现实的,所以使用 default 分支来包含所有上面没有匹配到字符的情况。由于这个 default 分支不需要执行任何动作,所以它只写了一条 break 语句。一旦落入到 default 分支中后,break 语句就完成了该分支的所有代码操作,代码继续向下,开始执行 if let 语句。
贯穿(Fallthrough)
在 Swift 里,switch 语句不会从上一个 case 分支跳转到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个 switch 代码块完成了它的执行。相比之下,C 语言要求你显式地插入 break 语句到每个 case 分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的 switch 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用 fallthrough 关键字。下面的例子使用 fallthrough 来创建一个数字的描述语句。
1 2 3 4 5 6 7 8 9 10 11
let integerToDescribe =5 var description ="The number \(integerToDescribe) is" switch integerToDescribe { case2, 3, 5, 7, 11, 13, 17, 19: description +=" a prime number, and also" fallthrough default: description +=" an integer." } print(description) // 输出“The number 5 is a prime number, and also an integer.”
print("The shopping list contains \(shoppingList.count) items.") // 输出“The shopping list contains 2 items.”(这个数组有2个项)
使用布尔属性 isEmpty 作为一个缩写形式去检查 count 属性是否为 0:
1 2 3 4 5 6
if shoppingList.isEmpty { print("The shopping list is empty.") } else { print("The shopping list is not empty.") } // 打印“The shopping list is not empty.”(shoppinglist 不是空的)
Swift 中的 Set 类型被写为 Set<Element>,这里的 Element 表示 Set 中允许存储的类型,和数组不同的是,集合没有等价的简化形式。
创建和构造一个空的集合
你可以通过构造器语法创建一个特定类型的空集合:
1 2 3
var letters =Set<Character>() print("letters is of type Set<Character> with \(letters.count) items.") // 打印“letters is of type Set<Character> with 0 items.”
favoriteGenres 被声明为一个变量(拥有 var 标示符)而不是一个常量(拥有 let 标示符),因为它里面的元素将会在下面的例子中被增加或者移除。
一个 Set 类型不能从数组字面量中被单独推断出来,因此 Set 类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个 Set 并且该数组字面量中的所有元素类型相同,那么你无须写出 Set 的具体类型。favoriteGenres 的构造形式可以采用简化的方式代替:
1
var favoriteGenres: Set= ["Rock", "Classical", "Hip hop"]
print("I have \(favoriteGenres.count) favorite music genres.") // 打印“I have 3 favorite music genres.”
使用布尔属性 isEmpty 作为一个缩写形式去检查 count 属性是否为 0:
1 2 3 4 5 6
if favoriteGenres.isEmpty { print("As far as music goes, I'm not picky.") } else { print("I have particular music preferences.") } // 打印“I have particular music preferences.”
你可以通过调用 Set 的 remove(_:) 方法去删除一个元素,如果该值是该 Set 的一个元素则删除该元素并且返回被删除的元素值,否则如果该 Set 不包含该值,则返回 nil。另外,Set 中的所有元素可以通过它的 removeAll() 方法删除。
1 2 3 4 5 6
iflet removedGenre = favoriteGenres.remove("Rock") { print("\(removedGenre)? I'm over it.") } else { print("I never much cared for that.") } // 打印“Rock? I'm over it.”
使用 contains(_:) 方法去检查 Set 中是否包含一个特定的值:
1 2 3 4 5 6
if favoriteGenres.contains("Funk") { print("I get up on the good foot.") } else { print("It's too funky in here.") } // 打印“It's too funky in here.”
遍历一个集合
你可以在一个 for-in 循环中遍历一个 Set 中的所有值。
1 2 3 4 5 6
for genre in favoriteGenres { print("\(genre)") } // Classical // Jazz // Hip hop
print("The dictionary of airports contains \(airports.count) items.") // 打印“The dictionary of airports contains 2 items.”(这个字典有两个数据项)
使用布尔属性 isEmpty 作为一个缩写形式去检查 count 属性是否为 0:
1 2 3 4 5 6
if airports.isEmpty { print("The airports dictionary is empty.") } else { print("The airports dictionary is not empty.") } // 打印“The airports dictionary is not empty.”
iflet oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") { print("The old value for DUB was \(oldValue).") } // 输出“The old value for DUB was Dublin.”
iflet airportName = airports["DUB"] { print("The name of the airport is \(airportName).") } else { print("That airport is not in the airports dictionary.") } // 打印“The name of the airport is Dublin Airport.”
iflet removedValue = airports.removeValue(forKey: "DUB") { print("The removed airport's name is \(removedValue).") } else { print("The airports dictionary does not contain a value for DUB.") } // 打印“The removed airport's name is Dublin Airport.”
字符串是是一系列字符的集合,例如 "hello, world","albatross"。Swift 的字符串通过 String 类型来表示。而 String 内容的访问方式有多种,例如以 Character 值的集合。
Swift 的 String 和 Character 类型提供了一种快速且兼容 Unicode 的方式来处理代码中的文本内容。创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。通过 + 符号就可以非常简单的实现两个字符串的拼接操作。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你可以在已有字符串中插入常量、变量、字面量和表达式从而形成更长的字符串,这一过程也被成为字符串插值。尤其是在为显示、存储和打印创建自定义字符串值时,字符串插值操作尤其有用。
尽管语法简易,但 Swift 中的 String 类型的实现却很快速和现代化。每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式。
注意
Swift 的 String 类型与 Foundation NSString 类进行了无缝桥接。Foundation 还对 String 进行扩展使其可以访问 NSString 类型中定义的方法。这意味着调用那些 NSString 的方法,你无需进行任何类型转换。
let quotation =""" The White Rabbit put on his spectacles. "Where shall I begin, please your Majesty?" he asked. "Begin at the beginning," the King said gravely, "and go on till you come to the end; then stop." """
let softWrappedQuotation =""" The White Rabbit put on his spectacles. "Where shall I begin, \ please your Majesty?" he asked. "Begin at the beginning," the King said gravely, "and go on \ till you come to the end; then stop." """
为了让一个多行字符串字面量开始和结束于换行符,请将换行写在第一行和最后一行,例如:
1 2 3 4 5 6
let lineBreaks =""" This string starts with a line break. It also ends with a line break. """
一个多行字符串字面量能够缩进来匹配周围的代码。关闭引号(""")之前的空白字符串告诉 Swift 编译器其他各行多少空白字符串需要忽略。然而,如果你在某行的前面写的空白字符串超出了关闭引号(""")之前的空白字符串,则超出部分将被包含在多行字符串字面量中。
let wiseWords ="\"Imagination is more important than knowledge\" - Einstein" // "Imageination is more important than knowledge" - Enistein let dollarSign ="\u{24}"// $,Unicode 标量 U+0024 let blackHeart ="\u{2665}"// ♥,Unicode 标量 U+2665 let sparklingHeart ="\u{1F496}"// 💖,Unicode 标量 U+1F496
Unicode是一个用于在不同书写系统中对文本进行编码、表示和处理的国际标准。它使你可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。Swift 的 String 和 Character 类型是完全兼容 Unicode 标准的。
Unicode 标量
Swift 的 String 类型是基于 Unicode 标量 建立的。Unicode 标量是对应字符或者修饰符的唯一的 21 位数字,例如 U+0061 表示小写的拉丁字母(LATIN SMALL LETTER A)(“a”),U+1F425 表示小鸡表情(FRONT-FACING BABY CHICK)(“🐥”)。
请注意,并非所有 21 位 Unicode 标量值都分配给字符,某些标量被保留用于将来分配或用于 UTF-16 编码。已分配的标量值通常也有一个名称,例如上面示例中的 LATIN SMALL LETTER A 和 FRONT-FACING BABY CHICK。
可扩展的字形群集
每一个 Swift 的 Character 类型代表一个可扩展的字形群。而一个可扩展的字形群构成了人类可读的单个字符,它由一个或多个(当组合时) Unicode 标量的序列组成。
举个例子,字母 é 可以用单一的 Unicode 标量 é(LATIN SMALL LETTER E WITH ACUTE, 或者 U+00E9)来表示。然而一个标准的字母 e(LATIN SMALL LETTER E 或者 U+0065) 加上一个急促重音(COMBINING ACTUE ACCENT)的标量(U+0301),这样一对标量就表示了同样的字母 é。 这个急促重音的标量形象的将 e 转换成了 é。
在这两种情况中,字母 é 代表了一个单一的 Swift 的 Character 值,同时代表了一个可扩展的字形群。在第一种情况,这个字形群包含一个单一标量;而在第二种情况,它是包含两个标量的字形群:
1 2 3
let eAcute: Character="\u{E9}"// é let combinedEAcute: Character="\u{65}\u{301}"// e 后面加上 ́ // eAcute 是 é, combinedEAcute 是 é
可扩展的字形集是一个将许多复杂的脚本字符表示为单个字符值的灵活方式。例如,来自朝鲜语字母表的韩语音节能表示为组合或分解的有序排列。在 Swift 都会表示为同一个单一的 Character 值:
1 2 3
let precomposed: Character="\u{D55C}"// 한 let decomposed: Character="\u{1112}\u{1161}\u{11AB}"// ᄒ, ᅡ, ᆫ // precomposed 是 한, decomposed 是 한
可拓展的字符群集可以使包围记号(例如 COMBINING ENCLOSING CIRCLE 或者 U+20DD)的标量包围其他 Unicode 标量,作为一个单一的 Character 值:
1 2
let enclosedEAcute: Character="\u{E9}\u{20DD}" // enclosedEAcute 是 é⃝
地域性指示符号的 Unicode 标量可以组合成一个单一的 Character 值,例如 REGIONAL INDICATOR SYMBOL LETTER U(U+1F1FA)和 REGIONAL INDICATOR SYMBOL LETTER S(U+1F1F8):
1 2
let regionalIndicatorForUS: Character="\u{1F1FA}\u{1F1F8}" // regionalIndicatorForUS 是 🇺🇸
计算字符数量
如果想要获得一个字符串中 Character 值的数量,可以使用 count 属性:
1 2 3
let unusualMenagerie ="Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪" print("unusualMenagerie has \(unusualMenagerie.count) characters") // 打印输出“unusualMenagerie has 40 characters”
注意在 Swift 中,使用可拓展的字符群集作为 Character 值来连接或改变字符串时,并不一定会更改字符串的字符数量。
let greeting ="Guten Tag!" greeting[greeting.startIndex] // G greeting[greeting.index(before: greeting.endIndex)] // ! greeting[greeting.index(after: greeting.startIndex)] // u let index = greeting.index(greeting.startIndex, offsetBy: 7) greeting[index] // a
let greeting ="Hello, world!" let index = greeting.firstIndex(of: ",") ?? greeting.endIndex let beginning = greeting[..<index] // beginning 的值为 "Hello"
// 把结果转化为 String 以便长期存储。 let newString =String(beginning)
let quotation ="We're a lot alike, you and I." let sameQuotation ="We're a lot alike, you and I." if quotation == sameQuotation { print("These two strings are considered equal") } // 打印输出“These two strings are considered equal”
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(_:) 方法来计算话剧中第一幕的场景数:
1 2 3 4 5 6 7 8
var act1SceneCount =0 for scene in romeoAndJuliet { if scene.hasPrefix("Act 1 ") { act1SceneCount +=1 } } print("There are \(act1SceneCount) scenes in Act 1") // 打印输出“There are 5 scenes in Act 1”
相似地,你可以用 hasSuffix(_:) 方法来计算发生在不同地方的场景数:
1 2 3 4 5 6 7 8 9 10 11
var mansionCount =0 var cellCount =0 for scene in romeoAndJuliet { if scene.hasSuffix("Capulet's mansion") { mansionCount +=1 } elseif scene.hasSuffix("Friar Lawrence's cell") { cellCount +=1 } } print("\(mansionCount) mansion scenes; \(cellCount) cell scenes") // 打印输出“6 mansion scenes; 2 cell scenes”