欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

公告 | 关于GitBook的说明

最编程 2024-07-28 07:59:38
...

声明(Declarations)

声明(declaration) 用以向程序里引入新的名字或者结构。举例来说,可以使用声明来引入函数和方法,变量和常量,或者定义新的具有命名的枚举、结构体、类和协议类型。还可以使用声明来扩展一个既有的具有命名的类型的行为,或者在程序里引入在其它地方声明的符号。

在 Swift 中,大多数声明在某种意义上讲也是定义,因为它们在声明时往往伴随着实现或初始化。由于协议并不提供实现,大多数协议成员仅仅只是声明而已。为了方便起见,也是因为这些区别在 Swift 中并不是很重要,“声明”这个术语同时包含了声明和定义两种含义。

声明语法

declaration

声明导入声明

声明常量声明

声明变量声明

声明类型别名声明

声明函数声明

声明枚举声明

声明结构体声明

声明类声明

声明协议声明

声明构造器声明

声明析构器声明

声明扩展声明

声明下标声明

声明运算符声明

declarations

多条声明声明 多条声明可选

*代码

Swift 的源文件中的*代码(top-level code)由零个或多个语句、声明和表达式组成。默认情况下,在一个源文件的顶层声明的变量,常量和其他具有命名的声明可以被同模块中的每一个源文件中的代码访问。可以使用一个访问级别修饰符来标记声明来覆盖这种默认行为,请参阅 访问控制级别

*声明语法

*声明多条语句可选

代码块

代码块(code block) 可以将一些声明和控制结构体组织在一起。它有如下的形式:

{
    语句
}

代码块中的“语句”包括声明、表达式和各种其他类型的语句,它们按照在源码中的出现顺序被依次执行。

代码块语法

code-block

代码块{ 多条语句可选}

导入声明

导入声明(import declaration) 让你可以使用在其他文件中声明的内容。导入语句的基本形式是导入整个模块,它由 import 关键字和紧随其后的模块名组成:

import 模块

可以对导入操作提供更细致的控制,如指定一个特殊的子模块或者指定一个模块或子模块中的某个声明。提供了这些限制后,在当前作用域中,只有被导入的符号是可用的,而不是整个模块中的所有声明。

import 导入类型 模块.符号名
import 模块.子模块

grammer_of_an_import_declaration

导入声明语法

import-declaration

导入声明特性列表可选import 导入类型可选导入路径

import-kind

导入类型typealias | struct | class | enum | protocol | let | var | func

import-path

导入路径导入路径标识符 | 导入路径标识符 . 导入路径

import-path-identifier

导入路径标识符标识符 | 运算符

常量声明

常量声明(constant declaration) 可以在程序中引入一个具有命名的常量。常量以关键字 let 来声明,遵循如下格式:

let 常量名称: 类型 = 表达式

常量声明在“常量名称”和用于初始化的“表达式”的值之间定义了一种不可变的绑定关系;当常量的值被设定之后,它就无法被更改。这意味着,如果常量以类对象来初始化,对象本身的内容是可以改变的,但是常量和该对象之间的绑定关系是不能改变的。

当一个常量被声明为全局常量时,它必须拥有一个初始值。在函数或者方法中声明一个常量时,它并不需要拥有一个初始值,只需要保证在第一次对其进行读操作之前为其设置一个值。在类或者结构体中声明一个常量时,它将作为常量属性(constant property)。常量声明不能是计算型属性,因此也没有存取方法。

如果常量名称是元组形式,元组中每一项的名称都会和初始化表达式中对应的值进行绑定。

let (firstNumber, secondNumber) = (10, 42)

在上例中,firstNumber 是一个值为 10 的常量,secnodeName 是一个值为 42 的常量。所有常量都可以独立地使用:

print("The first number is \(firstNumber).")
// 打印“The first number is 10.”
print("The second number is \(secondNumber).")
// 打印“The second number is 42.”

当常量名称的类型(: 类型)可以被推断出时,类型注解在常量声明中是可选的,正如 类型推断 中所描述的。

声明一个常量类型属性要使用 static 声明修饰符。类的常量类型属性总是隐式地被标记为 final ;你无法用 classfinal 声明修饰符实现允许或禁止被子类重写的目的。类型属性在 类型属性 中有介绍。

如果还想获得更多关于常量的信息或者想在使用中获得帮助,请参阅 常量和变量存储属性

grammer_of_a_constant_declaration

常量声明语法

constant-declaration

常量声明特性列表可选声明修饰符列表可选let 模式构造器列表

pattern-initializer-list

模式构造器列表模式构造器 | 模式构造器 , 模式构造器列表

