摘要

枚举和结构体都是值类型,而类是引用类型。实际开发中,你定义的大多数自定义数据类型将会是结构和枚举。

结构体和类是通用的灵活结构,它们成为程序代码的构建块。 您可以定义属性和方法,以使用与定义常量,变量和函数相同的语法向结构和类添加功能。

结构体和类都能定义属性、方法、下标、构造器,使用扩展,遵循协议。但是继承、类型转换、解析器和引用计数是类所具有而结构体没有的。类支持的其他功能是以增加复杂性为代价的。开发时应首选结构体,必要时再使用类。

与其他编程语言不同,Swift不需要您为自定义结构和类创建单独的接口和实现文件。 在Swift中,您可以在一个文件中定义一个结构或类,并且该类或结构的外部接口会自动提供给其他代码使用。

传统上将类的实例称为对象。 但是,Swift结构体和类在功能上比其他语言要紧密得多,本章的大部分内容描述了适用于类或结构类型的实例的功能。 因此,使用了更通用的术语实例。

1. 枚举

枚举(enumeration)为一组相关值定义一个通用类型,并使你能够在代码内以类型安全的方式使用这些值。在Swift 中,你不必为每种枚举 case 都提供值。 当然你也可以使用字符串、字符或任何整数或浮点类型的值,为每种枚举情况提供一个原始值(raw value)。

另外,枚举 case 可以指定要存储的任何类型的关联值以及每个不同的 case 值。您可以将一组常见的相关 case 定义为一个枚举的一部分,每个case 都有一组与之相关的适当类型的不同值。

Swift中的枚举本身就是一等类型( first-class types)。 它们采用了许多传统上仅由类支持的功能,例如提供枚举当前值的附加信息的计算属性,以及提供与枚举所表示的值相关的功能的实例方法。 枚举还可以定义初始值设定项以提供初始大小写值。 可以扩展其功能,使其超出其最初的实现; 并可以遵循协议以提供标准功能。

1.1 定义和使用枚举

使用 enum 关键字定义枚举,使用 case 关键字定义其中的每一个案例。

//	1.Swift中的枚举类型并非默认为整型,需要显式设置其类型

enum CompassPoint: String {
	case north
	case south
	case east
	case west
}

//	2.更简洁的写法:可以将多个 case 写在一行上

enum Planet {
    case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}

//	3.可以像使用其他类型一样使用枚举
var directionToHead = CompassPoint.west
directionToHead = CompassPoint.north

1.2 在 Switch 语句中匹配单个枚举值

示例代码如下:

//	1.在 Switch 语句中匹配单个枚举值

directionToHead = .south
switch directionToHead {
case .north:
    print("大多数行星都有北极")
case .south:
    print("去南极看企鹅去")
case .east:
    print("太阳从东边升起")
case .west:
    print("西部蓝天")
}

//	2.无法为每个枚举case提供值时,使用 default语句

let somePlanet = Planet.earth
switch somePlanet {
case .earth:
    print("目前唯一的宜居星球")
default:
    print("目前不适合人类居住")
}
// 输出: 目前唯一的宜居星球

1.3 遍历枚举的 case

遵循 : CaseIterable协议 或 使用 for-in 循环。记住,结合 allCases 属性使用。

//	: CaseIterable
enum LanguageVersion: CaseIterable {
    case English, Chinese, French, Japanese
}
let numberOfChoices = LanguageVersion.allCases.count
print("\(numberOfChoices) 个语言版本可用")
// 输出:4 个语言版本可用

//	使用 for-in 循环
for language in LanguageVersion.allCases {
    print(language)
}
//English
//Chinese
//French
//Japanese

1.4 关联值

有时可以将其他类型的值与枚举的case值一起存储,这种附加信息就称为关联值(associated value)。这样,你就可以使你的 case 值改变。你可以定义Swift枚举来存储任何给定类型的关联值,每种枚举的值类型可以不同。其他编程语言中,这被称为区分联合、标记联合或变体。

例如,假设库存跟踪系统需要通过两种不同类型的条形码来跟踪产品。 某些产品用UPC格式的一维条形码标记,它使用数字0到9。每个条形码都有一个数字系统数字,后跟五个制造商代码数字和五个产品代码数字。 这些数字后面跟一个校验位,以验证代码是否已正确扫描:

其他产品带有QR码格式的2D条形码标签,可以使用任何ISO 8859-1字符,并且可以编码最长为2953个字符的字符串:

库存跟踪系统可以方便地将UPC条形码存储为四个整数的元组,并将QR码条形码存储为任意长度的字符串。

在Swift中,定义两种类型产品条形码的枚举类型. 解读如下:

“定义一个称为条形码的枚举类型,该枚举类型可以采用具有关联类型(Int,Int,Int,Int)的 upc 值,也可以采用具有String类型的关联值的 qrCode 值。”

//定义条形码
enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")


switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}
// 输出 "QR code: ABCDEFGHIJKLMNOP."

//	如果都是常量或变量,可以有更简单的写法
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
    print("QR code: \(productCode).")
}
// 输出 "QR code: ABCDEFGHIJKLMNOP."

1.5 原始值

