Swift5.4 语言指南(四) 基础
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9711776.html
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
Swift是一种用于iOS,macOS,watchOS和tvOS应用程序开发的新编程语言。尽管如此,从您使用C和Objective-C进行开发的经验中,Swift的许多部分将为您所熟悉。
雨燕提供了自己的所有基本C和Objective-C类型的版本,包括Int
为整数,Double
并Float
为浮点值,Bool
布尔值,并String
为文本数据。雨燕还提供了三种主要类型的集合强大的版本Array
,Set
和Dictionary
,如在集合类型。
与C一样,Swift使用变量来存储和通过标识名称引用值。Swift还广泛使用了无法更改其值的变量。这些被称为常量,并且比C中的常量功能强大得多。在整个Swift中使用常量可以使代码在使用不需要更改的值时更安全,更清晰。
除了熟悉的类型外,Swift还引入了在Objective-C中找不到的高级类型,例如元组。元组使您可以创建和传递值分组。您可以使用元组从一个函数返回多个值作为单个复合值。
Swift还引入了可选类型,用于处理缺少值的情况。选配说要么“有是一个值,它等于X ”或“有没有一个价值可言”。nil
在Objective-C中,使用可选参数与使用指针相似,但是它们适用于任何类型,而不仅仅是类。可选控件不仅比nil
Objective-C中的指针更安全,更富有表现力,而且它们是Swift最强大的功能的核心。
Swift是一种类型安全的语言,这意味着该语言可帮助您弄清代码可以使用的值的类型。如果代码的一部分需要使用String
,则类型安全性可防止您Int
误将其传递。同样,类型安全性可以防止您意外地将可选参数传递String
给需要非可选参数的代码String
。类型安全性可帮助您在开发过程中尽早发现并修复错误。
常量和变量
常量和变量将名称(例如maximumNumberOfLoginAttempts
或welcomeMessage
)与特定类型的值(例如数字10
或字符串"Hello"
)相关联。常量的值一旦设置就无法更改,而将来可以将变量设置为其他值。
声明常量和变量
常量和变量必须在使用前声明。您可以使用let
关键字声明常量,并使用关键字声明变量var
。这是一个示例,说明如何使用常量和变量来跟踪用户进行的登录尝试次数:
- let maximumNumberOfLoginAttempts = 10
- var currentLoginAttempt = 0
此代码可以读取为:
“声明一个名为的新常数maximumNumberOfLoginAttempts
,并为其赋予值10
。然后,声明一个名为的新变量currentLoginAttempt
,并将其初始值赋予0
。”
在此示例中,将允许的最大登录尝试次数声明为一个常数,因为最大值不会更改。当前的登录尝试计数器被声明为变量,因为在每次失败的登录尝试之后都必须将该值递增。
您可以在一行中声明多个常量或多个变量,并用逗号分隔:
- var x = 0.0, y = 0.0, z = 0.0
笔记
如果代码中的存储值不变,请始终使用let
关键字将其声明为常量。仅将变量用于存储需要更改的值。
类型注释
声明常量或变量时,可以提供类型注释,以明确常量或变量可以存储的值的类型。通过在常量或变量名称后加冒号,后跟空格,后跟要使用的类型的名称来编写类型注释。
本示例为名为的变量提供类型注释welcomeMessage
,以指示该变量可以存储String
值:
- var welcomeMessage: String
声明中的冒号表示“…类型……”,因此上面的代码可以读作:
“声明一个名为welcomeMessage
that的类型的变量String
。”
“类型String
”一词的意思是“可以存储任何String
值”。将其视为可以存储的“事物的类型”(或“事物的类型”)。
welcomeMessage
现在可以将变量设置为任何字符串值,而不会出现错误:
- welcomeMessage = "Hello"
您可以在一行中定义多个相同类型的相关变量,并用逗号分隔,并在最终变量名称后添加一个类型注释,以单行表示:
- var red, green, blue: Double
笔记
在实践中很少需要编写类型注释。如果在定义的点为常量或变量提供初始值,Swift几乎总是可以推断出用于该常量或变量的类型,如Type Safety和Type Inference中所述。在welcomeMessage
上面的示例中,没有提供初始值,因此该welcomeMessage
变量的类型是通过类型注释指定的,而不是从初始值推断出来的。
命名常量和变量
常量和变量名称几乎可以包含任何字符,包括Unicode字符:
- let π = 3.14159
- let 你好 = "你好世界"
- let 🐶🐮 = "dogcow"
常量和变量名称不能包含空格字符,数学符号,箭头,专用的Unicode标量值或线条和框形图字符。它们也不能以数字开头,尽管数字可能包含在名称中的其他位置。
声明了某种类型的常量或变量后,就无法再次使用相同的名称对其进行声明,也无法将其更改为存储其他类型的值。也不能将常量更改为变量或将变量更改为常量。
笔记
如果您需要给常量或变量命名与保留的Swift关键字相同的名称,则`
在使用该关键字作为名称时,请在关键字两端加上反引号()。但是,除非绝对没有选择,否则避免将关键字用作名称。
您可以将现有变量的值更改为兼容类型的另一个值。在此示例中,的值friendlyWelcome
从更改"Hello!"
为"Bonjour!"
:
- var friendlyWelcome = "Hello!"
- friendlyWelcome = "Bonjour!"
- // friendlyWelcome is now "Bonjour!"
与变量不同,常数的值在设置后不能更改。编译代码时,尝试这样做会报告为错误:
- let languageName = "Swift"
- languageName = "Swift++"
- // This is a compile-time error: languageName cannot be changed.
打印常量和变量
您可以使用以下print(_:separator:terminator:)
函数打印常量或变量的当前值:
- print(friendlyWelcome)
- // Prints "Bonjour!"
该print(_:separator:terminator:)
函数是全局函数,可将一个或多个值打印到适当的输出。例如,在Xcode中,该print(_:separator:terminator:)
函数在Xcode的“控制台”窗格中打印其输出。该separator
和terminator
参数都有默认值,这样你就可以当你调用这个函数忽略它们。默认情况下,该函数通过添加换行符来终止其打印的行。要打印后没有换行符的值,请传递一个空字符串作为终止符,例如。有关具有默认值的参数的信息,请参见“默认参数值”。print(someValue, terminator: "")
Swift使用字符串插值法将常量或变量的名称作为占位符包含在较长的字符串中,并提示Swift将其替换为该常量或变量的当前值。将名称括在括号中,并在左括号前使用反斜杠将其转义:
- print("The current value of friendlyWelcome is \(friendlyWelcome)")
- // Prints "The current value of friendlyWelcome is Bonjour!"
笔记
你可以用串插中使用的所有选项中描述字符串插值。
评论
使用注释在代码中包括不可执行的文本,作为对自己的注释或提醒。在编译代码时,Swift编译器将忽略注释。
Swift中的注释与C中的注释非常相似。单行注释以两个正斜杠(//
)开头:
- // This is a comment.
多行注释以正斜杠后跟星号(/*
)开头,以星号后跟正斜杠(*/
)结束:
- /* This is also a comment
- but is written over multiple lines. */
与C中的多行注释不同,Swift中的多行注释可以嵌套在其他多行注释中。通过开始多行注释块,然后在第一个块中开始第二个多行注释来编写嵌套注释。然后关闭第二个块,然后关闭第一个块:
- /* This is the start of the first multiline comment.
- /* This is the second, nested multiline comment. */
- This is the end of the first multiline comment. */
嵌套的多行注释使您可以快速,轻松地注释掉大块代码,即使代码中已经包含多行注释。
分号
与许多其他语言不同,Swift不需要您;
在代码中的每个语句后写分号(),尽管您可以根据需要这样做。但是,如果要在一行上编写多个单独的语句,则需要使用分号:
- let cat = "🐱"; print(cat)
- // Prints "🐱"
整数
整数是没有小数部分的整数,例如42
和-23
。整数是带符号的(正,零或负)或无符号的(正或零)。
Swift提供8位,16位,32位和64位形式的有符号和无符号整数。这些整数遵循类似于C的命名约定,其中8位无符号整数的类型为UInt8
,而32位有符号整数的类型为Int32
。像Swift中的所有类型一样,这些整数类型都有大写的名称。
整数范围
您可以使用min
和max
属性访问每个整数类型的最小值和最大值:
- let minValue = UInt8.min // minValue is equal to 0, and is of type UInt8
- let maxValue = UInt8.max // maxValue is equal to 255, and is of type UInt8
这些属性的值是适当大小的数字类型(例如,UInt8
在上面的示例中),因此可以与相同类型的其他值一起在表达式中使用。
诠释
在大多数情况下,您无需选择特定大小的整数即可在代码中使用。Swift提供了另外一个整数类型,Int
其大小与当前平台的本机字大小相同:
- 在32位平台上,
Int
与大小相同Int32
。 - 在64位平台上,
Int
与大小相同Int64
。
除非您需要使用特定大小的整数,否则请始终Int
在代码中使用整数值。这有助于代码的一致性和互操作性。即使在32位平台上,Int
也可以存储-2,147,483,648
和之间的任何值2,147,483,647
,并且对于许多整数范围而言足够大。
UInt
Swift还提供了一个无符号整数类型,UInt
其大小与当前平台的本机字大小相同:
- 在32位平台上,
UInt
与大小相同UInt32
。 - 在64位平台上,
UInt
与大小相同UInt64
。
笔记
使用UInt
只有当你特别需要具有相同大小的平台的本地字大小的无符号整型。如果不是这种情况,那么Int
即使已知要存储的值是非负值,也还是首选。始终如一地使用Int
整数值有助于代码互操作性,避免了在不同数字类型之间进行转换的需要,并匹配了整数类型推断,如Type Safety和Type Inference中所述。
浮点数字
浮点数是具有小数部分的数字,例如3.14159
,0.1
,和-273.15
。
浮点类型可以代表比整数类型更大的值范围,并且可以存储比可以存储的数字大或小的数字Int
。Swift提供了两种带符号的浮点数类型:
Double
表示64位浮点数。Float
表示一个32位浮点数。
笔记
Double
的精度至少为15个十进制数字,而的精度Float
则可以低至6个十进制数字。要使用的适当浮点类型取决于您需要在代码中使用的值的性质和范围。在任何一种类型都适合的情况下,Double
首选。
类型安全性和类型推断
Swift是一种类型安全的语言。类型安全的语言鼓励您清楚代码可以使用的值的类型。如果您的代码的一部分需要使用String
,则不能Int
错误地传递它。
由于Swift是类型安全的,因此它会在编译代码时执行类型检查,并将所有不匹配的类型标记为错误。这使您能够在开发过程中尽早发现并修复错误。
使用不同类型的值时,类型检查可帮助您避免错误。但是,这并不意味着您必须指定声明的每个常量和变量的类型。如果您没有指定所需的值类型,则Swift会使用类型推断来得出适当的类型。通过类型推断,编译器只需检查您提供的值,即可在编译代码时自动推断出特定表达式的类型。
由于类型推断,Swift与C或Objective-C之类的语言相比,所需的类型声明要少得多。常量和变量仍然是显式输入的,但是指定它们的类型的许多工作都是为您完成的。
当您声明具有初始值的常量或变量时,类型推断特别有用。这通常是通过分配完成的文字值(或文字在你宣布它的点)的固定或可变。(A字面值是直接出现在源代码中,如一个值42
和3.14159
在下面的例子。)
例如,如果不给字面值赋42
一个新常量而没有说出它是什么类型,Swift会推断您希望该常量为Int
,因为您已经用一个看起来像整数的数字对其进行了初始化:
- let meaningOfLife = 42
- // meaningOfLife is inferred to be of type Int
同样,如果您没有为浮点文字指定类型,Swift会推断您要创建一个Double
:
- let pi = 3.14159
- // pi is inferred to be of type Double
在推断浮点数的类型时,Swift总是选择Double
(而不是Float
)。
如果在表达式中结合使用整数和浮点文字,Double
则会从上下文中推断出的类型:
- let anotherPi = 3 + 0.14159
- // anotherPi is also inferred to be of type Double
的字面值3
本身没有显式类型,因此Double
从作为加法的一部分的浮点字面量中推断出适当的输出类型。
数字文字
整数文字可以写为:
- 一个十进制数,无前缀
- 一个二进制数,有
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
浮点文字可以是十进制(无前缀)或十六进制(有0x
前缀)。它们的小数点两侧必须始终有一个数字(或十六进制数字)。小数浮点数也可以有一个可选的指数,用大写或小写表示e
; 十六进制浮点数必须具有指数,以大写或小写表示p
。
对于指数为的十进制数字exp
,基数乘以10 exp:
1.25e2
表示1.25 x 10 2,或125.0
。1.25e-2
表示1.25 x 10 -2,或0.0125
。
对于指数为的十六进制数exp
,将基数乘以2 exp:
0xFp2
表示15 x 2 2,或60.0
。0xFp-2
表示15 x 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
数值类型转换
将Int
类型用于代码中的所有通用整数常量和变量,即使已知它们是非负数也是如此。在日常情况下使用默认整数类型意味着整数常量和变量可立即在您的代码中互操作,并且将与推断的类型匹配整数文字值。
仅在由于即将从外部源显式调整数据大小或出于性能,内存使用或其他必要的优化而特别需要处理手头任务时,才使用其他整数类型。在这些情况下,使用显式大小的类型有助于捕获任何意外的值溢出,并隐式记录所使用数据的性质。
整数转换
对于每种数字类型,可以存储在整数常量或变量中的数字范围是不同的。一个Int8
常数或变量可以存储之间的数字-128
和127
,而UInt8
常数或变量可以存储之间的数字0
和255
。编译代码时,将不适合整数大小或常量类型的常量的数字报告为错误:
- let cannotBeNegative: UInt8 = -1
- // UInt8 can't store negative numbers, and so this will report an error
- let tooBig: Int8 = Int8.max + 1
- // Int8 can't store a number larger than its maximum value,
- // and so this will also report an error
由于每种数字类型可以存储不同范围的值,因此您必须视情况选择加入数字类型转换。这种选择加入的方法可以防止隐藏的转换错误,并有助于在代码中明确表明类型转换的意图。
要将一种特定的数字类型转换为另一种,可以使用现有值初始化所需类型的新数字。在下面的示例中,常量twoThousand
是type UInt16
,而常量one
是type UInt8
。不能将它们直接添加在一起,因为它们不是同一类型。相反,此示例调用UInt16(one)
创建一个新UInt16
的one
,并使用值初始化,并使用该值代替原始值:
- let twoThousand: UInt16 = 2_000
- let one: UInt8 = 1
- let twoThousandAndOne = twoThousand + UInt16(one)
因为加法的两边现在都是type UInt16
,所以允许加法。输出常量(twoThousandAndOne
)推断为type UInt16
,因为它是两个UInt16
值的总和。
SomeType(ofInitialValue)
是调用Swift类型的初始值设定项并传递初始值的默认方式。在幕后,UInt16
有一个接受UInt8
值的初始值设定项,因此该初始值设定项用于UInt16
从现有的中创建一个新值UInt8
。但是,您不能在此处传递任何类型-它必须是为其UInt16
提供初始化程序的类型。扩展中介绍了扩展现有类型以提供接受新类型(包括您自己的类型定义)的初始化程序的功能。
整数和浮点转换
整数和浮点数字类型之间的转换必须明确:
- let three = 3
- let pointOneFourOneFiveNine = 0.14159
- let pi = Double(three) + pointOneFourOneFiveNine
- // pi equals 3.14159, and is inferred to be of type Double
在这里,常量的值three
用于创建type的新值Double
,以便加法的两面都属于同一类型。如果没有这种转换,则不允许添加。
浮点数到整数的转换也必须明确。整数类型可以使用Double
或Float
值初始化:
- let integerPi = Int(pi)
- // integerPi equals 3, and is inferred to be of type Int
以这种方式用于初始化新整数值时,浮点值始终会被截断。这意味着4.75
成为4
,并且-3.9
成为-3
。
笔记
组合数字常量和变量的规则与数字文字的规则不同。文字值3
可以直接添加到文字值中0.14159
,因为数字文字本身本身没有显式类型。仅在编译器对其求值时才推断出它们的类型。
类型别名
类型别名为现有类型定义备用名称。您可以使用typealias
关键字定义类型别名。
当您想通过上下文更合适的名称来引用现有类型时,例如使用外部源中特定大小的数据时,类型别名非常有用:
- typealias AudioSample = UInt16
定义类型别名后,您可以在任何可能使用原始名称的地方使用别名:
- var maxAmplitudeFound = AudioSample.min
- // maxAmplitudeFound is now 0
在此,AudioSample
被定义为的别名UInt16
。因为它是一个别名,调用AudioSample.min
实际调用UInt16.min
,它提供的初始值0
的maxAmplitudeFound
变量。
布尔值
Swift具有一个基本的布尔类型,称为Bool
。布尔值被称为逻辑值,因为它们只能是true或false。Swift提供了两个布尔常量值,true
以及false
:
- let orangesAreOrange = true
- let turnipsAreDelicious = false
该类型的orangesAreOrange
和turnipsAreDelicious
被推断为Bool
一个事实,即他们与布尔文字值初始化。正如Int
和Double
上面,你并不需要声明常量或变量Bool
,如果将其设置为true
或者false
只要你创建它们。当类型推断使用其类型已知的其他值初始化常量或变量时,类型推断有助于使Swift代码更简洁易读。
当您使用条件语句(例如,if
语句)时,布尔值特别有用:
- if turnipsAreDelicious {
- print("Mmm, tasty turnips!")
- } else {
- print("Eww, turnips are horrible.")
- }
- // Prints "Eww, turnips are horrible."
诸如条件语句之类的条件语句if
在“控制流”中有更详细的介绍。
Swift的类型安全性可防止将非布尔值替换为Bool
。下面的示例报告一个编译时错误:
- 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
- }
比较的结果是type ,因此第二个示例通过了type-check。基本运算符中讨论了类似的比较。i == 1
Bool
i == 1
与Swift中的其他类型安全示例一样,此方法避免了意外错误,并确保始终清楚特定代码部分的意图。
元组
元组将多个值分组为一个复合值。元组中的值可以是任何类型,而不必彼此相同。
在此示例中,是描述HTTP状态代码的元组。HTTP状态代码是每当您请求网页时由Web服务器返回的特殊值。如果您请求一个不存在的网页,则返回状态码。(404, "Not Found")
404 Not Found
- let http404Error = (404, "Not Found")
- // http404Error is of type (Int, String), and equals (404, "Not Found")
的元组基团一起的和,得到的HTTP状态代码两个独立的值:一个数字和一个人类可读的描述。可以将其描述为“类型的元组”。(404, "Not Found")
Int
String
(Int, String)
您可以从任何类型的排列创建元组,并且它们可以包含任意多的不同类型。没有什么阻止你有型的元组,或者,或者你确实需要的任何其他排列。(Int, Int, Int)
(String, Bool)
您可以将元组的内容分解为单独的常量或变量,然后像往常一样访问它们:
- 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"
或者,使用从零开始的索引号访问元组中的各个元素值:
- 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)
笔记
元组对于简单的一组相关值很有用。它们不适合创建复杂的数据结构。如果您的数据结构可能更复杂,则将其建模为类或结构,而不是元组。有关更多信息,请参见结构和类。
选装件
在可能不存在值的情况下,可以使用可选选项。可选的代表两种可能性:要么有是一个值,你可以解开可选访问该值,或者有没有价值可言。
笔记
可选概念在C或Objective-C中不存在。在Objective-C中,最接近的是nil
从一种方法返回的能力,该方法原本会返回一个对象,nil
意思是“缺少有效的对象”。但是,这仅适用于对象,不适用于结构,基本C类型或枚举值。对于这些类型,Objective-C方法通常返回一个特殊值(例如NSNotFound
)以指示不存在值。这种方法假定方法的调用者知道要测试的特殊值,并且记住要进行检查。Swift的可选参数使您可以指示根本不需要任何类型的值,而无需特殊的常量。
这是一个示例,说明如何使用可选参数来解决缺少值的问题。Swift的Int
类型具有一个初始化程序,该初始化程序试图将一个String
值转换为一个Int
值。但是,并非每个字符串都可以转换为整数。字符串"123"
可以转换为数值123
,但是字符串没有明显的数值可以转换为数值。"hello, world"
以下示例使用初始化程序尝试将aString
转换为Int
:
- let possibleNumber = "123"
- let convertedNumber = Int(possibleNumber)
- // convertedNumber is inferred to be of type "Int?", or "optional Int"
由于初始化程序可能会失败,因此它返回一个optional Int
而不是一个Int
。可选Int
内容写为Int?
而不是Int
。问号表示它包含的值是可选的,这意味着它可能包含某个 Int
值,或者可能根本不包含任何值。(它不能包含其他任何内容,例如Bool
值或String
值。它可以是Int
,也可以完全不包含任何内容。)
零
您可以通过为可选变量分配特殊值来将其设置为无值状态nil
:
- var serverResponseCode: Int? = 404
- // serverResponseCode contains an actual Int value of 404
- serverResponseCode = nil
- // serverResponseCode now contains no value
笔记
您不能使用nil
非可选的常量和变量。如果在某些情况下代码中的常量或变量需要在没有值的情况下工作,请始终将其声明为适当类型的可选值。
如果您在不提供默认值的情况下定义了一个可选变量,则该变量会自动nil
为您设置为:
- var surveyAnswer: String?
- // surveyAnswer is automatically set to nil
笔记
Swiftnil
与nil
Objective-C中的不一样。在Objective-C中,nil
是指向不存在对象的指针。在Swift中,nil
它不是指针,而是缺少某种类型的值。可以将任何类型的Optionals设置为nil
,而不仅仅是对象类型。
如果语句和强制展开
您可以使用一条if
语句,通过将可选内容与进行比较,找出可选内容是否包含值nil
。您可以使用“等于”运算符(==
)或“不等于”运算符(!=
)进行比较。
如果一个可选参数有一个值,则认为它是“不等于” nil
:
- if convertedNumber != nil {
- print("convertedNumber contains some integer value.")
- }
- // Prints "convertedNumber contains some integer value."
一旦确定可选选项确实包含一个值,就可以通过!
在可选名称的末尾添加一个感叹号()来访问其基础值。感叹号有效地表示:“我知道此可选内容肯定具有价值;请使用它。” 这称为可选值的强制展开:
- if convertedNumber != nil {
- print("convertedNumber has an integer value of \(convertedNumber!).")
- }
- // Prints "convertedNumber has an integer value of 123."
有关该if
语句的更多信息,请参见控制流。
笔记
尝试用于!
访问不存在的可选值会触发运行时错误。nil
在!
用于强制拆包其值之前,请始终确保其包含非值。
可选装订
您可以使用可选绑定来确定可选对象是否包含值,如果可以,则将该值用作临时常量或变量。可选绑定可以与if
andwhile
语句一起使用,以检查可选内部的值,并将该值提取到常量或变量中,作为单个操作的一部分。if
和while
语句在控制流中更详细地描述。
为if
语句编写一个可选的绑定,如下所示:
- if let constantName = someOptional {
- statements
- }
您可以possibleNumber
从Optionals部分重写示例,以使用可选绑定而不是强制展开:
- if let actualNumber = Int(possibleNumber) {
- print("The string \"\(possibleNumber)\" has an integer value of \(actualNumber)")
- } else {
- print("The string \"\(possibleNumber)\" couldn't be converted to an integer")
- }
- // Prints "The string "123" has an integer value of 123"
此代码可以读取为:
“如果Int
by所返回的可选Int(possibleNumber)
内容包含一个值,请为该可选内容中包含的值设置一个新的常量actualNumber
。”
如果转换成功,则该actualNumber
常量可在该if
语句的第一个分支内使用。它已经被初始化与包含在值内的可选的,所以你不使用!
后缀来访问它的价值。在此示例中,actualNumber
仅用于打印转换结果。
您可以将常量和变量与可选绑定一起使用。如果要操作语句actualNumber
的第一个分支内的值,则if
可以编写,而可选值中包含的值将作为变量而不是常量提供。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")
- }
- // 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"
笔记
在if
语句中使用可选绑定创建的常量和变量仅在if
语句主体内可用。相反,用guard
语句创建的常量和变量在该语句之后的代码行中可用guard
,如Early Exit中所述。
隐式展开的可选
如上所述,可选的指示常量或变量被允许具有“无值”。可以使用if
语句检查可选项以查看值是否存在,并且可以使用可选绑定有条件地对其进行拆包,以访问可选值(如果存在)。
有时,从程序的结构中可以明显看出,在首次设置可选值之后,该可选值将始终具有一个值。在这些情况下,删除每次访问可选值时都不需要检查和取消包装的值很有用,因为可以安全地假定它始终都具有值。
这些类型的可选对象被定义为隐式解包的可选对象。您通过在要使其为可选的类型之后放置感叹号(String!
)而不是问号(String?
)来编写隐式解包的可选。声明时,不要在选项的名称之后放置一个感叹号,而是在选项的类型之后放置一个感叹号。
当在第一个定义了可选对象的值之后立即确认该可选对象的值存在并且可以肯定地认为此后的每个点上都存在该值时,隐式解开的可选对象将很有用。Swift中隐式解包的可选对象的主要用途是在类初始化期间,如“无主引用”和“隐式解包的可选属性”中所述。
隐式解开的可选内容是幕后的常规可选内容,但也可以像非可选值一样使用,而无需每次访问该可选值时都将其解包。下面的示例显示了在以显式方式访问其包装值时,可选字符串和隐式解包的可选字符串在行为上的区别String
:
- let possibleString: String? = "An optional string."
- let forcedString: String = possibleString! // requires an exclamation point
- let assumedString: String! = "An implicitly unwrapped optional string."
- let implicitString: String = assumedString // no need for an exclamation point
您可以将隐式解包的可选对象视为允许将可选对象强制解包(如果需要)的权限。当您使用隐式解包的可选值时,Swift首先尝试将其用作普通的可选值;如果不能将其用作可选内容,则Swift会强制展开该值。在上面的代码中,将可选值赋值assumedString
为之前先对其进行了强制拆包,implicitString
因为implicitString
它具有显式的非可选类型String
。在下面的代码中,optionalString
没有显式类型,因此它是普通的可选。
- let optionalString = assumedString
- // The type of optionalString is "String?" and assumedString isn't force-unwrapped.
如果一个隐式展开的可选nil
变量是,并且您尝试访问其包装值,则会触发运行时错误。结果与将感叹号放在不包含值的普通可选内容之后完全相同。
您可以检查隐式展开的可选是否nil
与检查普通可选相同:
- if assumedString != nil {
- print(assumedString!)
- }
- // Prints "An implicitly unwrapped optional string."
您还可以使用带有可选绑定的隐式解包后的可选内容,以在单个语句中检查和解开其值:
- if let definiteString = assumedString {
- print(definiteString)
- }
- // Prints "An implicitly unwrapped optional string."
笔记
当变量有可能nil
在以后出现时,请不要使用隐式展开的可选内容。如果需要nil
在变量的生存期内检查值,请始终使用普通的可选类型。
错误处理
您使用错误处理来响应程序在执行过程中可能遇到的错误情况。
与可以使用值的存在或不存在来传达函数成功或失败的可选选项相反,错误处理使您可以确定失败的根本原因,并在必要时将错误传播到程序的另一部分。
当函数遇到错误条件时,它将引发错误。然后,该函数的调用方可以捕获错误并做出适当的响应。
- func canThrowAnError() throws {
- // this function may or may not throw an error
- }
函数通过throws
在其声明中包含关键字来表明它可以引发错误。当您调用一个可能引发错误的函数时,您会将try
关键字放在表达式的前面。
Swift会自动将错误传播到当前范围之外,直到由catch
子句处理为止。
- do {
- try canThrowAnError()
- // no error was thrown
- } catch {
- // an error was thrown
- }
一条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(_:)
使用[String]
该catch
模式捕获的关联值来调用该函数。
错误处理中更详细地介绍了引发,捕获和传播错误。
断言和前提条件
断言和前提条件是在运行时进行的检查。您可以使用它们来确保在执行任何其他代码之前满足基本条件。如果断言或前提条件中的布尔条件求值为true
,则代码执行将照常继续。如果条件的计算结果为false
,则程序的当前状态无效;否则,结果为0。代码执行结束,您的应用程序终止。
您可以使用断言和前提条件来表达您在进行编码时所做的假设和期望,因此您可以将其包含在代码中。断言可帮助您在开发过程中发现错误和不正确的假设,前提条件可帮助您检测生产中的问题。
除了在运行时验证您的期望之外,断言和前提条件也成为代码中文档的一种有用形式。与上面的错误处理中讨论的错误条件不同,断言和前提条件不用于可恢复或预期的错误。由于失败的断言或前提条件指示无效的程序状态,因此无法捕获失败的断言。
使用断言和前提条件并不能替代以不太可能出现无效条件的方式来设计代码。但是,使用它们强制执行有效的数据和状态会导致您的应用程序在发生无效状态时更可预测地终止,并有助于使问题更易于调试。一旦检测到无效状态,立即停止执行还有助于限制该无效状态所造成的损害。
断言和前提条件之间的区别在于它们的检查时间:断言仅在调试版本中进行检查,而前提条件在调试和生产版本中均进行检查。在生产版本中,不会评估断言中的条件。这意味着您可以在开发过程中使用任意数量的断言,而不会影响生产性能。
断言调试
您可以通过调用assert(_:_:file:line:)
Swift标准库中的函数来编写断言。如果此条件的结果为,则向此函数传递一个表达式,该表达式的计算结果为true
或,false
并显示一条消息false
。例如:
- let age = -3
- assert(age >= 0, "A person's age can't be less than zero.")
- // This assertion fails because -3 isn't >= 0.
在此示例中,如果的值为,即,如果的值为非负数,则代码执行将继续。如果如上面的代码中所示,的值为负,则求值为,并且断言失败,从而终止应用程序。age >= 0
true
age
age
age >= 0
false
您可以省略断言消息,例如,当它只是将条件重复为散文时。
- 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
。例如:
- // In the implementation of a subscript...
- precondition(index > 0, "Index must be greater than zero.")
您还可以调用该preconditionFailure(_:file:line:)
函数以指示发生了故障-例如,如果采用了开关的默认情况,但是所有有效输入数据都应该由开关的其他情况之一处理。
笔记
如果您以非检查模式(-Ounchecked
)进行编译,则不检查前提条件。编译器假定前提条件始终为true,并会相应地优化您的代码。但是,fatalError(_:file:line:)
无论优化设置如何,该功能始终会暂停执行。
fatalError(_:file:line:)
通过编写fatalError("Unimplemented")
存根实现,可以在原型开发和早期开发过程中使用该函数为尚未实现的功能创建存根。由于致命错误永远不会被优化,这与断言或前提条件不同,因此可以确保在遇到存根实现时执行总是停止。