Swift 4.0 编程语言(一)

关于 Swift

重要这个文档所包含的准备信息, 是关于开发的 API 和技术的。这个信息可能会改变, 根据这个文档开发的软件, 应该使用最新的操作系统软件进行测试。

Swift 开发软件是极其出色的, 不管是为了手机, 桌面, 服务器还是其他什么。它是一门安全, 快速和交互式的编程语言。编译器和语言都经过了优化。

Swift 对新手开发者很友好。它是一门工业质量级别的编程语言, 和脚本语言一样富有表现力和令人愉悦。在 playground 中写 Swift 代码让你所见即所得, 完全不需要编译和运行程序。

Swift 采用了现代编程模式, 定义了大量的常见编程错误:

变量在使用之前总是需要初始化。

数组所以会判断越界错误。

整数会判断是否溢出。

可选类型确保空值被显式处理。

内存会自动管理。

错误处理允许从失败恢复。

Swift 代码被编译和优化以充分使用现代硬件。集合安全性和速度使得 Swift 成为一个优秀的选择, 可以编写从 “Hello, world!” 到整个操作系统。

Swift 使用现代、轻量级的语法, 集合了强有力的类型推断和模式匹配, 让复杂的想法可以用一个清晰和简单的方式来实现。这样以来, 代码不仅很容易编写, 也容易读取和维护。

Swift 已经面世多年, 它会继续发展新的特性和能力。我们的目标是星辰大海。我们等不及想看看你能创造什么出来了。


版本兼容性


这个教程描述 Swift 4.0, 它是包含在 Xcode 9 中默认的 Swift 版本。你可以使用 Xcode 9 编译使用了 Swift 4 或者 Swift 3 编写的代码。

备注当 Swift 4 编译器使用 Swift 3 代码时, 它标记语言的版本是 3.2—这就意味着你可以使用条件编译块比如like #if swift(>=3.2)来编写代码, 以兼容多个版本的 Swift 编译器。

当你使用 Xcode 9 去编译 Swift 3 代码时, 多数 Swift 4 的功能是不可用的。就是说, 下面的特性只能在 Swift 4 代码中使用:

子字符串操作会返回子字符串的类型, 而不是。

@objc 属性隐式添加到更少的地方。

在相同的文件里扩展一个类型, 可以访问这个类型的私有成员。

如果你有一个大项目, 分成多个框架, 你可以把你的代码从 Swift 3 移植到 Swift 4, 每次一个框架。

基本介绍


Swift 是为 iOS,macOS,watchOS 和 tvOS 应用开发的一门新的开发语言,尽管如此,Swift 很多部分都跟你以往从事 C 和Objective-C 开发经验很相似。

基于 C 和 Objective-C 的数据类型,Swift 提供了自己的版本,包括 Int, Double,Float,Bool 和 String。它同时也提供了三种集合类型,Array,Set 和 Dictionary。

跟C类似,Swift 通过变量名来存取值。Swift 还大量使用常量,这里的常量比C的常量更加强大。常量使用贯穿 Swift,用来让代码更加安全和容易推断,特别是在你不想变量值发生改变的时候。

除了基本类型,Swift 还引进了 Objective-C 中没有的高级类型,比如元组。元组让你可以创建和传递多值。你可以在一个函数里返回元组来作为一个单独的混合值。

Swift 还引进了可选类型,用来处理缺值的情况。可选的意思是‘这里有一个值,它等于x’ 或者‘这里没有任何值’。用可选值跟Objective-C 里使用 nil 指针有点像,不过可选类型可以服务任何类型,不仅仅是类类型。可选类型不仅仅安全而且更容易表达意思,它是 Swift 最重要特性里的核心特性。

Swift 是一门类型安全的语言,这也就意味着这门语言可以让你更清晰的知道代码使用的值类型。如果你的代码需要一个 String,类型安全会保护你不至于传递一个 Int 类型。同样,类型安全也会保护你,让你不会把非可选类型传递给需要可选类型的代码块。类型安全让你在开发阶段尽快发现问题,修复错误。

Swift 是为 iOS,macOS,watchOS 和 tvOS 应用开发的一门新的开发语言,尽管如此,Swift 很多部分都跟你以往从事 C 和Objective-C 开发经验很相似。

基于 C 和 Objective-C 的数据类型,Swift 提供了自己的版本,包括 Int, Double,Float,Bool 和 String。它同时也提供了三种集合类型,Array,Set 和 Dictionary。

跟C类似,Swift 通过变量名来存取值。Swift 还大量使用常量,这里的常量比C的常量更加强大。常量使用贯穿 Swift,用来让代码更加安全和容易推断,特别是在你不想变量值发生改变的时候。

除了基本类型,Swift 还引进了 Objective-C 中没有的高级类型,比如元组。元组让你可以创建和传递多值。你可以在一个函数里返回元组来作为一个单独的混合值。

Swift 还引进了可选类型,用来处理缺值的情况。可选的意思是‘这里有一个值,它等于x’ 或者‘这里没有任何值’。用可选值跟Objective-C 里使用 nil 指针有点像,不过可选类型可以服务任何类型,不仅仅是类类型。可选类型不仅仅安全而且更容易表达意思,它是 Swift 最重要特性里的核心特性。

Swift 是一门类型安全的语言,这也就意味着这门语言可以让你更清晰的知道代码使用的值类型。如果你的代码需要一个 String,类型安全会保护你不至于传递一个 Int 类型。同样,类型安全也会保护你,让你不会把非可选类型传递给需要可选类型的代码块。类型安全让你在开发阶段尽快发现问题,修复错误。

1.常量和变量

常量和变量是一个有特定类型的值,带有一个名字。常量值一旦确定就不能改变,变量在可用随时改变其值。

2.定义常量和变量

常量和变量必须要在使用前定义,常量使用let关键字,变量使用var关键字。下面是一个例子来展示如何使用常量和变量,这个例子是跟踪用户尝试的登录次数。

let maximumNumberOfLoginAttempts = 10

var currentLoginAttempt = 0

这段代码可以这样解读:

定义个常量 maximumNumberOfLoginAttempts ,给它一个值10.然后定义一个变量 currentLoginAttempt,给它一个值0.

在这个例子里,最大登录次数定义为一个常量,因为最大登录数不会改变。当前登录数定义为变量,因为这个值随着用户的登录尝试会逐渐增长。