前面的条形码示例展示了如何声明存储了不同类型的关联值。 作为关联值的替代方法,枚举 case 可以预先填充默认值(称为原始值),这些默认值都是同一类型。原始值可以是字符串,字符或任何整数或浮点数类型。 每个原始值在其枚举声明中必须唯一。

//	原始值
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

隐式指定的原始值(Implicitly Assigned Raw Values):通过枚举 case 的 rawValue 属性访问其原始值。

//	隐式指定的原始值

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
//mercury为 1,其他按顺序递增:2,3,...


enum CompassPoint: String {
    case north, south, east, west
}
//CompassPoint.south的原始值为“south”,其余雷同


let earthsOrder = Planet.earth.rawValue
// 输出: 3

let sunsetDirection = CompassPoint.west.rawValue
// 输出: "west"

如果您使用原始值类型定义枚举,则枚举会自动接收一个初始化程序,该初始化程序采用原始值类型的值(作为称为rawValue的参数),并返回枚举大小写或nil。 您可以使用此初始化程序尝试创建枚举的新实例。

let possiblePlanet = Planet(rawValue: 7)

let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
    case .earth:
        print("宜居星球")
    default:
        print("不适宜人类生存")
    }
} else {
    print("\(positionToFind)无行星")
}
//

1.6 递归枚举

递归枚举(recursive enumeration)也是一种枚举类型,该枚举将其中一个 case 实例作为一个或多个枚举 case 的关联值。使用 indirect 关键字

//第一种写法
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

//第二种写法
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}


let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))


func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}

print(evaluate(product))
//	输出:18

2. 结构体和类

2.1 结构体和类的比较

共同点

  • 定义属性( properties)以存储值
  • 定义提供功能的方法(methods)
  • 定义下标(subscripts)后使用下标语法提供对其值的访问
  • 定义构造器(initializers )设置初始状态
  • 使用使用扩展(extensions)丰富功能
  • 通过遵循协议(protocols)来提供某种标准功能

类具有而结构体没有的功能

  • 继承(inheritance):一个类可以继承另一个类的特征。
  • 类型转换(type casting):运行时检查和解释类的实例。
  • 解析器(deinitializers ): 可以释放其已分配到的所有资源。
  • 引用计数(reference counting): 允许对一个类实例进行多个引用。.

2.2 结构体或类的声明语法及实例的创建

结构体和类的声明语法类似。声明一个新的结构体或类后,你就创建了一个新的 Swift 类型,和 String、Bool、Int 一样。因此结构体或类的名称首字母应大写。

//	1.声明语法
struct SomeStructure {
    // 定义结构体
}
class SomeClass {
    // 定义类
}

//	2.结构体
struct Resolution {
    var width = 0
    var height = 0
}

//	3.类
class VideoMode {
    var resolution = Resolution()	
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

//	创建结构体和类的实例
let someResolution = Resolution()
let someVideoMode = VideoMode()

示例代码定义了一个名为 Resolution 的结构体,用来描述基于像素显示的分辨率。在结构体 Resolution 中声明了分别名为 width 和 height 的存储属性(stored properties)。存储属性是值绑定并存储为结构体或类的一部分的常量或变量。

同时,定义了一个名为 VideoMode 的结构体,用来描述特定的视频显示模式。类 VideoMode有 4 个可变的存储属性,其中第一个(resolution) 使用了结构体 Resolution( )的一个实例进行初始化设置。

2.3 访问一个实例的属性

使用.语法(dot syntax)”,你可以访问一个实例额的属性或子属性,你也可以为实例的可变属性赋值。

print("someResolution 的宽度(width) 是 \(someResolution.width)")
// 输出:someResolution 的宽度(width) 是 0

print("someVideoMode 的宽度(width) 是 \(someVideoMode.resolution.width)")
// 输出:someVideoMode 的宽度(width) 是 0

someVideoMode.resolution.width = 1280
print("现在,someVideoMode 的宽度(width) 是 \(someVideoMode.resolution.width)")
// 输出:现在,someVideoMode 的宽度(width) 是 1280

2.4 结构体的成员构造器

所有结构体都有一个自动生成的成员构造器,用来初始化结构体的新实例的成员属性。 可以通过名称将新实例的属性的初始值传递给成员构造器:

let vga = Resolution(width: 640, height: 480)

而类与结构体不同,类的实例没有默认的成员构造器。

3. 值类型与引用类型

值类型(value type):一种在将值分配给变量、常量或将其传递给函数时会复制其值的类型。Swift 的所有基本数据类型(整型、浮点型、布尔型、字符串、数组和字典等)都是值类型,并且都由结构体实现。

Swift 中,所有的结构体和枚举类型也都是值类型。这就是说,你创建的任何结构体和枚举实例,以及作为它们属性的任何值类型,都会在代码中传递时被复制。

引用类型(reference types):与值类型不同,将引用类型分配给变量、常量或将其传递给函数时,不会复制引用类型, 而是使用对相同现有实例的引用。类属于引用类型。


参考资料:Swift-Enumerations Swift-ClassesAndStructures

文档协议:CC-BY-4.0(共享-演绎-署名-不附加限制)