pattern-initializer

模式构造器模式 构造器可选

initializer

构造器= 表达式

变量声明

变量声明(variable declaration) 可以在程序中引入一个具有命名的变量,它以关键字 var 来声明。

变量声明有几种不同的形式,可以声明不同种类的命名值和可变值,如存储型和计算型变量和属性,属性观察器,以及静态变量属性。所使用的声明形式取决于变量声明的适用范围和打算声明的变量类型。

注意

也可以在协议声明中声明属性,详情请参阅 协议属性声明

可以在子类中重写继承来的变量属性,使用 override 声明修饰符标记属性的声明即可,详情请参阅 重写

存储型变量和存储型变量属性

使用如下形式声明一个存储型变量或存储型变量属性:

var 变量名称: 类型 = 表达式

可以在全局范围,函数内部,或者在类和结构体的声明中使用这种形式来声明一个变量。当变量以这种形式在全局范围或者函数内部被声明时,它代表一个存储型变量。当它在类或者结构体中被声明时,它代表一个存储型变量属性(stored variable property)

用于初始化的表达式不可以在协议的声明中出现,在其他情况下,该表达式是可选的。如果没有初始化表达式,那么变量声明必须包含类型注解(: type)。

如同常量声明,如果变量名称是元组形式,元组中每一项的名称都会和初始化表达式中对应的值进行绑定。

正如名字所示,存储型变量和存储型变量属性的值会存储在内存中。

计算型变量和计算型属性

使用如下形式声明一个计算型变量或计算型属性:

var 变量名称: 类型 {
    get {
        语句
    }
    set(setter 名称) {
        语句
    }
}

可以在全局范围、函数内部,以及类、结构体、枚举、扩展的声明中使用这种形式的声明。当变量以这种形式在全局范围或者函数内部被声明时,它表示一个计算型变量。当它在类、结构体、枚举、扩展声明的上下文中被声明时,它表示一个计算型属性(computed property)

getter 用来读取变量值,setter 用来写入变量值。setter 子句是可选的,getter 子句是必须的。不过也可以将这些子句都省略,直接返回请求的值,正如在 只读计算型属性 中描述的那样。但是如果提供了一个 setter 子句,就必须也提供一个 getter 子句。

setter 的圆括号以及 setter 名称是可选的。如果提供了 setter 名称,它就会作为 setter 的参数名称使用。如果不提供 setter 名称,setter 的参数的默认名称为 newValue,正如在 便捷 setter 声明 中描述的那样。

与存储型变量和存储型属性不同,计算型变量和计算型属性的值不存储在内存中。

要获得更多关于计算型属性的信息和例子,请参阅 计算型属性

存储型变量和属性的观察器

可以在声明存储型变量或属性时提供 willSetdidSet 观察器。一个包含观察器的存储型变量或属性以如下形式声明:

var 变量名称: 类型 = 表达式 {
    willSet(setter 名称) {
        语句
    }
    didSet(setter 名称) {
        语句
    }
}

可以在全局范围、函数内部,或者类、结构体的声明中使用这种形式的声明。当变量以这种形式在全局范围或者函数内部被声明时,观察器表示一个存储型变量观察器。当它在类和结构体的声明中被声明时,观察器表示一个属性观察器。

可以为任何存储型属性添加观察器。也可以通过重写父类属性的方式为任何继承的属性(无论是存储型还是计算型的)添加观察器 ,正如 重写属性观察器 中所描述的。

用于初始化的表达式在类或者结构的声明中是可选的,但是在其他声明中则是必须的。如果可以从初始化表达式中推断出类型信息,那么可以不提供类型注解。

当变量或属性的值被改变时,willSetdidSet 观察器提供了一种观察方法。观察器会在变量的值被改变时调用,但不会在初始化时被调用。

willSet 观察器只在变量或属性的值被改变之前调用。新的值作为一个常量传入 willSet 观察器,因此不可以在 willSet 中改变它。didSet 观察器在变量或属性的值被改变后立即调用。和 willSet 观察器相反,为了方便获取旧值,旧值会传入 didSet 观察器。这意味着,如果在变量或属性的 didiset 观察器中设置值,设置的新值会取代刚刚在 willSet 观察器中传入的那个值。

willSetdidSet 中,圆括号以及其中的 setter 名称是可选的。如果提供了一个 setter 名称,它就会作为 willSetdidSet 的参数被使用。如果不提供 setter 名称,willSet 观察器的默认参数名为 newValuedidSet 观察器的默认参数名为 oldValue

提供了 willSet 时,didSet 是可选的。同样的,提供了 didSet 时,willSet 则是可选的。