你也可以定义多个常量或变量在一行,用逗号分开即可:

var x = 0.0, y = 0.0, z = 0.0

3.类型注释

当定义常量或者变量的时候,你可以提供一个类型注释,这样可以更清楚的知道存储类型是什么。在变量名或者常量名后面加一个冒号,然后一个空格,然后是要使用的类型。

这个例子为变量 welcomeMessage 提供一个类型解释,来说明这个变量可以存储 String 值:

var welcomeMessage: String

这段代码可以理解为:

定义个变量叫 welcomeMessage ,它的类型是 String.

welcomeMessage 变量现在可以存储任何的字符串:

welcomeMessage = "Hello"

你可以在一行定义多个相关的相同类型的变量,用逗号分开,然后再最后加上类型注释:

var red, green, blue: Double

4.常量和变量命名

常量和变量命名可以包含几乎任何的字符,包括Unicode字符:

let π = 3.14159

let 你好 = "你好世界"

let 🐶🐮 = "dogcow"

常量和变量命名不能包含空格字符,数学符号,箭头,私有(无效)的 Unicode 字符码,或者-等。也不能以数字开始,虽然数字可以出现在名字里的任何地方。

一旦你给常量或者变量确定了类型,你就不能用同样的名字来重定义它们,或者改变他们存储的值类型。你也不能把常量改为变量,或者把变量变成常量。

你可以把常量的值改变成同样类型的其他的值。这个例子里,变量 friendlyWelcome 的值从”Hello!” 变成 “Bonjour!”:

var friendlyWelcome = "Hello!"

friendlyWelcome = "Bonjour!"

// friendlyWelcome is now "Bonjour!"

与变量不同,常量值一旦确定就不能再改变。如果尝试改变编译器会报错:

let languageName = "Swift"

languageName = "Swift++"

// 这是一个编译期错误: languageName 不能修改。

5.打印常量和变量

你可以使用 print(_:separator:terminator:) 函数打印常量或者变量的当前值:

print(friendlyWelcome)

// 打印 "Bonjour!"

print(:separator:terminator:) 是一个全局函数,可以打印一个或者多个值。在 Xcode 里,例如,print(:separator:terminator:) 打印结果会出现在控制台里。separator 和 terminator 参数都有默认值, 所以你可以忽略他们。 默认的, 这个函数打印完会加上换行符。 如果不想打印后换行, 传入一个空字符串作为终止—例如, print(someValue, terminator: “”)

Swift 使用字符串插入在长字符串里插入常量或者变量名, 同时会提示 Swift 去用当前的常量值活变量值来替换它。 用括号包含名字,然后在前面加上反斜杠:

print("The current value of friendlyWelcome is \(friendlyWelcome)")

// 打印 "The current value of friendlyWelcome is Bonjour!"

6.注释

在代码中,把注释加到不执行的文本,作为一个备注或者提醒。代码编译的时候,注释会被编译器忽略。

Swift 注释和 C 语言注释很像,不再赘述。

7.分号

跟其他语言不同,Swift 不要求在每条语句后写分号(;),不过,在一行写很多语句的时候,还是需要带上分号的:

let cat = "🐱"; print(cat)

// 打印 "🐱"

8.整数

Integers 意思是整个数字没有小数,比如 42 和 -23,整数要么是 signed(负数,0,正数) 要么是 unsigned(正数或者0)

Swift 提供8,16,32 和 64位的有符号和无符号的整数。这些整数类型和C语言很像,8位的无符号的整数是 UInt8,32位的有符号正数是Int32.

9.整数边界

你可以用 min 和 max 属性来获取每个整数类型的最小值和最大值:

let minValue = UInt8.min// minValue 等于 0, 类型是 UInt8

let maxValue = UInt8.max// maxValue 等于 255, 类型是 UInt8

10.Int、UInt、浮点数

int:大多数情况下,你不需要指定整数的位数。Swift 提供了额外的整数类型 Int ,这个和当前平台的本地字数一样:

32位机器,Int 等于 Int32

64位机器,Int 等于 Int64

除非你要用规定大小的整数,否则,一般只要用 Int 就可以了。

UInt:Swift 也提供了无符号的整形,UInt,这个和当前平台的本地字数也是一样的:

32位机器,UInt 等于 UInt32

64位机器,UInt 等于 UInt64

浮点数:是带有小数部分的数字,比如 3.14159,0.1 和 -273.15.

浮点数类型比整数类型表达更多的值域,比存在Int中的值更大或者更小。Swift 提供了两个有符号的浮点数类型:

Double 表示64位的浮点数。

Float 表示32位的浮点数。

11.类型安全与推断

Swift 是类型安全的语音。类型安全的语言鼓励你清楚知道自己代码使用的值的类型。如果你的代码需要 String 类型,那么你就不要传递Int 给它。

因为 Swift 是类型安全的,在编译代码的时候类型检查会执行来标记不匹配的类型错误。这让你可以尽可能早的发现代码中的错误。

类型检查帮你避免使用不同类型的时候犯错,然而,这并不意味你必须给所有的常量和变量指定类型。你不需要指定类型,Swift 使用类型推断来推断合适的类型。类型推断使得编译器可以在编译代码的时候自动推断表达式的类型,只是通过简单的检查你提供的值。

因为有类型推断,Swift 对类型声明要求的比其他语言要少的多。常量和变量依然要显示输入,但是大部分指定类型的工作都已经帮你做了。

类型推断在给常量或者变量赋初值的时候尤为有用。这个发生在你声明常量或者变量,并给他们指定(字面量)的时候。所谓的字面量就是指直接出现在你的源码中的值,比如下面例子里的 42 和 3.14159

例如,如果你给一个新常量指定一个字面量是 42,Swift 就会推断你想要这个常量的类型是 Int, 因为你给他初始化一个数字:

let meaningOfLife = 42

// meaningOfLife 推断为 Int 类型

同样,你也不需要给浮点数指定类型,Swift 会推断说你想要一个 Double:

let pi = 3.14159

// pi 推断为 Double 类型

Swift 在推断浮点数的时候总是选择 Double 而不是 Float

如果你合并整数和浮点数在一个表达式中,Double 类型将会被推断出来:

let anotherPi = 3 + 0.14159

// anotherPi 推断为 Double 类型

12.数值字面量

整数字面量这可以写:

十进制数字,没有前缀

二进制数字,用0b做前缀

八进制数字,用0o做前缀

十六进制数字,用0x做前缀

下面所有整数字面量值都是17:

let decimalInteger = 17

let binaryInteger = 0b10001      // 17 in binary notation

let octalInteger = 0o21          // 17 in octal notation

let hexadecimalInteger = 0x11    // 17 in hexadecimal notation

浮点数字面量可以是十进制或者十六进制。在十进制点的两边都必须有数字。十进制浮点数也有个可选的指数,用大小写 e 标明;十六进制浮点数也有指数,用大小写的 p 标明。

用 exp 指数表示十进制数, 基础数字乘以 10exp:

1.25e2 意思是 1.25 x 102, 或者是 125.0.

1.25e-2 意思是 1.25 x 10-2, 或者是 0.0125.

用 exp 指数表示十六进制数, 基础数字乘以 2exp:

0xFp2 意思是 15 x 22, 或者是 60.0.

0xFp-2 意思是 15 x 2-2, 或者是 3.75.

下面所有这些浮点数字面量十进制数都是 12.1875:

let decimalDouble = 12.1875

let exponentDouble = 1.21875e1

let hexadecimalDouble = 0xC.3p0

13.数值类型转换

在你的代码中使用 Int 做为一般用途的整形常量和变量,尽快知道他们是非负数的。日常开发使用默认整形类型意味着整形变量和常量立即可以使用,而且它们匹配整形字面量的推断类型。

特别指定的工作才会使用其他的整形类型,因为指定大小的整形类型需要额外的开销。日常开发中,使用指定大小的类型帮助捕获特定值的溢出,同时记录被使用的数据。

14.整型转换

不同数值类型,存储在常量或变量中的数值范围是不同的。一个 Int8 常量或者变量可以存储 -128 到 127,UInt8 常量或者变量能存储 0 到 255 之间的数值。如果数值不适合指定大小的整形类型,编译后会报错。

let cannotBeNegative: UInt8 = -1

// UInt8 cannot store negative numbers, and so this will report an error

let tooBig: Int8 = Int8.max + 1

// Int8 无法存储超过它能存储的最大值,

// 因此会报错

为了转换一个特定数值类型,先用一个存在的值初始化一个新的想要类型的新数值。下面的例子,常量 twoThousand 的类型是UInt16, 而常量 one 的类型是 UInt8 .他们不能直接相加,因为类型不同。取而代之的是,这个例子调用 UInt16(one)来创建一个新的UInt16,并用 one 初始化它,并在原来的位置使用这个值:

let twoThousand: UInt16 = 2_000

let one: UInt8 = 1

let twoThousandAndOne = twoThousand + UInt16(one)

因为加好两边的值类型都是 UInt16 ,所以加法是进行的。输出常量推断是 UInt16 ,因为它是两个 UInt16 值的和。

SomeType(ofInitialValue) 默认调用 Swift 类型的初始化函数,然后传递一个初始值。这个语句之前, UInt16 有一个构造器接受一个 UInt8 的值,所以这个构造器就用存在的 UInt8 创建了一个新的 UInt16.这里你不能随便传入类型-必须传入 UInt16 构造器接受的值。

15.整数和浮点数转换

整数和浮点数之间的转换必须是显示的:

let three = 3

let pointOneFourOneFiveNine = 0.14159

let pi = Double(three) + pointOneFourOneFiveNine

// pi 等于 3.14159, 推断为 Double 类型

这里,常量 three 用来创建新的 Double 数值,现在加号两边的类型是一样的,所以可以相加。如果没有这里的转换,加法是不允许的。

浮点数转整数也必须是显示进行的。一个整形可以用 Double 或者 Float 的值来初始化:

let integerPi = Int(pi)

// integerPi 等于 3, 类型推断为 Int

用这种方式初始化一个新的整形数值,浮点数会被截断。意思就是 4.75 会变成 4, -3.9 会变成 -3.

16.类型别名

type aliases 为已知类型定一个别名。定义类型别名使用 typealias 关键字。

类型别名在你想通过名字调用一个已知类型的时候很有用,这种名字在上下文中更合适,比如使用指定大小的外部数据时:

typealias AudioSample = UInt16

定义好类型别名后,你可以在任何使用原名的地方使用它:

var maxAmplitudeFound = AudioSample.min

// maxAmplitudeFound 现在是 0

这里,AudioSample 就是 UInt16 的别名。因为是别名,调用 AudioSample.min 实际上就是调用 UInt16.min,这给 maxAmplitudeFound 变量提供一个初始值 0.

17.布尔值

Swift 有个一基础布尔类型, 叫做 Bool. 布尔值用作逻辑调用,因为它只能是 true 或者 false. Swift 提供了两个布尔常量值, true 和 false

let orangesAreOrange = true

let turnipsAreDelicious = false

orangesAreOrange 和 turnipsAreDelicious 的类型被推断是 Bool. 同上面的 Int 和 Double 一样,你不需要声明常量或者变量为 Bool.

布尔值在if 语句这种条件语句中尤其有用:

if turnipsAreDelicious {

print("Mmm, tasty turnips!")

} else {

print("Eww, turnips are horrible.")

}

// 打印 "Eww, turnips are horrible."

Swift 类型安全防止非布尔值被替换成布尔值。下面的例子报一个编译错误:

let i = 1

if i {

// 这个例子不会编译, 它会报错

}

替代的例子如下:

let i = 1

if i == 1 {

// 这个例子可以成功编译

}

i==1 比较的结果是 Bool 类型,所以第二个例子通过了类型检查。

和其他类型安全例子一样,这个方法避免了突发的错误,确保特别代码块推断总是清晰的。

18.元组

Tuples 包含多值到一个单独的组合值。元组里的值可以是任何类型,彼此之间可以是不同类型。

在这个例子中, (404, “Not Found”) 是一个元组,代表 Http 的错误码。一个 Http 错误码是访问网页时网页服务器返回的特殊数值。如果你请求的网页不存在,就会返回 404 Not Found 的错误码。

let http404Error = (404, "Not Found")

// http404Error 是 (Int, String)类型, 等于 (404, "Not Found")

(404, “Not Found”) 元组包含了一个 Int 和一个 String,一个数字和一个人工可读的描述。它可以描述成一个类型为(Int, String)的元组。

