本文内容主要是 Swift 官方文档 LanguageGuide 第一节 The Basics 的部分翻译,主要用于让读者快速了解 Swift 语法及自我备忘。如需要更详细的介绍,请查阅原文档。 我们假设读者学习过某种常见的编程语言(不论是 C、C++、Java、JavaScript,Python),对编程语言中常见的名词概念有所了解。
TOC
常量与变量
声明
在 Swift 中可以用关键字 let
和 var
来声明常量与变量。
let maximumNumberOfLoginAttempts = 10 // 定义常量
var currentLoginAttempt = 0 // 定义变量
var x = 0,y = 0,z = 0 // 连续定义多个变量
需要注意到,Swift 的常量关键字不是 const
,这对于 JavaScript 开发者来说可能会有些奇怪(在 JavaScript 中使用 const
定义常量,而 let
和 var
定义的都是变量)
类型标记
在定义变量或常量的时候,可以为其添加类型标记,从而明确所能存储的值的类型。 例如:
var welcomeMessage: String // welcomeMessage存储String类型的数据
var red, green, blue: Double // 连续声明多个变量为Double类型
此外,和一些强类型语言一样,如果在定义变量时直接进行初始化,Swift 可以自动推导变量的类型。
命名
和大多数现代编程语言类似,Swift 常量和变量的名称可以包含几乎所有的字符(包括 Unicode 字符):
let π = 3.14159 // Unicode
let 你好 = "你好世界" // 中文
let 🐶🐮 = "dogcow" // emoji
常量和变量的名字不能含有:
- 空白字符,例如空格、tab、CR、LF 等
- 数学符号,例如加减乘除括号
- 箭头
- Unicode 的私有字符
- 制表符
除此之外变量名不能直接使用 Swift 保留的关键字、也不能由数字开头。
变量的输出
使用 print(_:separator:terminator:)
函数可以打印变量的值:
var friendlyWelcome = "Bonjour!"
print(friendlyWelcome) // 将输出"Bonjour!"
这个函数将变量的值打印到对应的输出,在 Xcode 中将会打印在 console 面板。
在输出时候可以使用「字符串插值」的方式进行格式化,例如:
var friendlyWelcome = "Bonjour!"
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// 将输出"The current value of friendlyWelcome is Bonjour!"
注释
Swift 的注释和大多数语言一样
// 这是单行注释
/*
这是多行注释
可以有多行
*/
分号
与很多其他类 C 语言不同,Swift 并不要求在每个语句后添加分号;
,当然如果想加也可以。
如果将多个语句写在了同一行内,此时必须用分号隔开,例如:
let cat = "🐱"; print(cat) // 必须用分号隔开
// 输出 "🐱"
整数
Swift 提供了等多种整数类型,包括有符号或无符号的 8、16、32、64 位整数。这些类型以类似 C 语言的方式命名,例如 8 位无符号整数记为 UInt8
,而 32 位有符号整数记为 Int32
。
整数边界
有时候我们需要知道某种整数类型能表示的数字范围,而各个整数类型都提供了两个属性来记录能表示的最大值和最小值:
let minValue = UInt8.min // minValue 等于 0, 类型为 UInt8
Int 和 UInt
大多数情况下,我们并不需要指定整数类型的长度,因此 Swift 提供了额外的整数类型 Int
,其大小由当前操作系统的字长决定:
- 在 32 位系统中,
Int
具有和Int32
一样的大小 - 在 64 位系统中,
Int
具有和Int64
一样的大小
UInt
的表现类似。
浮点数
Float
代表 32 位单精度浮点数Double
代表 64 位双精度浮点数
Swift 是一门强类型的语言,这意味着如果你的代码需要一个 String
类型的参数,就不能将 Int
类型的值传入。
由于 Swift 是强类型的,它会在编译代码时进行类型检查,这使得开发者能够在开发过程中就发现并修复一些可能的错误。
当然,类型检查不代表要显示声明所有变量的类型。如果没有指定变量的类型,Swift 使用「类型推导」来推断。自动的类型推导使得变量依旧有明确的类型,但代码中需要书写的类型声明比 C 或者 Objective-C 少得多,这在定义一个带初始值的常量或变量时将会很有用,例如:
let meaningOfLife = 42 // meaningOfLife的类型推导为Int
let pi = 3.14159 // pi的类型推导为Double
数字字面量
数字字面量有几种形式:
-
二进制数,以
0b
为前缀 -
八进制数,以
0o
为前缀 -
十六进制数,以
0x
为前缀
let decimalInteger = 17
let binaryInteger = 0b10001 // 17的二进制表示
let octalInteger = 0o21 // 17的八进制表示
let hexadecimalInteger = 0x11 // 17的十六进制表示
浮点数字面量可以是十进制(无前缀)或者十六进制(以 0x
为前缀),且小数点两边必须有数字存在。
十进制浮点数还可以有_指数_(非必须)部分,使用一个大写或小写的 e
来表示;而十六进制浮点数必须有指数部分,使用大写或小写的 p
表示。
对于带指数 exp
的十进制浮点数,基数需要乘以 $10^{exp}$:
1.25e2
表示 $1.25*10^undefined$ 或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
数字字面量可以包含一些额外的格式字符来提升可读性。整数和浮点数都可以使用额外的 0 来缩进,也可以包含下划线使其易读。
任何格式字符都不影响字面量的实际值:
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
数字类型的转换
一般来讲,应该在你的代码中使用 Int 类型的变量存储整数。其他的类型只在特定的需要时使用,例如从额外数据源中读取指定字长的数据,又或是为了优化性能和内存使用。
整数的转换
不同的整数类型能存储不同范围的数字,例如 Int8
存储 -128
到 127
之间的整数。如果想要存储超出范围的值将会在编译时报错:
let cannotBeNegative: UInt8 = -1
// UInt8不能存储负数,因此会报错
let tooBig: Int8 = Int8.max + 1
// Int8不能存储超出范围的值,因此也会报错
为了将一个整数类型转化为另一个类型,需要从已有值创建一个指定类型的新数值。在下面的例子中,常量 twoThousand
是 UInt16
类型的,而常量 one 是 UInt8
类型。由于二者类型不同,他们不能直接相加。因此,这个例子中调用了 UInt16(one)
来创建并使用 one 初始化了一个新的 UInt16
值,然后使用这个值来运算:
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
因为是两个 UInt16
值相加,所以得到的常量值 twoThousandAndOne
的类型被推导为 UInt16
。
SomeType(ofInitialValue)
是调用 Swift 类型构造器并传入初始值的默认方式,在底层实现中,UInt16
有一个接收 UInt8
值的构造器,从而根据一个已有的 UInt8
值构造新的 UInt16
值。这同时也意味着你不能传入任意类型,而是只能传入 UInt16
构造器支持的类型。
整数和浮点数的转换
整数和浮点数之间的转换必须显式进行:
let three = 3 // 推导类型为Int
let pointOneFourOneFiveNine = 0.14159 // 推导类型为Double
let pi = Double(three) + pointOneFourOneFiveNine
// pi等于3.14159,推导类型为Double
将浮点数转化为整数也需要显式进行,任何整数类型都可以使用 Double
或 Float
值初始化:
let integerPi = Int(pi)
// integerPi等于3, 类型为Int
这种方式将浮点数转化为整数将始终将浮点数「截断」,这意味着 4.75
将转化为 4
,而 -3.9
将转化为 -3
。
变量之间的运算和字面量之间直接运算不同,数字字面量
3
可以直接与0.14159
相加,因为数字字面量的具体类型还没有确定。
类型别名
类型别名可以为已有类型定义一个新的名字,你可以使用关键字 typealias
来定义别名。
类型别名在你想要给已有类型一个在上下文中更合适的名字,例如处理外部数据源时:
typealias AudioSample = UInt16
一旦你定义了一个类型别名,就可以在任何你可能使用已有名字的地方用它:
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound现在为0
布尔值
Swift 中的布尔类型名为 Bool
。布尔值字面量为 true
和 false
:
let orangesAreOrange = true
let turnipsAreDelicious = false
由于是从布尔值字面量初始化,常量 orangesAreOrange
和 turnipsAreDelicious
的类型被推导为 Bool
。
布尔值在 if
这样的条件语句中会非常有用:
if turnipsAreDelicious {
print("Mmm, tasty turnips!")
} else {
print("Eww, turnips are horrible.")
}
// Prints "Eww, turnips are horrible."
条件语句在控制流一节中详细描述。
Swift 的类型安全检查会阻止非布尔值作为布尔值使用。下面的例子将会抛出编译错误:
let i = 1
if i {
// this example will not compile, and will report an error
}
但是,另一个例子是可以的:
let i = 1
if i == 1 {
// this example will compile successfully
}
i == 1
的比较结果是 Bool
类型的,因此第二个例子能够通过类型检查。类似 i == 1
的比较在 Basic Operators 中讨论。
元组(tuple)
元组是将多个值组合成一个复合值。元组中的值可以是任意类型且不需要相同。
在下面这个例子中,(404, "Not Found")
是一个描述 HTTP 状态码的元组,这个状态码将在你请求了不存在的网页时返回:
let http404Error = (404, "Not Found")
// http404Error的类型是(Int, String)
你可以将元组的内容解构到不同的常量或变量中:
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// Prints "The status code is 404"
print("The status message is \(statusMessage)")
// Prints "The status message is Not Found"
如果只需要元组中的某些值,可以在解构时使用下划线来忽略不想要的部分:
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// Prints "The status code is 404"
元组内部的元素还可以通过从 0
开始的下标访问:
print("The status code is \(http404Error.0)")
// Prints "The status code is 404"
print("The status message is \(http404Error.1)")
// Prints "The status message is Not Found"
你也可以在定义元组时为其中的元素命名:
let http200Status = (statusCode: 200, description: "OK")
如果为元组中的元素指定了名称,则可以通过这些名称来访问:
print("The status code is \(http200Status.statusCode)")
// Prints "The status code is 200"
print("The status message is \(http200Status.description)")
// Prints "The status message is OK"
元组在作为函数返回值使用时特别有用。一个尝试获取网页的函数可能返回一个 (Int, String)
的元组来描述成功与否。关于更多信息,可以看 Functions with Multiple Return Values。
元组适合组合几个相关的值,但不适合创建一个复杂的数据结构。如果数据结构更为复杂,请使用一个 class 或者 structure 而不是元组。详情查阅 Structures and Classes.
可选值
当一个值可能缺失时,你可以使用可选值,一个可选值代表着两个可能:要么是一个可以提取使用的值,要么没有任何值。
这里有个关于可选值使用的例子。Swift 的 Int
类型有一个能试着将 String
值转化为 Int
值的构造器,然而并不是所有的字符串都能转化为整数。例如字符串 "123"
可以转换为数字的 123
,但字符串 "hello, world"
显然不能转换成一个整数。
下面的例子使用构造器来尝试将 String
类型转换为 Int
类型:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber的类型为"Int?"
因为构造器可能会失败,所以它返回的是一个「可选」的 Int
值,而不是一个 Int
值。一个可选的 Int
值被记为 Int?
而不是 Int
。问号表示其值可选,意味着可能是一个 Int
值也可能没有任何值。
nil
你可以将一个可选值赋予代表没有值的一个特殊的值 nil
:
var serverResponseCode: Int? = 404
// serverResponseCode包含一个Int类型的值404
serverResponseCode = nil
// serverResponseCode现在没有值
如果你定义了一个可选变量而没有提供初始值,这个变量将会自动的初始化为 nil
:
var surveyAnswer: String?
// surveyAnswer is automatically set to nil
If 语句和强制提取
你可以使用 if
语句将变量和 nil
进行比较,从而确定一个可选值是否包含一个值。可以使用的比较运算符有「等于」(==
) 和「不等于」(!=
)。
如果一个可选值包含一个值,那么可以认为是「不等于」nil
:
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// Prints "convertedNumber has an integer value of 123."
如果你确定可选值包含了一个值,你可以在通过在可选值最后添加感叹号 (!
) 来强制提取这个值。
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// Prints "convertedNumber has an integer value of 123."
可选值绑定
通过「可选值绑定」的方式可以在可选值有值的时候创建一个临时的变量。if
语句和 while
语句都支持这样的方式检查可选值,并提取其中的值。
使用 if
语句的时候大概是这样:
if let constantName = someOptional {
statements
}
之前的 possibleNumber
的例子可以按以下方式重写,并不再需要强制提取:
if let actualNumber = Int(possibleNumber) {
print("The string \"\(possibleNumber)\" has an integer value of \(actualNumber)")
} else {
print("The string \"\(possibleNumber)\" could not be converted to an integer")
}
// Prints "The string "123" has an integer value of 123"
可以同时使用多个绑定并和布尔值条件一起用,只需要用逗号隔开。若任意可选值为 nil
或者布尔值为 false
,则整个 if
语句的条件就认为是 false
。下面的两段代码是等价的:
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
// Prints "4 < 42 < 100"
if let firstNumber = Int("4") {
if let secondNumber = Int("42") {
if firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
}
}
// Prints "4 < 42 < 100"
异常处理
给函数声明加上 throws
关键字后,就意味着这个函数可能会抛出异常。在调用这样的函数时,应当额外使用 try
关键字。
如果异常没有被处理,Swift 会自动将异常抛出当前作用域,直到这个异常被 catch
语句处理。
do {
try canThrowAnError()
// 如果没有异常,这部分将被执行
} catch {
// 如果抛出异常,这部分将被执行
}
一个 do
语句创建了一个新的容器作用域,使得异常能被分发到一个或多个 catch
语句。这有一个例子:
func makeASandwich() throws {
// ...
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
在这个例子中,makeASandwich()
函数会在没有干净的盘子或者没有调味料时抛出一个异常。因为 makeASandwich()
函数能抛出异常,因此需要使用 try
关键字。然后通过用 do
语句包裹函数调用,任何抛出的异常都会被分发到提供的 catch
语句。
异常处理相关详细内容在官方文档 Error Handling 一节。