要获得更多信息以及查看如何使用属性观察器的例子,请参阅 属性观察器

类型变量属性

要声明一个类型变量属性,用 static 声明修饰符标记该声明。类可以改用 class 声明修饰符标记类的类型计算型属性从而允许子类重写超类的实现。类型属性在 类型属性 章节有详细讨论。

grammer_of_a_variable_declaration

变量声明语法

variable-declaration

变量声明变量声明头 模式构造器列表

变量声明变量声明头 变量名称 类型注解 代码块

变量声明变量声明头 变量名称 类型注解 getter-setter 代码块

变量声明变量声明头 变量名称 类型注解 getter-setter 关键字代码块

变量声明变量声明头 变量名称 构造器 willSet-didSet 代码块

变量声明变量声明头 变量名称 类型注解 构造器可选willSet-didSet 代码块

variable-declaration-head

变量声明头特性列表可选声明修饰符列表可选var

variable-name

变量名称标识符

getter-setter-block

getter-setter 代码块代码块

getter-setter 代码块{ getter 子句 setter 子句可选}

getter-setter 代码块{ setter 子句 getter 子句 }

getter-clause

getter 子句特性列表可选get 代码块

setter-clause

setter 子句特性列表可选set setter 名称可选代码块

setter-name

setter 名称( 标识符 )

getter-setter-keyword-block

getter-setter 关键字代码块{ getter 关键字子句 setter 关键字子句可选}

getter-setter 关键字代码块{ setter 关键字子句 getter 关键字子句 }

getter-keyword-clause

getter 关键字子句特性列表可选get

setter-keyword-clause

setter 关键字子句特性列表可选set

willSet-didSet-block

willSet-didSet 代码块{ willSet 子句 didSet 子句可选}

willSet-didSet 代码块{ didSet 子句 willSet 子句可选}

willSet-clause

willSet 子句特性列表可选willSet setter 名称可选代码块

didSet-clause

didSet 子句特性列表可选didSet setter 名称可选代码块

类型别名声明

类型别名(type alias) 声明可以在程序中为一个既有类型声明一个别名。类型别名声明语句使用关键字 typealias 声明,遵循如下的形式:

typealias 类型别名 = 现存类型

当声明一个类型的别名后,可以在程序的任何地方使用“别名”来代替现有类型。现有类型可以是具有命名的类型或者混合类型。类型别名不产生新的类型,它只是使用别名来引用现有类型。

类型别名声明可以通过泛型参数来给一个现有泛型类型提供名称。类型别名为现有类型的一部分或者全部泛型参数提供具体类型。例如:

typealias StringDictionary<Value> = Dictionary<String, Value>

// 下列两个字典拥有同样的类型
var dictionary1: StringDictionary<Int> = [:]
var dictionary2: Dictionary<String, Int> = [:]

当一个类型别名带着泛型参数一起被声明时,这些参数的约束必须与现有参数的约束完全匹配。例如:

typealias DictionaryOfInts<Key: Hashable> = Dictionary<Key, Int>

因为类型别名可以和现有类型相互交换使用,类型别名不可以引入额外的类型约束。

如果在声明处省略所有泛型参数,一个类型别名可以传递已有类型的所有泛型参数。例如,此处声明的 Diccionario 类型别名拥有和 Dictionary 同样的约束和泛型参数。

typealias Diccionario = Dictionary

在协议声明中,类型别名可以为那些经常使用的类型提供一个更短更方便的名称,例如:

protocol Sequence {
    associatedtype Iterator: IteratorProtocol
    typealias Element = Iterator.Element
}

func sum<T: Sequence>(_ sequence: T) -> Int where T.Element == Int {
    // ...
}

假如没有类型别名,sum 函数将必须引用关联类型通过 T.Iterator.Element 的形式来替代 T.Element。

另请参阅 协议关联类型声明

grammer_of_a_type_alias_declaration

类型别名声明语法

typealias-declaration

类型别名声明特性列表可选访问级别修饰符可选typealias 类型别名名称 类型别子句 类型别名赋值

typealias-name

类型别名名称标识符

typealias-assignment

类型别名赋值= 类型

函数声明

使用函数声明(function declaration) 在程序中引入新的函数或者方法。在类、结构体、枚举,或者协议中声明的函数会作为方法。函数声明使用关键字 func,遵循如下的形式:

func 函数名称(参数列表) -> 返回类型 {
    语句
}

如果函数返回 Void 类型,返回类型可以省略,如下所示:

func 函数名称(参数列表) {
    语句
}

