11.3 枚举

在C和Objective-C中,枚举用来管理一组相关常量集合,通过使用枚举可以提高程序的可读性,使代码更清晰,更易于维护。而在Swift中,枚举的作用已经不仅仅是定义一组常量、提高程序的可读性了,它还具有了面向对象特性。

我们先来看Swift声明。Swift中也是使用enum关键词声明枚举类型,具体定义放在一对大括号内,枚举的语法格式如下:

  1. enum 枚举名
  2. {
  3. 枚举的定义
  4. }

“枚举名”是该枚举类型的名称。它首先应该是有效的标识符,其次应该遵守面向对象的命名规范。它应该是一个名称,如果采用英文单词命名,首字母应该大写,尽量用一个英文单词。这个命名规范也适用于类和结构体的命名。“枚举的定义”是枚举的核心,它由一组成员值和一组相关值组成。

11.3.1 成员值

在枚举类型中定义一组成员,与C和Objective-C中枚举的主要作用是一样的,不同的是,在C和Objective-C中成员值是整数类型,因此在C和Objective-C中枚举类型就是整数类型。

而在Swift中,枚举的成员值默认情况下不是整数类型,以下代码是声明枚举示例:

  1. enum WeekDays {
  2. case Monday
  3. case Tuesday
  4. case Wednesday
  5. case Thursday
  6. case Friday
  7. }

上述代码声明了WeekDays枚举,表示一周中的每个工作日,其中定义了5个成员值:MondayTuesdayWednesdayThursdayFriday,这些成员值并不是整数类型。

在这些成员值前面还要加上case关键字,也可以将多个成员值放在同一行,用逗号隔开,如下所示:

  1. enum WeekDays {
  2. case Monday, Tuesday, Wednesday, Thursday, Friday
  3. }

下面我们看一个示例,代码如下:

  1. var day = WeekDays.Friday
  2. day = WeekDays.Wednesday
  3. day = .Monday
  4. func writeGreeting(day : WeekDays) {
  5. switch day {
  6. case .Monday:
  7. println("星期一好!")
  8. case .Tuesday :
  9. println("星期二好!")
  10. case .Wednesday :
  11. println("星期三好!")
  12. case .Thursday :
  13. println("星期四好!")
  14. case .Friday :
  15. println("星期五好!")
  16. }
  17. }
  18. writeGreeting(day)
  19. writeGreeting(WeekDays.Friday)

上述代码是使用WeekDays枚举的一个示例,其中第①行代码是把WeekDays枚举的成员值Friday赋值给变量day,第②行和第③行代码也是给变量day赋值,我们可以采用完整的“枚举类型名.成员值”的形式,也可以省略枚举类型采用“.成员值”的形式(见代码第③行)。这种省略形式能够访问的前提是,Swift能够根据上下文环境推断类型。因为我们已经在第①行和第②行给day变量赋值,所以即使第③行代码采用缩写,Swift也能够推断出数据类型是WeekDays

为了方便反复调用,我们在第④行定义了writeGreeting函数。第⑤~⑥行代码使用了switch语句,枚举类型与switch语句能够很好地配合使用。在switch语句中使用枚举类型可以没有default分支,这在使用其他类型时是不允许的。使用default分支的代码如下:

  1. func writeGreeting(day : WeekDays) {
  2. switch day {
  3. case .Monday:
  4. println("星期一好!")
  5. case .Tuesday :
  6. println("星期二好!")
  7. case .Wednesday :
  8. println("星期三好!")
  9. case .Thursday :
  10. println("星期四好!")
  11. default:
  12. println("星期五好!")
  13. }
  14. }

需要注意,在switch中使用枚举类型时,switch语句中的case必须全面包含枚举中的所有成员,不能多也不能少,包括使用default的情况下,default也表示某个枚举成员。在上面的示例中,default表示的是Friday枚举成员,在这种情况下,Friday枚举成员的case分支不能再出现了。

上述代码第⑦行和第⑧行是调用函数writeGreeting,传递的参数可以是WeekDays变量(见代码第⑦行),也可以是WeekDays中的成员值(见代码第⑧行)。

11.3.2 原始值

出于业务上的需要,要为每个成员提供某种具体类型的默认值,我们可以为枚举类型提供原始值(raw values)声明,这些原始值类型可以是:字符、字符串、整数和浮点数等。