你可以用任何变化的类型创建元组,你可以按照需要要创建不同的元组。

你可以分解元组到分离的常量或者变量,通常你会访问它们:

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"

20.可选类型

当一个值可能缺失的时候使用可选类型。一个可选值包含两种可能:或者有一个值,你可以通过拆包访问这个值,或者根本没有值。

这里有一个例子,说明可选类型如何在值缺失的时候使用。Swift 的 Int 类型有一个构造器,可以把 String 转换为 Int 值。不过,不是每一个字符串都可以转换的。“123” 可以转换为 123,但是 “helloworld” 就不可以。

下面的例子使用构造器去转换 String :

let possibleNumber = "123"

let convertedNumber = Int(possibleNumber)

// convertedNumber 被推断为 "Int?" 类型, 或者说 "可选的 Int"

因为构造器有可能失败,所以它返回一个可选的 Int,而不是 Int. 一个可选 Int 写作 Int,不是 Int. 问号表示包含的值是可选的,意思就是它可能包含某个 Int 值,也可能不包含任何值。

21.nil

通过赋值 nil 来把可选值设置成无值状态:

var serverResponseCode: Int? = 404

// serverResponseCode 包含一个实际的整数值 404

serverResponseCode = nil

// serverResponseCode 现在没有值

如果定义一个可选变量,但是没有提供默认值,这个变量会被自动设置为 nil:

var surveyAnswer: String?

// surveyAnswer 自动设置为 nil

22.if 语句和强制拆包

用 if 语句,通过与 nil 做比较,你可以知道一个可选项是否包含一个值,用(==)或者(!=)来做比较。如果一个可选项有值,那么它不等于 nil:

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."


23.可选绑定

使用可选绑定找出可选项是否包含一个值。如果要这样做,使这个值作为一个临时常量或者变量。可选绑定用 if 和 while 语句来判断可选项的值,然后提取这个值到常量或者变量,作为操作的一部分。

用 if 语句像下面这样写:

if let constantName = someOptional {

statements

}

你可以重写 possibleNumber 实例,通过使用可选绑定而不是强制拆包:

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 var actualNumber 来替换代码,然后可选项的值会变成变量值而不是常量值。

你可以在单独的 if 语句包括尽可能多的可选绑定和布尔条件,用逗号分开即可。如果任何可选项的值为 nil 或者布尔条件等于 false,if 条件被认为是 false. 下面的 if 语句是一样的:

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"


24.隐式拆包可选项

如上所述,可选项表示一个常量或者变量可以没有值。可选项可以用 if 语句判断是否存在值,如果不存在,可以有条件用可选绑定拆包来访问可选项的值。

有时候,通过程序的结构可以很清楚的知道可选项有值,然后这个值被第一次设置。这种情况,就不需要每次都判断和拆包了,因为可以安全的假设总是有值。

这种可选项定义为隐式拆包可选项。隐式拆包可选项的写法是,在类型后面假设感叹号而不是问号。

当可选项的值在首次定义后就能确定存在的事,隐式拆包可选项很有用。隐式拆包可选项主要用在类的初始化。

隐式拆包可选项在这种场景下,是正常可选项,但是也可以用作一个非可选项,无需每次访问都拆包。下面的例子展示了,可选字符串和隐式拆包可选项字符串作为显示 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."

25.错误处理

使用错误处理响应程序执行中错误条件。

与可选项做对照,可选项用有无值来表示一个函数的成功失败,错误处理允许你检测迁走的失败原因,同时如果必要的话,会把错误传递到程序的另外一部分。

当函数遇到一个错误情况,它就会 throws 一个错误。函数调用者可以捕获这个错并正确响应。

func canThrowAnError() throws {

// 这个函数可能会抛出一个错误

}

一个函数表明它可以通过在定义中包含 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 项。

如果没有错误抛出,eatASandwich 函数会被调用。如果一个错误抛出然后匹配 SandwichError.outOfCleanDishes 这个条件,那么 washDishes 会被调用。如果一个错误抛出然后匹配 SandwichError.missingIngredients 条件,那么 buyGroceries 会被调用

26.断言和先决条件

断言和先决条件的判断发生在运行时。使用它们可以保证一个条件必须满足才会执行后面的代码。如果断言和先决条件的布尔值等于真, 代码会继续执行。如果这个条件是假, 程序的当前状态是无效的; 代码结束执行, 你的应用也会终止。

在编码时, 你可以用断言和先决条件表达假设和预期, 所以你可以在代码里包含它们。断言可以帮你在开发时发现错误和不正确的假设, 先决条件帮你在产品阶段检测问题。除了在运行时校验你的预期, 断言和先决条件也可以成为代码的有效的文本形式。跟上面讨论的错误条件不同, 断言和先决条件不用于可恢复和预期的错误。因为一个失败的断言或者先决条件表明一个无效的程序状态, 没有办法捕获一个失败的断言。

使用断言和先决条件不是代码设计的替代, 这种方式下无效条件不太可能出现。不过, 使用它们强制有效的数据和状态, 当无效状态发生时, 你的程序可以预期的终止, 有助于问题的定位。停止执行可以限制无效状态导致的破坏。

断言和先决条件的区别是它们的判断时间: 断言只在调试运行时判断, 而先决条件可以在调试和产品运行时判断。在产品运行时, 断言是不执行的。这就意味你可以在开发时尽量多的使用断言, 不会影响产品的性能。

27.使用断言调试

调用 Swift 标准库函数 assert(::file:line:) 就可以编写断言。传入判断真假的表达式和一条信息即可。例如:

let age = -3

assert(age >= 0, "A person's age can't be less than zero.")

// 这个断言会失败因为 -3 不是 >= 0.

在这个例子里, 如果 age >= 0 代码会执行执行, 就是说, 如果年龄值是正数。如果这个年龄值是负数, 就像上面的代码那样, 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.")

}

28.执行先决条件

当一个条件有可能失败时, 使用先决条件, 但是真的时候代码必须可以继续执行。例如, 使用一个先决条件判断一个下标没有越界, 或者判断一个函数传入了一个有效值。

调用 precondition(::file:line:) 函数就可以实现先决条件。传入判断真假的表达式和一条信息即可。例如:

// 一个下标实现里的代码

precondition(index > 0, "Index must be greater than zero.")