每个参数的类型都要标明,因为它们不能被推断出来。如果您在某个参数类型前面加上了 inout,那么这个参数就可以在这个函数作用域当中被修改。更多关于 inout 参数的讨论,请参阅 输入输出参数

函数声明中语句只包含一个表达式,可以理解为返回该表达式的值。

函数可以使用元组类型作为返回类型来返回多个值。

函数定义可以出现在另一个函数声明内。这种函数被称作嵌套函数(nested function)

大多数时候,嵌套函数都是可逃逸的函数。仅当一个嵌套函数捕获了某个确保了永不逃逸的值——例如一个输入输出参数——或者传入一个非逃逸函数参数的时候,这个嵌套函数才是非逃逸的。

更多关于嵌套函数的讨论,请参阅 嵌套函数

参数名

函数的参数列表由一个或多个函数参数组成,参数间以逗号分隔。函数调用时的参数顺序必须和函数声明时的参数顺序一致。最简单的参数列表有着如下的形式:

参数名称: 参数类型

每个参数有一个参数名称,这个名称与实参标签一样都可以在函数体内被使用。默认情况下,参数名也会被作为实参标签来使用。例如:

func f(x: Int, y: Int) -> Int { return x + y }
f(x: 1, y: 2) // 参数 x 和 y 都有标签

可以按照如下两种形式之一,重写参数名称的默认行为:

实参标签 参数名称: 参数类型 _ 参数名称: 参数类型

在参数名称前的名称会作为这个参数的显式实参标签,它可以和参数名称不同。在函数或方法调用时,相对应的参数必须使用这个实参标签。

参数名称前的下划线(_)可以去除参数的实参标签。在函数或方法调用时,相对应的参数必须去除标签。

func repeatGreeting(_ greeting: String, count n: Int) { /* Greet n times */ }
repeatGreeting("Hello, world!", count: 2) //  count 有标签, greeting 没有

输入输出参数

输入输出参数被传递时遵循如下规则:

  1. 函数调用时,参数的值被拷贝。
  2. 函数体内部,拷贝后的值被修改。
  3. 函数返回后,拷贝后的值被赋值给原参数。

这种行为被称为拷入拷出(copy-in copy-out)值结果调用(call by value result)。例如,当一个计算型属性或者一个具有属性观察器的属性被用作函数的输入输出参数时,其 getter 会在函数调用时被调用,而其 setter 会在函数返回时被调用。

作为一种优化手段,当参数值存储在内存中的物理地址时,在函数体内部和外部均会使用同一内存位置。这种优化行为被称为引用调用(call by reference),它满足了拷入拷出模式的所有要求,且消除了复制带来的开销。在代码中,要规范使用拷入拷出模式,不要依赖于引用调用。

不要使用传递给输入输出参数的值,即使原始值在当前作用域中依然可用。当函数返回时,你对原始值所做的更改会被拷贝的值所覆盖。不要依赖于引用调用的优化机制来试图避免这种覆盖。

不能将同一个值传递给多个输入输出参数,因为这种情况下的拷贝与覆盖行为的顺序是不确定的,因此原始值的最终值也将无法确定。

更多关于内存安全和内存独占权的讨论,请参阅 内存安全

如果一个闭包或者嵌套函数捕获了一个输入输出参数,那么这个闭包或者嵌套函数必须是非逃逸的。如果你需要捕获一个输入输出参数,但并不对其进行修改或者在其他代码中观察其值变化,那么你可以使用捕获列表来显式地表明这是个不可变捕获。

func someFunction(a: inout Int) -> () -> Int {
    return { [a] in return a + 1 }
}

如果你需要捕获并修改一个输入输出参数,使用一个显式局部拷贝来进行修改操作,在一些例如多线程的场景中,这样做可以确保函数返回之前所有的修改都已完成。

如果嵌套函数在外层函数返回后才调用,嵌套函数对输入输出参数造成的任何改变将不会影响到原始值。例如:

func multithreadedFunction(queue: DispatchQueue, x: inout Int) {
    // 创建一个局部拷贝并在适当时候手动拷贝回去
    var localX = x
    defer { x = localX }

    // 并行地操作 localX,然后在函数返回前一直等待
    queue.async { someMutatingOperation(&localX) }
    queue.sync {}
}

关于输入输出参数的详细讨论,请参阅 输入输出参数

特殊参数

参数可以被忽略,数量可以不固定,还可以为其提供默认值,使用形式如下:

_ : 参数类型
参数名称: 参数类型...
参数名称: 参数类型 = 默认参数值

以下划线(_)命名的参数会被显式忽略,无法在函数内使用。

