Swift是一种用于iOS、macOS、watchOS和tvOS应用程序开发的新编程语言。尽管如此,由于我们在C和Objective-C中的开发经验,Swift的许多部分还是很熟悉的。
Swift提供了所有基本 C 和 Objective-C 类型的自己的版本,包括整数的Int、浮点值的Double和Float、布尔值的Bool和文本数据的String。Swift还提供了三种主要集合类型Array、Set和Dictionary的类型,如Collection Types所述。
像C一样,Swift使用变量来存储,并通过一个标识名引用这个值。Swift还广泛使用了值不能更改的变量。这些被称为常量,比C中的常量功能强大得多。当我们处理不需要更改的值时,在Swift中使用常量可以使代码更加安全和清晰。
除了熟悉的类型之外,Swift还引入了Objective-C中没有的更高级的类型,比如tuples。tuples使我们能够创建和传递值。我们可以使用tuples将函数中的多个值作为单个复合值来返回。
Swift还引入了optional的可选类型,用于处理缺少值的情况。optional表示“有一个值,它等于x”或“根本没有值”。使用optional,类似于在我们在Objective-C中对指针使用nil,但它们适用于任何类型,而不仅仅是类。optionals不仅比Objective-C中的nil指针更安全、更具表现力,而且还是Swift最强大功能的核心。
Swift是一种类型安全语言,这意味着该语言可以帮助我们明确代码可以使用的值的类型。如果部分代码需要字符串,则类型安全性可以防止我们错误地将Int传递给它。同样,这种类型安全性可以防止我们不小心将可选String传递给需要非可选String的代码段。类型安全性帮助我们在开发过程中尽早捕获并修复这些错误。
常量和变量
常量和变量将名称(如MaximumNumberOfLoginAttents或welcomeMessage)与特定类型的值(如数字10或字符串“Hello”)相关联。常量的值一旦设置就不能更改,而变量可以在将来设置为不同的值。
声明常量和变量
常量和变量在使用前必须声明。用let关键字声明常量,用var关键字声明变量。下面是一个如何使用常量和变量来跟踪用户登录尝试次数的示例:
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
此代码可以理解为:
声明一个名为MaximumNumberOfLoginAttents的新常量,并将其值设为10。
然后,声明一个名为currentloginattent的新变量,并将其初始值设置为0。
在本例中,允许的最大登录尝试次数被声明为常量,因为最大值从不更改。当前登录尝试计数器声明为变量,因为每次登录尝试失败后,此值必须递增。
可以在一行中声明多个常量或多个变量,并用逗号分隔:
var x = 0.0, y = 0.0, z = 0.0
注意:
如果代码中的存储值不会更改,请始终使用let关键字将其声明为常量。仅将变量用于存储需要更改的值。
类型批注
在声明常量或变量时,可以提供类型批注,以明确常量或变量可以存储的值的类型。写一个类型注释,方法是在常量或变量名后加一个冒号,后跟一个空格,然后跟要使用的类型名。
此示例为名为welcomeMessage的变量提供类型批注,以指示该变量可以存储字符串值:
var welcomeMessage: String
声明中的冒号表示“…of type…”,因此上面的代码可以理解为:
“声明一个名为welcomeMessage的字符串类型变量。”
短语“of type String”的意思是“可以存储字符串值”,可以将其理解为“可以存储的对象类型”(或“对象类型”)。
welcomeMessage变量现在可以设置为任何字符串值而不会出错:
welcomeMessage = "Hello"
我们可以在一行中定义同一类型的多个相关变量,用逗号分隔,并在最终变量名后使用一个类型注释:
var red, green, blue: Double
注意:
在实践中,很少需要编写类型批注的。如果在定义常量或变量时为其提供初始值,那么Swift一般可以推断出用于该常量或变量的类型,正如接下来我们在类型安全和类型推断中所述。在上面的welcomessage示例中,没有提供初始值,因此welcomessage变量的类型是用类型批注指定的,而不是从初始值推断出来的。
命名常量和变量
常量和变量名几乎可以包含任何字符,包括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中的所有类型一样,这些整数类型的名称都是大写的。
整数边界
可以使用每个整数类型的“最小值”和“最大值”属性访问其最小值和最大值:
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也可以存储-2147483648和2147483647之间的任何值,并且对于许多整数范围都足够大。
无符号整型
Swift还提供了一个无符号整数类型UInt,其大小与当前平台的本机字大小相同:
在32位平台上,UInt的大小与UInt32相同。
在64位平台上,UInt的大小与UInt64相同。
注意
仅当我们特别需要与平台本机字长相同大小的无符号整数类型时才使用UInt。如果不是这样,则最好使用Int,即使要存储的值已知为非负。对整数值一致使用Int有助于代码的互操作性,避免在不同的数字类型之间进行转换,并匹配整数类型推断,如类型安全和类型推断中所述。
浮点数
浮点数是带有小数部分的数字,例如3.14159、0.1和-273.15。
与整数类型相比,浮点类型可以表示更广泛的值范围,并且可以存储比整数类型大或小得多的数字。Swift提供了两种有符号浮点类型:
Double表示64位浮点数。
Float表示32位浮点数。
注意
Double的精度至少是15位小数,而Float的精度可以是6位小数。要使用的适当浮点类型取决于代码中需要处理的值的性质和范围。在任何一种类型都合适的情况下,首选Double。
类型安全与类型推断
Swift是一种类型安全语言。类型安全语言鼓励我们明确代码可以使用的值的类型。如果部分代码需要字符串,则不能错误地将Int传递给它。
因为Swift是类型安全的,所以它在编译代码时执行类型检查,并将任何不匹配的类型标记为错误。这使我们能够在开发过程中尽早捕获和修复错误。
类型检查有助于在处理不同类型的值时避免错误。但是,这并不意味着必须指定所声明的每个常量和变量的类型。如果没有指定所需的值的类型,Swift将使用类型推断来推断出适当的类型。类型推断使编译器能够在编译代码时,只需检查我们提供的值就可以自动推断出特定表达式的类型。
由于类型推断,Swift需要的类型声明比 C 或 Objective-C 等语言少得多。虽然常量和变量仍然是显式类型化的,但指定其类型的大部分工作都为我们完成了。
当用初始值声明常量或变量时,类型推断就特别有用。这通常是通过在声明常量或变量时为其指定一个文本值(或文本)来实现的。(文本值就是直接出现在源代码中的值,如下面示例中的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前缀)。小数点两边必须有一个数字(或十六进制数)。十进制浮点也可以有一个可选的指数,用大写或小写字母表示;十六进制浮点必须有一个指数,用大写或小写字母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 cannot store negative numbers, and so this will report an error
let tooBig: Int8 = Int8.max + 1
// Int8 cannot store a number larger than its maximum value,
// and so this will also report an error
因为每个数字类型可以存储不同的值范围,所以我们必须根据具体情况选择数字类型转换。这种opt-in方法可以防止潜在的转换错误,并有助于在代码中明确类型转换的意图。
要将一种特定的数字类型转换为另一种类型,可以使用现有值初始化所需类型的新数字。在下面的例子中,常数twoThousand的类型是UInt16,而常数one的类型是UInt8。它们不能直接加在一起,因为它们不是同一类型的。相反,本例调用UInt16(one)来创建一个新的UInt16,该UInt16使用值one进行初始化,并使用此值代替原始值:
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
因为加法的两边现在都是UInt16类型,所以加法是允许的。输出常量(twoThousandAndOne)被推断为UInt16类型,因为它是两个UInt16值的总和。
SomeType(ofInitialValue)是调用Swift类型的初始化方法并传入初始值的默认方式。UInt16有一个UInt8参数类型的初始化方法,因此该初始化方法用于从现有UInt8值生成新的UInt16。不能在这里传入其他类型,必须是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
这里,常量3的值被创建Double类型的新值,因此加法的两边都是相同类型的。如果没有这种转换,就不允许添加。
浮点到整数的转换也必须是显式的。整数类型可以用双精度或浮点值来初始化:
let integerPi = Int(pi)
// integerPi equals 3, and is inferred to be of type Int
以这种方式初始化新整数值时,浮点值总是被取整截断。这意味着4.75变为4,-3.9变为-3。
注意
数值常量和变量的组合规则与数值的组合规则不同。单纯只有数值3,是可以直接添加到值0.14159上,因为数值本身没有显式类型。它们的类型仅在编译器对其求值时推断。
Type 别名
类型别名定义了现有类型的一个替代名称。使用typealias关键字定义类型的别名。
如果我们要使用更适合上下文的名称来引用现有类型,如使用设备的特定字长大小的数据时,类型别名非常有用:
typealias AudioSample = UInt16
定义类型别名后,可以在任何可能使用原始名称的位置使用它的别名:
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound is now 0
这里,AudioSample被定义为UInt16的别名。因为它是一个别名,所以AudioSample.min实际上是UInt16.min,它为maxampitudefound变量提供了一个初始值0。
布尔值
Swift有一个基本的布尔类型,叫做Bool。布尔值被称为逻辑值,因为它们只能要么是真要么假。Swift提供了两个布尔常量值,true和false:
let orangesAreOrange = true
let turnipsAreDelicious = false
orangesAreOrange和turnipsareficious的类型被推断为Bool,因为它们是用布尔值初始化的。与上面的Int和Double一样,如果在创建常量或变量时,将它们设置为true或false,就不需要将它们声明为Bool。当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
}
i==1
比较的结果是Bool类型的,因此第二个示例通过了类型检查。类似i==1的比较在基本运算符中讨论。
与Swift中的其他类型安全示例一样,这种方法避免了意外错误,并确保代码特定部分的表达的意思始终是明确的。