6.5 枚举的原始值与相关值
枚举的原始值特性可以将枚举值与另一种数据类型进行绑定,相关值则可以为枚举值关联一些其他数据。通过相关值,开发者可以实现复杂的枚举类型。
6.5.1 枚举的原始值
上节中创建的枚举其实并没有声明一个原始值类型,Swift语言中的枚举支持开发者声明一个原始类型,并将某个已经存在的类型的值与枚举值进行绑定,枚举指定原始值类型的语法与继承的语法有些类似,示例如下:
//为枚举类型指定一个原始值类型 enum CharEnum:Character{ //通过赋值的方式来为枚举值设置一个原始值 case a = "a" case b = "b" case c = "c" case d = "d" }
如果开发者要指定枚举的原始值类型为Int类型,也可以只设置第一个枚举值的原始值,其后的枚举值的原始值会在第一个枚举值原始值的基础上依次递增,示例如下:
enum IntEnum:Int { //第一个枚举值的原始值设置为1 case一 = 1 //默认原始值为2 case二 //默认原始值为3 case三 //默认原始值为4 case四 }
通过枚举类型中的rawValue属性来获取枚举的原始值,示例如下:
//创建枚举变量 var char = CharEnum.a //获取char枚举变量的原始值 "a" var rawValue = char.rawValue
在枚举变量初始化时,开发者可以使用枚举类型加点语法的方式,如果这个枚举有指定的原始值,也可以通过枚举值的原始值来完成枚举实例的构造,示例如下:
//通过原始值构造枚举变量 一 var intEnum = IntEnum(rawValue: 1)
需要注意,通过原始值进行枚举实例的构造时,是有可能构造失败的,因为开发者传入的原始值不一定会对应到某一个枚举值。因此这个方法实际上返回的是一个Optional类型的可选值,如果构造失败,则会返回nil。
6.5.2 枚举的相关值
Swift语言在很多方面的设计都比其他编程语言更加灵活与现代,枚举的相关值语法最能够体现这一特点。
枚举类型的设计思路是帮助开发者将一些简单的同类数据进行整合。举个例子,在游戏类软件的开发中经常会使用到各种物理模型,以形状为例,开发者通常会定义一系列的枚举值作为物理形状的枚举,如圆形、三角形、矩形等,示例如下:
//定义形状枚举 enum Shape { //圆形 case circle //矩形 case rect //三角形 case triangle }
上面的代码进行了形状的定义,但是有一个问题,这种枚举值的定义方式只适合简单数据类型的定义,而不同的形状可能需要不同的参数。例如圆形需要圆心和半径来确定,矩形需要中心点与宽高来确定,三角形需要3个顶点来确定。如果对枚举类型进行实例化,可以根据不同的形状设置不同的参数,那么在使用时对开发者来说将十分方便,在Swift语言中,对枚举设置相关值就可以完成这样的需求。
在定义枚举值的时候,开发者可以为其设置一个参数列表,这个参数列表被称为枚举的相关值,示例如下:
//定义形状枚举 enum Shape { //圆形 设置圆心和半径 为相关值 case circle(center:(Double, Double), radius:Double) //矩形 设置中心 宽 高 为相关值 case rect(center:(Double, Double), width:Double, height:Double) //三角形 设置3个顶点 为相关值 case triangle(point1:(Double, Double), point2:(Double, Double), point3:(Double, Double)) }
在创建有相关值枚举的时候,开发者需要提供参数列表中所需要的参数,示例如下:
//创建圆形枚举实例 此圆的圆心为(0,0),半径为3 var circle = Shape.circle(center: (0, 0), radius: 3) //创建矩形枚举实例 此矩形的中心点为(1,1),宽度为10,高度为15 var rect = Shape.rect(center: (1, 1), width: 10, height: 15) //创建三角形枚举实例 此三角形的3个顶点为(2,2), (3,3), (2,5) var triangle = Shape.triangle(point1: (2, 2), point2: (3, 3), point3: (2, 5))
在switch-case结构语句中,匹配到枚举后,可以通过参数捕获的方式来获取枚举实例的相关值,这里捕获到的相关值参数,可以在开发者的代码中使用,示例如下:
//写一个匹配函数 参数为Shape枚举类型 func shapeFunc(param:Shape){ switch param { //进行参数捕获 case let .circle(center, radius): print("此圆的圆心为:\(center),半径为:\(radius)") case let .rect(center, width, height): print("此矩形的中心为:\(center),宽为:\(width),高为:\(height)") case let .triangle(point1, point2, point3): print("此三角形的3个顶点分别为:\(point1), \(point2), \(point3)") } } shapeFunc(param: circle) shapeFunc(param: rect) shapeFunc(param: triangle)
6.5.3 递归枚举
递归枚举是Swift语言枚举相关语法中比较难于理解的一个语法,但是如果可以将其完全掌握,则可以编写出结构十分优美的代码。要完全明白递归枚举的意义与使用,首先需要明白两点—— 递归与枚举实质。
递归是一种代码算法技巧,它并不区分语言,各种高级语言都可以实现自己的递归算法。简单来说,递归就是程序调用自身的编程技巧。针对函数来说,递归函数就是在函数内部进行了此函数本身的调用。读者需要注意一点,递归算法效率十分高,但是其性能资源的耗费也十分严重。在大多数情况下,开发者应该尽量避免使用递归。前面的章节中曾经使用过循环结构来计算正整数的阶乘,例如5! =5*4*3*2*1=120,这里我们要使用递归算法来实现一个正整数的阶乘,代码如下:
//使用递归算法来实现计算正整数的阶乘 func mathsFunc(param:Int)->Int{ let tmp = param-1 if tmp>0 { //递归 return mathsFunc(param: tmp) * param }else{ return 1 } } mathsFunc(param: 5)
函数的功能是进行数据计算,递归函数只是使用递归的算法来进行数据的计算。而枚举则不同,枚举的功能是数据的描述。例如6.5.2节中创建的形状枚举,其中只是对几种形状的数据结构进行描述和定义,它并不具有数据计算的功能。那么递归枚举其实就是使用递归的方式来进行数据描述。
来使用枚举描述加减乘除四则表达式示例代码如下:
//使用枚举来模拟加减乘除四则运算 enum Expression { //表示加法运算 两个相关值param1与param2代表进行加法运算的两个参数 case add(param1:Int, param2:Int) //表示减法运算 两个相关值param1与param2代表进行减法运算的两个参数 case sub(param1:Int, param:Int) //表示乘法运算 两个相关值param1与param2代表进行乘法运算的两个参数 case mul(param1:Int, param2:Int) //表示除法运算 两个相关值param1与param2代表进行除法运算的两个参数 case div(param1:Int, param2:Int) }
使用上面创建的枚举来描述四则运算表达式,示例如下:
//表示表达式5+5 var exp1 = Expression.add(param1: 5, param2: 5) //表示表达式10-5 var exp2 = Expression.sub(param1: 10, param2: 5) //表示表达式5*5 var exp3 = Expression.mul(param1: 5, param2: 5) //表示表达式10/2 var exp4 = Expression.div(param1: 10, param2: 2)
这里读者需要注意,变量exp1、exp2、exp3、exp4只是四则运算表达式的描述,并没有运算功能。可以简单地理解为:Expression枚举模拟的是一种四则运算表达式类型,如果要进行运算,开发者还需要实现具体的功能函数。
可以发现,Expression能够描述的表达式只是单运算表达式,它不能够进行复合表达式的描述,例如对于((5+5)*2-8)/2表达式的描述。分析这类复合表达式,其实质只是将单运算表达式作为计算的参数传入另一个单运算表达式。类比于Swift语言中的枚举,一个枚举值的相关值类型,可以设置为这个枚举本身的类型,通过这种递归的方式就可以实现复合表达式的描述,将前面创建的Expression枚举修改如下:
//使用枚举来模拟加减乘除四则运算 enum Expression { //描述单个数字 case num(param:Int) //表示加法运算 将Expression作为相关值参数类型 indirect case add(param1:Expression, param2:Expression) //表示减法运算 indirect case sub(param1:Expression, param2:Expression) //表示乘法运算 indirect case mul(param1:Expression, param2:Expression) //表示除法运算 indirect case div(param1:Expression, param2:Expression) }
使用indirect关键字修饰的枚举值表示这个枚举值是可递归的,即此枚举值中的相关值可以使用其枚举类型本身。使用修改后的Expression枚举来描述复合表达式((5+5)*2-8)/2的代码如下:
//创建单值5 var num5 = Expression.num(param: 5) //进行表达式5+5描述 var exp1 = Expression.add(param1: num5, param2: num5) //创建单值2 var num2 = Expression.num(param: 2) //进行表达式(5+5)*2的描述 var exp2 = Expression.mul(param1: exp1, param2: num2) //创建单值8 var num8 = Expression.num(param: 8) //进行表达式(5+5)*2-8的描述 var exp3 = Expression.sub(param1: exp2, param2: num8) //进行表达式((5+5)*2-8)/2的描述 var expFinal = Expression.div(param1: exp3, param2: num2)
最后得到的变量expFinal就是对((5+5)*2-8)/2的描述。另外,读者可以为这四则表达式枚举类型Expression实现一个函数来进行运算,在开发中将描述与运算结合,能够编写出十分优美的代码,处理递归枚举通常会采用递归函数,函数方法实现示例如下:
//这个递归函数的作用是将Expression描述的表达式进行运算 结果返回 func expressionFunc(param:Expression) -> Int { switch param { //单值直接返回 case let .num(param): return param case let .add(param1, param2): //返回加法运算结果 return expressionFunc(param: param1)+expressionFunc(param: param2) case let .sub(param1, param2): //返回减法运算结果 return expressionFunc(param: param1)-expressionFunc(param:param2) case let .mul(param1, param2): //返回乘法运算结果 return expressionFunc(param: param1)*expressionFunc(param: param2) //返回除法运算结果 case let .div(param1, param2): return expressionFunc(param: param1)/expressionFunc(param: param2) } } //进行((5+5)*2-8)/2运算 结果为6 expressionFunc(param: expFinal)
关于递归枚举还有一点需要注意,如果一个枚举中所有的枚举值都是可递归的,开发者可以直接将整个枚举类型声明为可递归的,示例如下:
//使用枚举来模拟加减乘除四则运算 indirect enum Expression { //描述单个数字 case num(param:Int) //表示加法运算 将Expression作为相关值参数类型 case add(param1:Expression, param2:Expression) //表示减法运算 case sub(param1:Expression, param2:Expression) //表示乘法运算 case mul(param1:Expression, param2:Expression) //表示除法运算 case div(param1:Expression, param2:Expression) }