一个参数的基本类型名称如果紧跟着三个点(...),会被视为可变参数。一个函数至多可以拥有一个可变参数,且必须是最后一个参数。可变参数会作为包含该参数类型元素的数组处理。举例来讲,可变参数 Int... 会作为 [Int] 来处理。关于使用可变参数的例子,请参阅 可变参数

如果在参数类型后面有一个以等号(=)连接的表达式,该参数会拥有默认值,即给定表达式的值。当函数被调用时,给定的表达式会被求值。如果参数在函数调用时被省略了,就会使用其默认值。

func f(x: Int = 42) -> Int { return x }

f()     // 有效,使用默认值
f(7)    // 有效,提供了值
f(x: 7) // 无效,该参数没有外部名称

特殊方法

枚举或结构体的方法如果会修改 self,则必须以 mutating 声明修饰符标记。

子类重写超类中的方法必须以 override 声明修饰符标记。重写方法时不使用 override 修饰符,或者被 override 修饰符修饰的方法并未对超类方法构成重写,都会导致编译错误。

枚举或者结构体中的类型方法,要以 static 声明修饰符标记,而对于类中的类型方法,除了使用 static,还可使用 class 声明修饰符标记。类中使用 class 声明修饰的方法可以被子类实现重写;类中使用 class finalstatic 声明修饰的方法不可被重写。

抛出错误的函数和方法

可以抛出错误的函数或方法必须使用 throws 关键字标记。这类函数和方法被称为抛出函数和抛出方法。它们有着下面的形式:

func 函数名称(参数列表) throws -> 返回类型 {
    语句
}

抛出函数或抛出方法的调用必须包裹在 try 或者 try! 表达式中(也就是说,在作用域内使用 try 或者 try! 运算符)。

throws 关键字是函数的类型的一部分,非抛出函数是抛出函数的子类型。所以,可以在使用抛出函数的地方使用非抛出函数。

不能仅基于函数能否抛出错误来进行函数重写。也就是说,可以基于函数的函数类型的参数能否抛出错误来进行函数重写。

抛出方法不能重写非抛出方法,而且抛出方法不能满足协议对于非抛出方法的要求。也就是说,非抛出方法可以重写抛出方法,而且非抛出方法可以满足协议对于抛出方法的要求。

重抛错误的函数和方法

函数或方法可以使用 rethrows 关键字来声明,从而表明仅当该函数或方法的一个函数类型的参数抛出错误时,该函数或方法才抛出错误。这类函数和方法被称为重抛函数和重抛方法。重新抛出错误的函数或方法必须至少有一个参数的类型为抛出函数。

func someFunction(callback: () throws -> Void) rethrows {
    try callback()
}

重抛函数或者方法不能够从自身直接抛出任何错误,这意味着它不能够包含 throw 语句。它只能够传递作为参数的抛出函数所抛出的错误。例如,在 do-catch 代码块中调用抛出函数,并在 catch 子句中抛出其它错误都是不允许的。

func alwaysThrows() throws {
    throw SomeError.error
}
func someFunction(callback: () throws -> Void) rethrows {
    do {
        try callback()
        try alwaysThrows()  // 非法, alwaysThrows() 不是一个抛出函数类型的参数
    } catch {
        throw AnotherError.error
    }
}

抛出方法不能重写重抛方法,而且抛出方法不能满足协议对于重抛方法的要求。也就是说,重抛方法可以重写抛出方法,而且重抛方法可以满足协议对于抛出方法的要求。

永不返回的函数

Swift 定义了 Never 类型,它表示函数或者方法不会返回给它的调用者。Never 返回类型的函数或方法可以称为不归,不归函数、方法要么引发不可恢复的错误,要么永远不停地运作,这会使调用后本应执行得代码就不再执行了。但即使是不归函数、方法,抛错函数和重抛出函数也可以将程序控制转移到合适的 catch 代码块。

不归函数、方法可以在 guard 语句的 else 字句中调用,具体讨论在 Guard 语句

你可以重写一个不归方法,但是新的方法必须保持原有的返回类型和没有返回的行为。

grammer_of_a_function_declaration

函数声明语法

function-declaration

函数声明函数头 函数名 泛型形参子句可选函数签名 泛型 where 子句 函数体可选

function-head

函数头特性列表可选声明修饰符列表可选func

function-name

函数名标识符 | 运算符

function-signature

函数签名参数子句列表 throws可选函数结果可选

函数签名参数子句列表 rethrows 函数结果可选

function-result

函数结果-> 特性列表可选类型

function-body

函数体代码块

parameter-clause

参数子句( ) | ( 参数列表 )

parameter-list

参数列表参数 | 参数 , 参数列表

parameter