原始值枚举的语法格式如下:

  1. enum 枚举名 : 数据类型
  2. {
  3. case 成员名 = 默认值
  4. ......
  5. }

在“枚举名”后面跟“:”和“数据类型”就可以声明原始值枚举的类型,然后在定义case成员的时候需要提供默认值。

以下代码是声明枚举示例:

  1. enum WeekDays : Int {
  2. case Monday = 0
  3. case Tuesday = 1
  4. case Wednesday = 2
  5. case Thursday = 3
  6. case Friday = 4
  7. }

我们声明的WeekDays枚举类型的原始值类型是Int,需要给每个成员赋值,只要是Int类型都可以,但是每个分支不能重复。我们还可以采用如下简便写法,只需要给第一个成员赋值即可,后面的成员值会依次加1。

  1. enum WeekDays : Int {
  2. case Monday = 0, Tuesday, Wednesday, Thursday, Friday
  3. }

以下是完整的示例代码:

  1. var day = WeekDays.Friday
  2. func writeGreeting(day : WeekDays) {
  3. switch day {
  4. case .Monday:
  5. println("星期一好!")
  6. case .Tuesday :
  7. println("星期二好!")
  8. case .Wednesday :
  9. println("星期三好!")
  10. case .Thursday :
  11. println("星期四好!")
  12. case .Friday :
  13. println("星期五好!")
  14. }
  15. }
  16. let friday = WeekDays.Friday.toRaw() ①
  17. let thursday = WeekDays.fromRaw(3) ②
  18. if (WeekDays.Friday.toRaw() == 4) { ③
  19. println("今天是星期五")
  20. }
  21. writeGreeting(day)
  22. writeGreeting(WeekDays.Friday)

上述代码与上一节的示例非常类似,相同的地方将不再赘述,我们重点看有标号的代码部分。其中第①行代码是通过WeekDays.Friday的方法toRaw()转换为原始值。虽然在定义的时候Friday被赋值为4,但是并不等于WeekDays.Friday就是整数4了,而是它的原始值为整数4,因此下面的比较是错误的。

  1. if (WeekDays.Friday == 4) {
  2. println("今天是星期五")
  3. }

我们需要使用WeekDays.Friday的原始值进行比较,见代码第③行。

toRaw()方法是将成员值转换为原始值,相反fromRaw()方法是将原始值转换为成员值,见代码第②行。

11.3.3 相关值

在Swift中除了可以定义一组成员值,还可以定义一组相关值(associated values),它有点类似于C中的联合类型。下面看一个枚举类型的声明:

  1. enum Figure {
  2. case Rectangle(Int, Int)
  3. case Circle(Int)
  4. }

枚举类型Figure(图形)有两个相关值: Rectangle(矩形)和Circle(圆形)。RectangleCircle是与Figure有关联的相关值,它们都是元组类型,对于一个特定的Figure实例,只能是其中一个相关值。从这一点来看,枚举类型的相关值类似于C中的联合类型。

下面我们看一个示例,代码如下:

  1. func printFigure(figure : Figure) {
  2. switch figure {
  3. case .Rectangle(let width, let height):
  4. println("矩形的宽:\(width) 高:\(height)")
  5. case .Circle(let radius):
  6. println("圆形的半径:\(radius)")
  7. }
  8. }
  9. var figure = Figure.Rectangle(1024, 768)
  10. printFigure(figure)
  11. figure = .Circle(600)
  12. printFigure(figure)

上述代码使用前文声明的枚举类型Figure,为了能够反复调用,我们在代码第①行定义了一个函数。其中代码第②~③行使用了switch语句,为了从相关值中提取数据,可以在元组字段前面添加letvar。如果某个相关值元组中字段类型一致,需要全部提取,则可以在相关值前面添加letvar。我们可以使用如下方式修改Rectangle分支:

  1. switch figure {
  2. case let .Rectangle( width, height):
  3. println("矩形的宽:\(width) 高:\(height)")
  4. case .Circle(let radius):
  5. println("圆形的半径:\(radius)")
  6. }

上述代码第④行var figure = Figure.Rectangle(1024, 768)是声明变量figure,并初始化为Rectangle类型的相关值。第⑤行代码是调用函数printFigure打印输出结果:

  1. 矩形的宽:1024 高:768

代码第⑥行figure = .Circle(600)初始化为Circle类型的相关值。第⑦行代码调用函数printFigure并打印输出结果:

  1. 圆形的半径:600