你还可以调用 preconditionFailure(_:file:line:) 函数表明一个失败发生—例如, switch 语句的默认分支执行, 不过所有有效输入数据都已经被其他分支处理了。

备注 如果你以非判断模式编译 (-Ounchecked), 先决条件不会判断。编译器假设先决条件总是真, 它会相应优化你的代码。不过, fatalError(:file:line:) 函数总会终止执行, 不管最优化设置。

你可以在原型和早期开发时, 使用 fatalError(:file:line:) 函数穿件还没有实现的功能, 比如 fatalError(“Unimplemented”). 因为致命错误永远不会优化, 跟断言或者先决条件不同, 如果遭遇未实现的功能, 你可以保证执行停止。

29.基础运算符

operator 是特殊的符号或者短语,用来检查,改变,或者合并数值。比如,加号(+)用来加两个数值,比如 let = 1 + 2,逻辑与(&&)合并两个布尔值,比如 enteredDoorCode && passedRetinaScan

Swift 支持大部分的 C 语言操作符,并且提升了消除一般编码错误的能力。赋值运算符(=)不会返回一个值,用来防止错误是使用(==)。算数运算符(+,-,*,/,% 等等)监测和拒绝值溢出,为了避免值溢出造成未知的结果。你可以用 Swift 的溢出运算符选择值溢出行为。

Swift 同时提供了两个范围运算符(a..\<b和a…b),这在 C 语言里没有。这些作为表达一个范围值的快捷方式。

这个章节介绍 Swift 的普通运算符。 高级运算符包含了 Swift 的高级运算符,描述如何定义你自己的运算符,然后为你自定义的类型实现标准运算符。

30.术语

运算符是一元的,二元的,或者三元的:

一元运算符操作单一目标(比如 -a)。一元前缀操作费直接写在目标前(比如 !b),一元后缀操作费则直接出现在目标后(比如 c!)。

二元运算符操作两个目标(比如 2+3),并且出现在两个目标的中间。

三元运算符操作三个目标,跟 C 语言一样,Swift 只有一个三元运算符,也就是三元条件运算符(a ?b :c)

运算符作用的值是操作数。在表达式 1 + 2 里,+ 号是二元运算符,两个操作数分别是 1 和 2.

31.赋值运算符

赋值运算符(a=b)用 b 的值初始化或者更新 a 的值。

let b = 10

var a = 5

a = b

// a 等于 10

如果赋值语句的右侧是多值的元组,它的元素可以一次分解为多个常量或者变量:

let (x, y) = (1, 2)

// x 等于 1, y 等于 2

这个特性防止 = 与 == 混淆。 让 if x = y 无效, Swift 帮你避免这种错误。

32.算术运算符

Swift 对所有类型支持四种标准算术运算符:

加号 (+) 1 + 2 // 等于 3

减号 (-) 5 - 3 // 等于 2

乘号 () 2 3 // 等于 6

除号 (/) 10.0 / 2.5 // 等于 4.0

与 C 语言 和 Objective-C 语言不同, Swift 算术运算符默认不允许值溢出。 你可以通过 Swift 溢出运算符来选择值溢出行为(例如 a &+ b)。

加号也支持字符串连接:

"hello, " + "world"  // 等于 "hello, world"

33.余数运算符

余数运算符 (a % b) 计算 a 中 b 的倍数,并且返回余数。

备注 余数运算符 (%) 在其他语言中作为取模运算符。 不过, 在 Swift 中对负数来说, 严格来讲, 它是余数而不是一个取模运算。

下面演示余数运算符是如何工作的。 计算 9 % 4, 你首先算出9里有多少个4:

你可以确定9里有两个4, 余数是 1 (橙色显示)。

在 Swift 里, 这个会写作:

9 % 4 // 等于 1

为了确定 a % b 的结果, % 运算符计算下面的方程式,然后返回余数作为输出:

a = (b x 倍数) + 余数

这里倍数是a里面b的最大倍数。

把 9 和 4 代入方程式值域:

9 = (4 x 2) + 1

计算负值a的余数也是用相同的方法:

-9 % 4  // 等于 -1

活的余数 -1.

负值 b 被忽略。 意思就是 a % b 和 a % -b 结果是一样的。

33.一元减法运算符

数值符号可以用前缀 - 连接, 也就是大家熟知的一元减法运算符:

let three = 3

let minusThree = -three      // minusThree 等于 -3

let plusThree = -minusThree  // plusThree 等于 3, 或者 "负负 3"

一元减法运算符 (-) 直接放在操作数前, 没有空格。

34.一元加法运算符

一元加法运算符 (+) 返回操作数值, 没有任何改变:

let minusSix = -6

let alsoMinusSix = +minusSix  // alsoMinusSix 等于 -6

尽管一元加法运算符实际上不做任何事, 你可以用它来提供对称的代码,当在代码中使用正负数的时候。

35.复合赋值运算符

和 C 语言相似, Swift 提供复合赋值运算符来合并赋值 (=) 和其他操作数。 一个例子就是加法赋值运算符 (+=):

var a = 1

a += 2

// a 现在等于 3

表达式 a += 2 是 a = a + 2 的快捷方式。 实际上, 加法和赋值合并进一个操作符,同时做了两件事。

备注 复合赋值运算符不返回值。 比如, 你不能这样写 let b = a += 2.

36.比较运算符

Swift 支持所有标准 C 语言比较运算符:

等于 (a == b)

不等于(a != b)

大于 (a > b)

小于 (a < b)

大于等于 (a >= b)

小于等于 (a <= b)

备注 Swift 同时提供两个相等运算符 (=== and !==), 你可以用来测试两个对象引用是否引用了相同的对象实例。 更多信息参考类和结构体。

每个比较运算符都返回一个布尔值,来表明语句是否是真的:

1 == 1 // 真,因为1 等于 1

2 != 1 // 真,因为2 不等于 1

2 > 1 // 真,因为2 大于 1

<1 2="" <="" 真,因为1="" 小于="" 2<="" li="">

1 >= 1 // 真,因为1大于或者等于1

2 <= 1 // 假,因为2不小于或者等于1

比较运算符通常用于条件语句, 例如 if 语句:

let name = “world”

if name == “world” {

print(“hello, world”)

} else {

print(“I’m sorry (name), but I don’t recognize you”)

}

// 打印 “hello, world”, because name is indeed equal to “world”.

更多 if 语句, 参考控制流章节。