参数外部参数名可选内部参数名 类型注解 默认参数子句可选

参数外部参数名可选内部参数名 类型注解

参数外部参数名可选内部参数名 类型注解 ...

external-parameter-name

外部参数名标识符 | _

local-parameter-name

内部参数名标识符 | _

default-argument-clause

默认参数子句= 表达式

枚举声明

在程序中使用枚举声明(enumeration declaration) 来引入一个枚举类型。

枚举声明有两种基本形式,使用关键字 enum 来声明。枚举声明体包含零个或多个值,称为枚举用例,还可包含任意数量的声明,包括计算型属性、实例方法、类型方法、构造器、类型别名,甚至其他枚举、结构体和类。枚举声明不能包含析构器或者协议声明。

枚举类型可以采纳任意数量的协议,但是枚举不能从类、结构体和其他枚举继承。

不同于类或者结构体,枚举类型并不隐式提供默认构造器,所有构造器必须显式声明。一个构造器可以委托给枚举中的其他构造器,但是构造过程仅当构造器将一个枚举用例赋值给 self 后才算完成。

和结构体类似但是和类不同,枚举是值类型。枚举实例在被赋值到变量或常量时,或者传递给函数作为参数时会被复制。更多关于值类型的信息,请参阅 结构体和枚举是值类型

可以扩展枚举类型,正如在 扩展声明 中讨论的一样。

任意类型的枚举用例

如下的形式声明了一个包含任意类型枚举用例的枚举变量:

enum 枚举名称: 采纳的协议 {
    case 枚举用例1
    case 枚举用例2(关联值类型)
}

这种形式的枚举声明在其他语言中有时被叫做可识别联合。

在这种形式中,每个用例块由关键字 case 开始,后面紧接一个或多个以逗号分隔的枚举用例。每个用例名必须是独一无二的。每个用例也可以指定它所存储的指定类型的值,这些类型在关联值类型的元组中被指定,紧跟用例名之后。

具有关联值的枚举用例可以像函数一样使用,通过指定的关联值创建枚举实例。和真正的函数一样,你可以获取枚举用例的引用,然后在后续代码中调用它。

enum Number {
    case integer(Int)
    case real(Double)
}

// f 的类型为 (Int) -> Number
let f = Number.integer

// 利用 f 把一个整数数组转成 Number 数组
let evenInts: [Number] = [0, 2, 4, 6].map(f)

要获得更多关于具有关联值的枚举用例的信息和例子,请参阅 关联值

递归枚举

枚举类型可以具有递归结构,就是说,枚举用例的关联值类型可以是枚举类型自身。然而,枚举类型的实例具有值语义,这意味着它们在内存中有固定布局。为了支持递归,编译器必须插入一个间接层。

要让某个枚举用例支持递归,使用 indirect 声明修饰符标记该用例。

enum Tree<T> {
    case empty
    indirect case node(value: T, left: Tree, right:Tree)
}

要让一个枚举类型的所有用例都支持递归,使用 indirect 修饰符标记整个枚举类型,当枚举有多个用例且每个用例都需要使用 indirect 修饰符标记的时候这将非常便利。

indirect 修饰符标记的枚举用例必须有一个关联值。使用 indirect 修饰符标记的枚举类型可以既包含有关联值的用例,同时还可包含没有关联值的用例。但是,它不能再单独使用 indirect 修饰符来标记某个用例。

拥有原始值的枚举用例

以下形式声明了一种枚举类型,其中各个枚举用例的类型均为同一种基本类型:

enum 枚举名称: 原始值类型, 采纳的协议 {
    case 枚举用例1 = 原始值1
    case 枚举用例2 = 原始值2
}

在这种形式中,每一个用例块由 case 关键字开始,后面紧跟一个或多个以逗号分隔的枚举用例。和第一种形式的枚举用例不同,这种形式的枚举用例包含一个基础值,叫做原始值,各个枚举用例的原始值的类型必须相同。这些原始值的类型通过原始值类型指定,必须表示一个整数、浮点数、字符串或者字符。原始值类型必须符合 Equatable 协议和下列字面量转换协议中的一种:整型字面量需符合 IntergerLiteralConvertible 协议,浮点型字面量需符合 FloatingPointLiteralConvertible 协议,包含任意数量字符的字符串型字面量需符合 StringLiteralConvertible 协议,仅包含一个单一字符的字符串型字面量需符合 ExtendedGraphemeClusterLiteralConvertible 协议。每一个用例的名字和原始值必须唯一。

如果原始值类型被指定为 Int,则不必为用例显式地指定原始值,它们会隐式地被赋值 012 等。每个未被赋值的 Int 类型的用例会被隐式地赋值,其值为上一个用例的原始值加 1

