Swift 入门(一 - 基础语法)
常量和变量
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
var x = 0.0, y = 0.0, z = 0.0
类型注解
var welcomeMessage: String
var red, green, blue: Double
一般来说很少需要写类型注解。如果在声明常量或变量的时候赋来了一个初始值,Swift可以推断出这个量的类型。
变量和常量的命名
可以包含任何字符,包括Unicode字符,但不能数学符号、箭头,保留的(或非法的)Unicode码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。
let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"
一旦确定了常量或者变量的类型,就不能使用相同的名字在此进行声明,或者改变其存储的值的类型。同时,也不能讲常量和变量进行互转。
如果你需要使用与 Swift 保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome 现在是 "Bonjour!"
let languageName = "Swift"
languageName = "Swift++"
// 这会报编译时错误 - languageName 不可改变
输出常量和变量
print(friendlyWelcome)
// 输出“Bonjour!
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// 输出“The current value of friendlyWelcome is Bonjour!”
print(_:separator:terminator:)
是一个用来输出一个或多个值到适当输出区的全局函数。如果你用 Xcode,print(_:separator:terminator:)
将会输出内容到“console”面板上。separator
和 terminator
参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给 terminator
参数--例如,print(someValue, terminator:"")
。
分号
Swift 并不强制要求你在每条语句的结尾处使用分号,但打算在同一行内写多条独立语句时,必须要用分号。
let cat = "🐱"; print(cat)
// 输出“🐱”
整数
let minValue = UInt8.min // minValue 为 0,是 UInt8 类型
let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型
UInt:无符号类型
浮点数
Double
表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。Float
表示32位浮点数。精度要求不高的话可以使用此类型。
Double
精确度很高,至少有15位数字,而Float
只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择Double
。
类型安全和类型判断
let meaningOfLife = 42
// meaningOfLife 会被推测为 Int 类型
let pi = 3.14159
// pi 会被推测为 Double 类型
let anotherPi = 3 + 0.14159
// anotherPi 会被推测为 Double 类型
数值型字面量
let decimalInteger = 17
let binaryInteger = 0b10001 // 二进制的17
let octalInteger = 0o21 // 八进制的17
let hexadecimalInteger = 0x11 // 十六进制的17
1.25e2
表示 1.25 × 10^2,等于125.0
。1.25e-2
表示 1.25 × 10^-2,等于0.0125
。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
数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量:
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
数值型类型转换
整数转换
let cannotBeNegative: UInt8 = -1
// UInt8 类型不能存储负数,所以会报错
let tooBig: Int8 = Int8.max + 1
// Int8 类型不能存储超过最大值的数,所以会报错
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
现在两个数字的类型都是 UInt16
,可以进行相加。目标常量 twoThousandAndOne
的类型被推断为 UInt16
,因为它是两个 UInt16
值的和。
整数和浮点数转换
必须显式制定类型:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159,所以被推测为 Double 类型
let integerPi = Int(pi)
// integerPi 等于 3,所以被推测为 Int 类型
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 4.75
会变成 4
,-3.9
会变成 -3。
结合数字类常量和变量不同于结合数字类字面量。字面量
3
可以直接和字面量0.14159
相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。
类型别名
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 现在是 0
布尔值
let orangesAreOrange = true
let turnipsAreDelicious = false
元组
let http404Error = (404, "Not Found")
// http404Error 的类型是 (Int, String),值是 (404, "Not Found")let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 输出“The status code is 404”
print("The status message is \(statusMessage)")
// 输出“The status message is Not Found”let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// 输出“The status code is 404”print("The status code is \(http404Error.0)")
// 输出“The status code is 404”
print("The status message is \(http404Error.1)")
// 输出“The status message is Not Found”let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)")
// 输出“The status code is 200”
print("The status message is \(http200Status.description)")
// 输出“The status message is OK”
如果你的数据结构比较复杂,不要使用元组,用类或结构体去建模。
可选类型
使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示两种可能: 或者有值, 你可以解析可选类型访问这个值, 或者根本没有值。
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
因为该构造器可能会失败,所以它返回一个可选类型(optional)Int
,而不是一个 Int
。一个可选的 Int
被写作 Int?
而不是 Int
。问号暗示包含的值是可选类型,也就是说可能包含 Int
值也可能不包含值。(不能包含其他任何值比如 Bool
值或者 String
值。只能是 Int
或者什么都没有。)
nil
var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值
nil
不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil
if 语句以及强制解析
if convertedNumber != nil {print("convertedNumber contains some integer value.")
}
// 输出“convertedNumber contains some integer value.”if convertedNumber != nil {print("convertedNumber has an integer value of \(convertedNumber!).")
}
// 输出“convertedNumber has an integer value of 123.”
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!
)来获取值。
可选绑定
使用可选绑定 来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 if
和 while
语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。
if let constantName = someOptional {statements
}
if let actualNumber = Int(possibleNumber) {print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {print("\'\(possibleNumber)\' could not be converted to an integer")
}
// 输出“'123' has an integer value of 123”
这段代码可以被理解为:如果 Int(possibleNumber)
返回的可选 Int
包含一个值,创建一个叫做 actualNumber
的新常量并将可选包含的值赋给它。
如果转换成功,actualNumber
常量可以在 if
语句的第一个分支中使用。它已经被可选类型 包含的 值初始化过,所以不需要再使用 !
后缀来获取它的值。在这个例子中,actualNumber
只被用来输出转换结果。
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {print("\(firstNumber) < \(secondNumber) < 100")
}
// 输出“4 < 42 < 100”if let firstNumber = Int("4") {if let secondNumber = Int("42") {if firstNumber < secondNumber && secondNumber < 100 {print("\(firstNumber) < \(secondNumber) < 100")}}
}
// 输出“4 < 42 < 100”
在
if
条件语句中使用常量和变量来创建一个可选绑定,仅在if
语句的句中(body
)中才能获取到值。相反,在guard
语句中使用常量和变量来创建一个可选绑定。
隐式解析可选类型
有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(String?
)改成感叹号(String!
)来声明一个隐式解析可选类型。
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感叹号
如果在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。
if assumedString != nil {print(assumedString!)
}
// 输出“An implicitly unwrapped optional string.”
if let definiteString = assumedString {print(definiteString)
}
// 输出“An implicitly unwrapped optional string.”
如果一个变量之后可能变成
nil
的话请不要使用隐式解析可选类型。如果需要在变量的生命周期中判断是否是nil
的话,请使用普通可选类型。
错误处理
do {try canThrowAnError()// 没有错误消息抛出
} catch {// 有一个错误消息抛出
}
func makeASandwich() throws {// ...
}do {try makeASandwich()eatASandwich()
} catch SandwichError.outOfCleanDishes {washDishes()
} catch SandwichError.missingIngredients(let ingredients) {buyGroceries(ingredients)
}
在此例中,makeASandwich()
(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为 makeASandwich()
抛出错误,函数调用被包裹在 try
表达式中。将函数包裹在一个 do
语句中,任何被抛出的错误会被传播到提供的 catch
从句中。
如果没有错误被抛出,eatASandwich()
函数会被调用。如果一个匹配 SandwichError.outOfCleanDishes
的错误被抛出,washDishes()
函数会被调用。如果一个匹配 SandwichError.missingIngredients
的错误被抛出,buyGroceries(_:)
函数会被调用,并且使用 catch
所捕捉到的关联值 [String]
作为参数。
断言和先决条件
断言和先决条件是在运行时所做的检查。可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。如果断言或者先决条件中的布尔条件评估的结果为 true(真),则代码像往常一样继续执行。如果布尔条件评估结果为 false(假),程序的当前状态是无效的,则代码执行结束,应用程序中止。
let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// 因为 age < 0,所以断言会触
如果不需要断言信息,可以忽略:
assert(age >= 0)
如果代码已经检查了条件,可以使用 assertionFailure(_:file:line:)
函数来表明断言失败了:
if age > 10 {print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {print("You can ride the ferris wheel.")
} else {assertionFailure("A person's age can't be less than zero.")
}
强制执行先决条件
当一个条件可能为假,但是继续执行代码要求条件必须为真时,需要使用先决条件。例如使用先决条件来检查下标越界,或者检查是否将一个正确的参数传给函数。
可以使用全局precondition(_:_:file:line:)
函数来写一个先决条件。向这个函数传入一个结果为 true
或者 false
的表达式以及一条信息,当表达式的结果为 false
的时候这条信息会被显示:
// 在一个下标的实现里...
precondition(index > 0, "Index must be greater than zero.")