你可以比较含有相同数量值的元组, 只要元组里的值可以比较。 例如, Int 和 String 可以比较, 意思就是 (Int, String) 可以比较。 相反, Bool 不能比较, 意思就是包含布尔值的元组不能作比较。

元组是从左到右做比较的, 每次一个值, 直到比较发现两个值不等为止。 这两个被比较的值, 比较的结果决定了整个元组比较的结果。 如果所有元素都相等, 那么元组就是相等的。例如:

(1, “zebra”) < (2, “apple”) // 真,因为1小于2; “zebra” 和 “apple” 不比较

(3, “apple”) < (3, “bird”) // 真,以为3等于3, “apple” 小于 “bird”

(4, “dog”) == (4, “dog”) // 真,因为4等于4, “dog” 等于 “dog”

上面这个例子, 在第一行你可以看见从左到右比较的行为。 因为1小于2, (1, “zebra”) 被认为小于 (2, “apple”), 不用管元组里的其他值。 不管 “zebra” 是不是小于 “apple”, 因为比较已经取决于元组里的第一个元素了。 不过, 当元组第一个元素相等的时候,比较就像第二行,第三行发生的那样。

# 三元条件运算符

三元条件运算符是含有三部分的特殊运算符, 样式是 问题 ? 答案1 : 答案2。 这是基于问题是真假对表达式之一的判断。 如果问题是真, 求 answer1 的值并返回; 否则, 求 answer2 的值并返回。

三元条件运算符是下面代码的简写:

if question {

answer1

} else {

answer2

}

这里有一个例子, 用例计算列表行高。 如果行有头部,那么行高比内容高度加 50,如果没有头部,那么高度加 20:

let contentHeight = 40

let hasHeader = true

let rowHeight = contentHeight + (hasHeader ? 50 : 20)

// rowHeight 等于 90

let contentHeight = 40

let hasHeader = true

let rowHeight: Int

if hasHeader {

rowHeight = contentHeight + 50

} else {

rowHeight = contentHeight + 20

}

// rowHeight 等于 90

第一个例子使用三元条件运算符,意味着行高可以在一行代码里正确设置, 这个比第二个例子的代码要简洁。

三元条件运算符提供一个简写,来决定使用两个表达式中的哪一个。 小心使用三元条件运算符。 过度使用,代码就是很难理解。 尽量避免把三元条件运算符的多个实例合并到一个符合语句。

# Nil-联合运算符

nil-联合运算符 (a ?? b) 展开一个可选项 a 如果它包含一个值的话, 或者返回一个默认值 b 如果 a 是 nil。 表达式 a 总是一个可选类型。 表达式 b 必须匹配存储在 a 里的值类型。

nil-联合运算符是下面代码的简写:

a != nil ? a! : b

上面的代码使用三元条件运算符,然后强制展开 (a!) 来获取 a 中的值,如果这个值不空的话, 否则返回 b。nil-联合运算符提供了更优雅简洁的方式来压缩这个条件判断和展开。

> 备注 如果值非空, b 的值不会得到。 这就是人们熟知的短路估算。

下面的例子使用 nil-联合运算符在默认颜色名称和可选用户定义的颜色名称之间做选择:

let defaultColorName = “red”

var userDefinedColorName: String? // 默认是 nil

var colorNameToUse = userDefinedColorName ?? defaultColorName

// userDefinedColorName 是 nil, 所以colorNameToUse 设置成默认值 “red”

userDefinedColorName 变量定义成可选的 String, 默认值是 nil. 因为 userDefinedColorName 是个可选类型, 你可以使用nil-联合运算符来获取它的值。 上面的例子, 运算符用来决定变量 colorNameToUse 的一个初始值。 因为 userDefinedColorName 是 nil, userDefinedColorName ?? defaultColorName 表达式返回了 defaultColorName 的值, 或者 “red”.

如果你给 userDefinedColorName 赋了一个非空值,然后执行 nil-联合运算符再次判断, userDefinedColorName 中包含的值就取代了默认值:

userDefinedColorName = “green”

colorNameToUse = userDefinedColorName ?? defaultColorName

// userDefinedColorName is not nil, so colorNameToUse is set to “green”

# 范围运算符

Swift 有两个范围运算符, 是表达值范围的简写。

# 闭合区间运算符

闭合区间运算符 (a…b) 定义从a到b的范围, 包含 a 和 b 的值。a 的值不能比 b 大。

闭合区间运算符在范围迭代很有用,特别是你想使用所有的值的时候。例如 for-in 循环:

for index in 1…5 {

print(“(index) times 5 is (index * 5)”)

}

// 1 乘以 5 等于 5

// 2 乘以 5 等于 10

// 3 乘以 5 等于 15

// 4 乘以 5 等于 20

// 5 乘以 5 等于 25

更多 for-in 循环, 参考控制流。

# 半开区间运算符