enum ExampleEnum: Int {
    case a, b, c = 5, d
}

在上面的例子中,ExampleEnum.A 的原始值是 0ExampleEnum.B 的原始值是 1。因为 ExampleEnum.C 的原始值被显式地设定为 5,因此 ExampleEnum.D 的原始值会自动增长为 6

如果原始值类型被指定为 String 类型,你不用明确地为用例指定原始值,每个没有指定原始值的用例会隐式地将用例名字作为原始值。

enum GamePlayMode: String {
    case cooperative, individual, competitive
}

在上面这个例子中,GamePlayMode.cooperative 的原始值是 "cooperative"GamePlayMode.individual 的原始值是 "individual"GamePlayMode.competitive 的原始值是 "competitive"

枚举用例具有原始值的枚举类型隐式地符合定义在 Swift 标准库中的 RawRepresentable 协议。所以,它们拥有一个 rawValue 属性和一个可失败构造器 init?(rawValue: RawValue)。可以使用 rawValue 属性去获取枚举用例的原始值,例如 ExampleEnum.b.rawValue。还可以根据原始值来创建一个相对应的枚举用例,只需调用枚举的可失败构造器,例如 ExampleEnum(rawValue: 5),这个可失败构造器返回一个可选类型的用例。要获得更多关于具有原始值的枚举用例的信息和例子,请参阅 原始值

访问枚举用例

使用点语法(.)来引用枚举类型的枚举用例,例如 EnumerationType.enumerationCase。当枚举类型可以由上下文推断而出时,可以省略它(但是 . 仍然需要),正如 枚举语法显式成员表达式 所述。

可以使用 switch 语句来检验枚举用例的值,正如 使用 switch 语句匹配枚举值 所述。枚举类型是模式匹配的,依靠 switch 语句 case 块中的枚举用例模式,正如 枚举用例模式 所述。

grammer_of_an_enumeration_declaration

枚举声明语法

enum-declaration

枚举声明特性列表可选访问级别修饰符可选联合风格枚举

枚举声明特性列表可选访问级别修饰符 可选原始值风格枚举

联合风格枚举indirect可选enum 枚举名称 泛型形参子句可选类型继承子句可选{ 多个联合风格枚举成员可选}

union-style-enum-members

多个联合风格枚举成员联合风格枚举成员 多个联合风格枚举成员可选

union-style-enum-member

联合风格枚举成员声明 | 联合风格枚举用例子句 | 编译控制流语句

union-style-enum-case-clause

联合风格枚举用例子句特性列表可选indirect可选case 联合风格枚举用例列表

union-style-enum-case-list

联合风格枚举用例列表联合风格枚举用例 | 联合风格枚举用例 , 联合风格枚举用例列表

union-style-enum-case

联合风格枚举用例枚举用例名称 元组类型可选

enum-name

枚举名称标识符

enum-case-name

枚举用例名称标识符

raw-value-style-enum

原始值风格枚举enum 枚举名称 泛型形参子句可选类型继承子句 泛型 where 子句 { 多个原始值风格枚举成员 }

raw-value-style-enum-members

多个原始值风格枚举成员原始值风格枚举成员 多个原始值风格枚举成员可选

raw-value-style-enum-member

原始值风格枚举成员声明 | 原始值风格枚举用例子句 | 编译控制流语句

raw-value-style-enum-case-clause

原始值风格枚举用例子句特性列表可选case 原始值风格枚举用例列表

raw-value-style-enum-case-list

原始值风格枚举用例列表原始值风格枚举用例 | 原始值风格枚举用例 , 原始值风格枚举用例列表

raw-value-style-enum-case

原始值风格枚举用例枚举用例名称 原始值赋值可选

raw-value-assignment

原始值赋值= 原始值字面量

raw-value-literal

原始值字面量数字型字面量 | 字符串型字面量 | 布尔型字面量

结构体声明

使用结构体声明(structure declaration) 可以在程序中引入一个结构体类型。结构体声明使用 struct 关键字,遵循如下的形式:

struct 结构体名称: 采纳的协议 {
    多条声明
}

结构体内可包含零个或多个声明。这些声明可以包括存储型和计算型属性、类型属性、实例方法、类型方法、构造器、下标、类型别名,甚至其他结构体、类、和枚举声明。结构体声明不能包含析构器或者协议声明。关于结构体的详细讨论和示例,请参阅 类和结构体

结构体可以采纳任意数量的协议,但是不能继承自类、枚举或者其他结构体。

