Skip to content

Swift语言基础

Published: at 21:47

本文内容主要是 Swift 官方文档 LanguageGuide 第一节 The Basics 的部分翻译,主要用于让读者快速了解 Swift 语法及自我备忘。如需要更详细的介绍,请查阅原文档。 我们假设读者学习过某种常见的编程语言(不论是 C、C++、Java、JavaScript,Python),对编程语言中常见的名词概念有所了解。

TOC

常量与变量

声明

在 Swift 中可以用关键字 letvar 来声明常量与变量。

let maximumNumberOfLoginAttempts = 10    // 定义常量
var currentLoginAttempt = 0            // 定义变量
var x = 0,y = 0,z = 0                // 连续定义多个变量

需要注意到,Swift 的常量关键字不是 const,这对于 JavaScript 开发者来说可能会有些奇怪(在 JavaScript 中使用 const 定义常量,而 letvar 定义的都是变量)

类型标记

在定义变量或常量的时候,可以为其添加类型标记,从而明确所能存储的值的类型。 例如:

var welcomeMessage: String        // welcomeMessage存储String类型的数据
var red, green, blue: Double        // 连续声明多个变量为Double类型

此外,和一些强类型语言一样,如果在定义变量时直接进行初始化,Swift 可以自动推导变量的类型。

命名

和大多数现代编程语言类似,Swift 常量和变量的名称可以包含几乎所有的字符(包括 Unicode 字符):

let π = 3.14159        // Unicode
let 你好 = "你好世界"    // 中文

let 🐶🐮 = "dogcow"    // emoji

常量和变量的名字不能含有:

除此之外变量名不能直接使用 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,其大小由当前操作系统的字长决定:

UInt 的表现类似。

浮点数

Swift 是一门强类型的语言,这意味着如果你的代码需要一个 String 类型的参数,就不能将 Int 类型的值传入。

由于 Swift 是强类型的,它会在编译代码时进行类型检查,这使得开发者能够在开发过程中就发现并修复一些可能的错误。

当然,类型检查不代表要显示声明所有变量的类型。如果没有指定变量的类型,Swift 使用「类型推导」来推断。自动的类型推导使得变量依旧有明确的类型,但代码中需要书写的类型声明比 C 或者 Objective-C 少得多,这在定义一个带初始值的常量或变量时将会很有用,例如:

let meaningOfLife = 42    // meaningOfLife的类型推导为Int
let pi = 3.14159            // pi的类型推导为Double

数字字面量

数字字面量有几种形式:

let decimalInteger = 17
let binaryInteger = 0b10001       // 17的二进制表示
let octalInteger = 0o21           // 17的八进制表示
let hexadecimalInteger = 0x11     // 17的十六进制表示

浮点数字面量可以是十进制(无前缀)或者十六进制(以 0x 为前缀),且小数点两边必须有数字存在。

十进制浮点数还可以有_指数_(非必须)部分,使用一个大写或小写的 e 来表示;而十六进制浮点数必须有指数部分,使用大写或小写的 p 表示。

对于带指数 exp 的十进制浮点数,基数需要乘以 $10^{exp}$:

对于带指数 exp 的十六进制浮点数,基数需要乘以 $2^{exp}$:

以下几个浮点数字面量都等于十进制的 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 存储 -128127 之间的整数。如果想要存储超出范围的值将会在编译时报错:


let cannotBeNegative: UInt8 = -1


// UInt8不能存储负数,因此会报错
let tooBig: Int8 = Int8.max + 1
// Int8不能存储超出范围的值,因此也会报错

为了将一个整数类型转化为另一个类型,需要从已有值创建一个指定类型的新数值。在下面的例子中,常量 twoThousandUInt16 类型的,而常量 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

将浮点数转化为整数也需要显式进行,任何整数类型都可以使用 DoubleFloat 值初始化:

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。布尔值字面量为 truefalse

let orangesAreOrange = true
let turnipsAreDelicious = false

由于是从布尔值字面量初始化,常量 orangesAreOrangeturnipsAreDelicious 的类型被推导为 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 一节。