半开区间运算符 (a..\

逻辑非 (!a)

逻辑与 (a && b)

逻辑或 (a || b)


37.逻辑非运算符

逻辑非运算符 (!a) 反转布尔值,这样真就变成假, 假变成了真。

逻辑非运算符是个前缀运算符, 直接写在操作数前面, 没有任何空格。 可以读作 “not a”, 下面的例子可以看到:

let allowedEntry = false

if !allowedEntry {

print("ACCESS DENIED")

}

// 打印 "ACCESS DENIED"

语句 if !allowedEntry 可以读作 “if not allowed entry.” 后面一行仅执行 “not allowed entry” 是 true; 也就是, if allowedEntry 是 false.

在这个例子中, 谨慎定义布尔常量和变量名,可以让代码具有可读性和简洁性, 同时避免双重否定或者混乱的逻辑语句。


38.逻辑与运算符

逻辑与运算符 (a && b) 创建逻辑表达式,这个表达式中两个值为真,表达式也要为真。

如果任何一个值为假, 表达式的结果也将是假。 事实上, 如果第一个值为假, 第二个值不会再计算, 因为它不会让所有表达式都等于真。 这就是人们熟知的短路估值。

这个例子有两个布尔值,只有两个值都是真的时候才允许访问:

let enteredDoorCode = true

let passedRetinaScan = false

if enteredDoorCode && passedRetinaScan {

print("Welcome!")

} else {

print("ACCESS DENIED")

}

// 打印 "ACCESS DENIED"

39.逻辑或运算符

逻辑或运算符 (a || b) 是两个竖线组成的中间运算符。 用来创建逻辑表达式,这个表达式中只要有一个值为真,表达式的结果就是真。

跟上面逻辑与类似, 逻辑或使用短路估值去计算表达式。 如果逻辑或的左侧是真, 右侧就不再估值, 因为它不会改变整个表达式的结果。

下面的例子, 第一个布尔值 (hasDoorKey) 是假, 但是第二个布尔值 (knowsOverridePassword) 是真。 因为一个值为真, 表达式结果就是真, 所以允许访问:

let hasDoorKey = false

let knowsOverridePassword = true

if hasDoorKey || knowsOverridePassword {

print("Welcome!")

} else {

print("ACCESS DENIED")

}

// 打印 "Welcome!"


40.合并逻辑运算符

你可以合并多个逻辑运算符来创建更长的复合表达式:

if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {

print("Welcome!")

 } else {

print("ACCESS DENIED")

 }

// 打印 "Welcome!"

这个例子使用了多个 && 和 || 运算符来创建一个更长的复合表达式。 不过, && 和 || 运算符依然只能操作两个值, 所以,这实际上是三个小表达式链接起来的。

备注 Swift 逻辑运算符 && 和 || 是左联想的, 意思就是多个逻辑运算符的复合表达式,首先计算最左边的子表达式

41.显示括号

虽然不是严格需要,但是包含括号还是有用的, 使得复杂表达式的意图很容易理解。 上述实例, 给第一部分加上括号很有用,会让它的意图很明显:

if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {

print("Welcome!")

} else {

print("ACCESS DENIED")

}

// 打印 "Welcome!"

括号让第一部分作为独立可能的状态,这样在整个逻辑中就很清晰。 符合表达式的输出不变, 但是整体意图很清晰。 比起简洁,可读性是首选的。使用括号会让你的意图清晰明了。

42.字符串和字符

字符串是字符集合, 比如 “hello, world” 或者 “albatross”. Swift 字符串用 String 类型表示。 字符串内容有多种访问方法, 包括字符值的集合。

Swift 的 String 和 Character 类型提供一个快速的, Unicode 方式来作用于你的文本。 字符串创建和操作的语法是轻量和可读的, 字面语法跟 C 类似。 字符串连接和使用加号运算符一样简单, 字符的可变性由选择常量还是变量来管理, 就像 Swift 中其他值。 你还可以使用字符串把常量,变量,字面量,和表达式插入更长的字符串, 在一个众所周知的字符串插值。 这让显示自定义字符串变得容易。

尽快语法简单, Swift 的 String 类型是个快速, 现代化的字符串实现。 每个字符串有独立编码的 Unicode 字符组成, 提供用多种Unicode 形式访问这些字符的支持。

43.字符串字面量

你可以在字符串字面量中包含预定义的字符串值。一个字符串字面量就是用双引号包起来的字符序列。

使用字面量作为常量或者变量的初始值:

let someString = "Some string literal value"

Swift 推断 someString 常量为 String 类型, 因为它是用一个字符串字面量进行初始化。

如果你需要分为几行的字符串, 可以使用一个多行字符串字面量。多行字符串字面量是三个双引号包起来的字符序列:

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 threeDoubleQuotes = """

Escaping the first quote \"""

Escaping all three quotes \"\"\"

"""

在多行的形式里, 字面量包含的所有行都在打开和关闭的引号里。这个字符串开始于打开的引号 (“””) 结束于关闭的引号 (“””), 这意味着引号不是以换行开始和结束的。下面的字符串都是相同的:

let singleLineString = "These are the same."

let multilineString = """

These are the same.

"""

如果要让多行文本以换行开始和结束的话, 可以在开始和结束分别写一个空白行。例如:

"""

This string starts with a line feed.

It also ends with a line feed.

"""

多行字符串可以缩进来匹配周围的代码。关闭引号之前的空格告诉 (“””) Swift 忽略所有其他行之前的空格。例如, 尽管下面的多行字符串字面量是缩进的, 实际的字符串行并不是以空格开始的。

func generateQuotation() -> String {

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."

"""

return quotation

}

print(quotation == generateQuotation())

// 打印 "true"

不过, 除了结束引号之前的空格之外, 你写在行前的空格会被字面量包含进去。

44.初始化空字符串

创建空字符串值作为更长字符串的起点, 或者给一个变量赋一个空字符串值, 或者用初始化方法来初始化一个新的字符串实例:

var emptyString = ""              // 空字面量

var anotherEmptyString = String()  // 构造语法

// 这两个字符串都是空的, 彼此相等

判读一个字符串是否为空,可以使用它的布尔属性 isEmpty:

if emptyString.isEmpty {

print("Nothing to see here")

}

// 打印 "Nothing to see here"

45.字符串易变性

你来指定一个特定字符串能不能改变(或者突变),这种变化通过把它赋给一个变量实现。 (这种情况它是可以改变的), 或者赋值给一个常量实现 (这种情况它不能被改变):

var variableString = "Horse"

variableString += " and carriage"

// variableString 等于 "Horse and carriage"

let constantString = "Highlander"

constantString += " and another Highlander"

// 报编译期错误 - 常量不能修改


46.字符串是值类型

Swift 的字符串类型是值类型。 如果你创建了一个新的字符串值, 这个字符串值在传给函数或者方法的时候被复制, 或者当它被赋值给一个常量或者变量的时候。这两种情况, 已存在字符串值的拷贝被创建, 新的拷贝被传递或者赋值, 而不是原来的字符串。 值类型在结构体和枚举是值类型中有描述。

很清楚你拥有精确的字符串值,而不用关心它从哪里来的。你可以确信传给你的字符串不会被改变,除非你自己改变它。

在幕后, Swift 的编译器优化了字符串的使用,这使得实际的拷贝只有真正需要的时候才会占用空间。 这就意味你使用字符串作为值类型总可以获得高性能。

47.使用字符

你可以用 for-in 循环语句,通过它的字符属性来迭代访问每一个单独的字符:

for character in "Dog!🐶".characters {

print(character)

}

// D

// o

// g

// !

// 🐶

或者, 你可以创建一个单独的字符常量或者变量,这变量通过提供一个字符类型注释的单字符字符串值:

let exclamationMark: Character = "!"

字符串的值可以, 可以通过把字符数组传入它的构造器来构造:

let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]

let catString = String(catCharacters)

print(catString)

// 打印 "Cat!🐱"

48.连接字符串和字符

字符串值可以通过加号连接成新的字符串:

let string1 = "hello"

let string2 = " there"

var welcome = string1 + string2

// welcome 现在等于 "hello there"

你也可以通过赋值运算符(+=)把字符串添加到已存在的字符串变量:

var instruction = "look over"

instruction += string2

// instruction 等于 "look over there"

你可以使用 append() 方法向字符串后面添加一个字符:

let exclamationMark: Character = "!"

welcome.append(exclamationMark)

// welcome 等于 "hello there!"


49.字符串插入

String 字符串插入是构建新字符串的一种方法,这种方法通过混合常量,变量,字面量,表达式中的字面量来实现。每个你要插入的值都包括在括弧里,前面是一个反斜杠:

let multiplier = 3

let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"

// message is "3 times 2.5 is 7.5"

上面这个例子, multiplier 的值以 \(multiplier) 的形式插入到一个字符串。 这个占位符会被 multiplier 的实际值取代,当插值被计算创建新字符串的时候。

multiplier 的值是字符串后面大表达式的一部分。 这个表达式计算 Double(multiplier) 2.5 的值,然后把结果 (7.5) 插入字符串。 这种情况下, 当它被包括进字符串字面量的时候,表达式就写作 \(Double(multiplier) 2.5).

50.Unicode

Unicode 是在不同写作体系中用作编码,表达,处理文字的一种国际标准。 让你可以用一种标准形式表示任何语言的任何字符, 在诸如文本文件或者网页等外部资源,去读写这些字符。Swift 的字符串和字符是完全 Unicode 编译的。

51.Unicode 标量

背后, Swift 的本地字符串类型建立于 Unicode 标量值。对应字符或者修饰符来说, 一个 Unicode 标量是唯一的一个21位数字, 例如 U+0061 是 LATIN SMALL LETTER A (“a”), 或者 U+1F425 是 FRONT-FACING BABY CHICK (“🐥”).

注意并非所有 21-位 Unicode 标量都会被赋值给一些给未来预留的字符标量。 分配给字符的标量通常有一个名字, 比如 LATIN SMALL LETTER A 和 FRONT-FACING BABY CHICK.

52.字符串字面量里的特殊字符

字符串字面量可以包括下面的特殊字符:

转义字符 \0(空字符), \(反斜杠), \t (水平制表符), \n (换行), \r (回车), \” (双引号) 和 \’ (单引号)

任意一个 Unicode 标量, 写作 \u{n}, n 是 一个 1–8 位十六进制数字,这个数字有个和 有效的 Unicode 编码点相等的值。

下面四个例子显示了特殊字符的编码。wiseWords 常量包括两个转义的双引号字符。dollarSign, blackHeart, 和sparklingHeart 常量展示了 Unicode 标量的格式:

let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"

// "Imagination is more important than knowledge" - Einstein

let dollarSign = "\u{24}"        // $,  Unicode scalar U+0024

let blackHeart = "\u{2665}"      // ♥,  Unicode scalar U+2665

let sparklingHeart = "\u{1F496}" // 💖, Unicode scalar U+1F496

53.扩展字形集

Swift 的每个字符类型的实例带包一个单独的扩展字形集。 一个扩展字形集是一个或者多个 Unicode 标量,这些标量(当合并时)生成一个人可读的字符。

这里有个例子。 字符 é 可以表示成单独的 Unicode 标量 é (LATIN SMALL LETTER E WITH ACUTE<, 或者 U+00E9). 不过, 相同的字母也可以表示成标量集的一部分,字母 e (LATIN SMALL LETTER E, 或者 U+0065), 后面跟着 COMBINING ACUTE ACCENT 标量 (U+0301).COMBINING ACUTE ACCENT 标量 应用于之前的标量, 在一个 Unicode 识别 文本渲染系统渲染的时候,把 e 变成 é.

在这两个例子里, 字符 é 表示为一个单独的 Swift 字符值,这个值表示一个扩展字形集。 第一个例子, 集合包含一个单独的标量; 第二个例子, 是两个标量的集合:

let eAcute: Character = "\u{E9}"                        // é

let combinedEAcute: Character = "\u{65}\u{301}"          // e followed by ́

// eAcute is é, combinedEAcute is é

扩展字形集是一种灵活的方式去表示很多复杂脚本字符作为单独字符值。 比如, 来自朝鲜字母的朝鲜语音节可以表示成复合或者分离的序列。 在 Swift 里,这些表达都是合格的字符值:

let precomposed: Character = "\u{D55C}"                  // 한

let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"  // ᄒ, ᅡ, ᆫ

// precomposed is 한, decomposed is 한

扩展字形集使得封闭标志的标量 (比如 COMBINING ENCLOSING CIRCLE, 或者 U+20DD) 可以装入其他 Unicode 标量作为一个单独字符值的一部分:

let enclosedEAcute: Character = "\u{E9}\u{20DD}"

// enclosedEAcute is é⃝

Unicode 局部指示器符号标量可以成对组合来生成一个单独的字符值, 比如 REGIONAL INDICATOR SYMBOL LETTER U (U+1F1FA) 和 REGIONAL INDICATOR SYMBOL LETTER S (U+1F1F8) 的组合:

let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"

// regionalIndicatorForUS is 🇺🇸

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 53.计算字符 在字符串中获取字符值的数量, 可以使用字符串字符属性中的计数属性: let unusualMena...
    无沣阅读 1,002评论 0 4
  • 136.泛型 泛型代码让你可以写出灵活,可重用的函数和类型,它们可以使用任何类型,受你定义的需求的约束。你可以写出...
    无沣阅读 1,395评论 0 4
  • 86.复合 Cases 共享相同代码块的多个switch 分支 分支可以合并, 写在分支后用逗号分开。如果任何模式...
    无沣阅读 1,272评论 1 5
  • 十二年华悄纵,忽晓当时情共。 相忆诉心深,慨笑巧书无用。 天弄,天弄,千字好生如梦! 2015年8月23日 苏州
    逸之阅读 303评论 0 0
  • OpenCV是一个开源跨平台的的计算机视觉和机器学习库,可以用来做图片视频的处理、图形识别、机器学习等应用。 安装...
    雨影阅读 4,744评论 0 6