有三种方法可以创建一个已声明的结构体实例:

  • 调用结构体内声明的构造器,正如 构造器 所述。

  • 如果没有声明构造器,调用结构体的成员逐一构造器,正如 结构体类型的成员逐一构造器 所述。

  • 如果没有声明构造器,而且结构体的所有属性都有初始值,调用结构体的默认构造器,正如 默认构造器 所述。

结构体的构造过程请参阅 构造过程

结构体实例的属性可以用点语法(.)来访问,正如 访问属性 所述。

结构体是值类型。结构体的实例在被赋予变量或常量,或传递给函数作为参数时会被复制。关于值类型的更多信息,请参阅 结构体和枚举是值类型

可以使用扩展声明来扩展结构体类型的行为,请参阅 扩展声明

grammer_of_a_structure_declaration

结构体声明语法

struct-declaration

结构体声明特性列表可选访问级别修饰符 可选struct 结构体名称 泛型形参子句可选类型继承子句可选泛型 where 子句可选结构体主体

struct-name

结构体名称标识符

struct-body

结构体主体{ 多条声明可选}

struct-name

结构体多个成员结构体成员 结构体多个成员可选

struct-member

结构体成员声明 | 编译控制流语句

类声明

可以在程序中使用类声明(class declaration) 来引入一个类。类声明使用关键字 class,遵循如下的形式:

class 类名: 超类, 采纳的协议 {
    多条声明
}

类内可以包含零个或多个声明。这些声明可以包括存储型和计算型属性、实例方法、类型方法、构造器、唯一的析构器、下标、类型别名,甚至其他结构体、类和枚举声明。类声明不能包含协议声明。关于类的详细讨论和示例,请参阅 类和结构体

一个类只能继承自一个超类,但是可以采纳任意数量的协议。超类紧跟在类名和冒号后面,其后跟着采纳的协议。泛型类可以继承自其它泛型类和非泛型类,但是非泛型类只能继承自其它非泛型类。当在冒号后面写泛型超类的名称时,必须写上泛型类的全名,包括它的泛型形参子句。

正如 构造器声明 所讨论的,类可以有指定构造器和便利构造器。类的指定构造器必须初始化类中声明的所有属性,并且必须在调用超类构造器之前。

类可以重写属性、方法、下标以及构造器。重写的属性、方法、下标和指定构造器必须以 override 声明修饰符标记。

为了要求子类去实现超类的构造器,使用 required 声明修饰符标记超类的构造器。子类实现超类构造器时也必须使用 required 声明修饰符。

虽然超类属性和方法声明可以被当前类继承,但是超类声明的指定构造器却不能。即便如此,如果当前类重写了超类的所有指定构造器,它就会继承超类的所有便利构造器。Swift 的类并不继承自一个通用基础类。

有两种方法来创建已声明的类的实例:

  • 调用类中声明的构造器,请参阅 构造器

  • 如果没有声明构造器,而且类的所有属性都被赋予了初始值,调用类的默认构造器,请参阅 默认构造器

类实例属性可以用点语法(.)来访问,请参阅 访问属性

类是引用类型。当被赋予常量或变量,或者传递给函数作为参数时,类的实例会被引用,而不是被复制。关于引用类型的更多信息,请参阅 结构体和枚举是值类型

可以使用扩展声明来扩展类的行为,请参阅 扩展声明

grammer_of_a_class_declaration

类声明语法

class-declaration

类声明特性列表可选访问级别修饰符可选final可选class 类名 泛型形参子句可选类型继承子句可选泛型 where 子句可选类主体

类声明特性列表可选final 访问级别修饰符可选class 类名 泛型形参子句可选类型继承子句可选泛型 where 子句可选类主体

class-name

类名标识符

class-body

类主体{ 多条声明可选}

类多个成员类成员 类多个成员可选

class-member

类成员声明 | 编译控制流语句

协议声明

协议声明(protocol declaration) 可以为程序引入一个命名的协议类型。协议声明只能在全局区域使用 protocol 关键字来进行声明,并遵循如下形式:

protocol 协议名称: 继承的协议 {
    协议成员声明
}

协议的主体包含零个或多个协议成员声明,这些成员描述了任何采纳该协议的类型必须满足的一致性要求。一个协议可以声明采纳者必须实现的某些属性、方法、构造器以及下标。协议也可以声明各种各样的类型别名,叫做关联类型,它可以指定协议的不同声明之间的关系。协议声明不能包括类、结构体、枚举或者其它协议的声明。协议成员声明会在后面进行讨论。

协议类型可以继承自任意数量的其它协议。当一个协议类型继承自其它协议的时候,来

上一篇: CSS - 子绝父相

下一篇: css之定位