iOS开发Swift_VIP专场汇总ios开发资源收集

Swift中enum、struct、class三者异同

2016-11-20  本文已影响3057人  老板娘来盘一血

前言

由于在开发过程中常常需要用到系统提供的基础类型之外的的类型,所以Swift允许我们根据自己的需要构建属于自己的类型系统以便于更加灵活和方便的开发程序并将其称之为 “named types“

Swift主要为我们提供了以下四种”named types“ 分别是:enumstructclassprotocol。相信熟悉iOS开发的同学们对于枚举、结构体和类的概念一点都不陌生。相比于前辈Objective-C中的这三者,Swift将enumstruct变得更加灵活且强大,并且赋予了他们很多和class相同的属性实现更加丰富多彩的功能,以至于有时候我们很难分清他们到底有什么区别以及我该什么时候用哪种类型,接下来本文将重点介绍一下在Swift中enumstruct的定义和新特性以及两者与class之间的异同,也是自己学习Swift以来的阶段性总结。

�枚举(enum)

//定义一个表示学生类型的全新枚举类型 StudentType,他有三个成员分别是pupil(小学生,玩LOL最怕遇到这种队友了)、middleSchoolStudent(中学生,现在的中学生都很拽)、collegeStudents(大学生,据说大学生活很不错,注意断句)
enum StudentType {
    case pupil
    case middleSchoolStudent
    case collegeStudent
}

上面的代码可以读作:如果存在一个StudentType的实例,他要么是pupil (小学生)、要么是middleSchoolStudent(中学生)、要么是collegeStudent(大学生)。注意,和C、Objective-C中枚举的不同,Swift 中的枚举成员在被创建时不会分配一个默认的整数值。而且不需要给枚举中的每一个成员都提供值(如果你需要也是可以的)。如果一个值(所谓“原始值”)要被提供给每一个枚举成员,那么这个值可以是字符串、字符、任意的整数值,或者是浮点类型(引自文档翻译)。简单说Swift中定义的枚举只需要帮助我们表明不同的情况就够了,他的成员可以没有值,也可以有其他类型的值(不局限于整数类型)。
枚举中有两个很容易混淆的概念:原始值(raw value)关联值(associated value),两个词听起来比较模糊,下面简单介绍一下:

enum StudentType: Int{
      case pupil = 10
      case middleSchoolStudent = 15
      case collegeStudents = 20
}

定义好StudentType成员的原始值之后,我们可以使用枚举成员的rawValue属性来访问成员的原始值,或者是使用原始值初始化器来尝试创建一个枚举的新实例

//  常量student1值是 10
let student1 = StudentType.pupil.rawValue
//  变量student2值是 15
var student2 = StudentType.middleSchoolStudent.rawValue
//  使用成员rawValue属性创建一个`StudentType`枚举的新实例
let student3 = StudentType.init(rawValue: 15)
//  student3的值是 Optional<Senson>.Type
type(of: student3)
//  student4的值是nil,因为并不能通过整数30得到一个StudentType实例的值
let student4 = StudentType.init(rawValue: 30)

使用原始值初始化器这种方式初始化创建得到StudentType的实例student4是一个StudentType可选类型,因为并不是给定一个年龄就能找到对应的学生类型,比如在StudentType中给定年龄为30就找不到对应的学生类型(很可能30岁的人已经是博士了)。所以原始值初始化器是一个可失败初始化器。
总结一句:原始值是为枚举的成员们绑定了一组类型必须相同值不同的固定的值(可能是整型,浮点型,字符类型等等)。这样很好解释为什么提供原始值的时候用的是等号。

//定义一个表示学生类型的枚举类型 StudentType,他有三个成员分别是pupil、middleSchoolStudent、collegeStudents
enum StudentType {
    case pupil(String)
    case middleSchoolStudent(Int, String)
    case collegeStudents(Int, String)
}

这里我们并没有为StudentType的成员提供具体的值,而是为他们绑定了不同的类型,分别是pupil绑定String类型、middleSchoolStudentcollegeStudents绑定(Int, String)元祖类型。接下来就可以创建不同StudentType枚举实例并为对应的成员赋值了。

    //student1 是一个StudentType类型的常量,其值为pupil(小学生),特征是"have fun"(总是在玩耍)
let student1 = StudentType.pupil("have fun")
    //student2 是一个StudentType类型的常量,其值为middleSchoolStudent(中学生),特征是 7, "always study"(一周7天总是在学习)
let student2 = StudentType.middleSchoolStudent(7, "always study")
    //student3 是一个StudentType类型的常量,其值为collegeStudent(大学生),特征是 7, "always LOL"(一周7天总是在撸啊撸)
let student3 = StudentType.middleSchoolStudent(7, "always LOL")

这个时候如果需要判断某个StudentType实例的具体的值就需要这样做了:

switch student3 {
      case .pupil(let things):
          print("is a pupil and \(things)")
      case .middleSchoolStudent(let day, let things):
          print("is a middleSchoolStudent and \(day) days \(things)")
      case .collegeStudent(let day, let things):
          print("is a collegeStudent and \(day) days \(things)")
    }  

控制台输出:is a collegeStudent and 7 days always LOL,看到这你可能会想,是否可以为一个枚举成员提供原始值并且绑定类型呢,答案是不能的!因为首先给成员提供了固定的原始值,那他以后就不能改变了;而为成员提供关联值(绑定类型)就是为了创建枚举实例的时候赋值。这不是互相矛盾吗。

func factorial(n: Int)->Int {
      if n > 0 {
          return n * factorial(n: n - 1)
      } else {
          return 1
      }
  }
  //1 * 2 * 3 * 4 * 5 * 6 = 720
  let sum = factorial(n: 6)

函数factorial (n: int)-> Int在执行过程中很明显的调用了自身。结合枚举的概念我们这里可以简单的理解为递归枚举类似上面将枚举值本身传入给成员去判断的情况。因为实在没找到很好体现递归枚举的例子,而且本人对递归枚举的使用场景都用在哪些地方还不是很了解,所以呢这里就不献丑了。

可以看出Swift中枚举变得更加灵活和复杂,有递归枚举的概念,还有很多和Class类似的特性,比如:计算属性用来提供关于枚举当前值的额外信息;实例方法提供与枚举表示值相关的功能;定义初始化器来初始化成员值;而且能够遵循协议来提供标准功能等等,由于笔者目前还没有更加深入的学习这些东西,所以这些内容有机会将在后面的章节讲到。

� 结构体(struct)

 //定义一个 Student(学生)类型的结构体用于表示一个学生,Student的成员分别是语、数、外三科`Int`类型的成绩
 struct Student {
    var chinese: Int
    var math: Int
    var english: Int
 }

看到这里熟悉Swift的同学可能已经发现了一点结构体和类的区别了:定义结构体类型时其成员可以没有初始值。如果使用这种格式定义一个类,编译器是会报错的,他会提醒你这个类没有被初始化。

 //使用Student类型的结构体创建Student类型的实例(变量或常量)并初始化三个成员(这个学生的成绩会不会太好了点)
 let student2 = Student(chinese: 90, math: 80, english: 70)

所有的结构体都有一个自动生成的成员初始化器,你可以使用它来初始化新结构体实例的成员就像上面一样(前提是没有自定义的初始化器)。如果我们在定义Student时为他的成员赋上初值,那么下面的代码是编译通过的:

struct Student {
    var chinese: Int = 50
    var math: Int = 50
    var english: Int = 50
}
let student2 = Student(chinese: 90, math: 80, english: 70)
let student4 = Student()

总结一句:定义结构体类型时其成员可以没有初始值,但是创建结构体实例时该实例的成员必须有初值。

struct Student {
    var chinese: Int = 50
    var math: Int = 50
    var english: Int = 50
        init() {}
        init(chinese: Int, math: Int, english: Int) {
              self.chinese = chinese
              self.math = math
             self.english = english
        }
        init(stringScore: String) {
             let cme = stringScore.characters.split(separator: ",")
             chinese = Int(atoi(String(cme.first!)))
             math = Int(atoi(String(cme[1])))
             english = Int(atoi(String(cme.last!)))
        }
    }
    let student6 = Student()
    let student7 = Student(chinese: 90, math: 80, english: 70)
    let student8 = Student(stringScore: "70,80,90")

一旦我们自定义了初始化器,系统自动的初始化器就不起作用了,如果还需要使用到系统提供的初始化器,在我们自定义初始化器后就必须显式的定义出来。

//更改某个学生某门学科的成绩
func changeChinese(num: Int, student: inout Student){
    student.chinese += num
}
changeChinese(num: 20, student: &student7)

此时student7的语文成绩就由原来的90被修改到了110,但是此方法有两个明显的弊端:1,学生的语文成绩chineseStudent结构体的内部成员,一个学生的某科成绩无需被Student的使用者了解。即我们只关心学生的语文成绩更改了多少,而不是关心学生语文成绩本身是多少。2,更改一个学生的语文成绩本身就是和Student结构体内部成员计算相关的事情,我们更希望达到形如:student7.changeChinese(num: 10) 的效果,因为只有学生本身清楚自己需要将语文成绩更改多少(更像是面向对象封装的思想)。很明显此时changeChinese(num:)方法是Student结构体内部的方法而不是外部的方法,所以我定义了一个修改某个学生数学成绩的内部方法用于和之前修改语文成绩的外部方法对比:

struct Student {
    var chinese: Int = 50
    var math: Int = 50
    var english: Int = 50
   //修改数学成绩
    mutating func changeMath(num: Int) {
        self.math += num
    }
  }
  var student7 = Student(chinese: 20, math: 30, english: 40)
  //更改分数中语文学科的成绩
  func changeChinese(num: Int, student: inout Student){
      student.chinese += num
    }
  changeChinese(num: 20, student: &student7)
  student7.changeMath(num: 10)

尽管两者都能达到同样的效果,但是把修改结构体成员的方法定义在结构体内部显得更加合理同时满足面向对象封装的特点。以上两点就是我们为Student结构体内部添加changeMath(num:)的原因,他让我们把类型相关的计算表现的更加自然和统一,即自己的事情应该用自己的方法实现不应该被别人关心。值得一提的是在结构体内部方法中如果修改了结构体的成员,那么该方法之前应该加入:mutating关键字。

由于结构体是值类型,Swift规定不能直接在结构体的方法(初始化器除外)中修改成员。原因很简单,结构体作为值的一种表现类型怎么能提供改变自己值的方法呢,但是使用mutating我们便可以办到这点,当然这也是和类的不同点。

//表示数值类型的结构体:
    Int,Float,Double,CGFloat...
//表示字符和字符串类型的结构体
    Character,String...
//位置和尺寸的结构体
    CGPoint,CGSize...
//集合类型结构体
    Array,Set,Dictionary...

很多时候你不细心观察的话可能不会想到自己信手拈来的代码中居然藏了这么多结构体。另外有时候在使用类和结构体的时候会出现下面的情况

// Person 类
class Person {
      var name: String = "jack"
      let life: Int = 1
}
      var s1 = Person()
      var s2 = s1
       s2.name = "mike"
       s1
// People 结构体数据结构
struct People {
      var name: String = "jack"
      let life: Int = 1
}
      var p1 = People()
      var p2 = p1
        p2.name = "mike"
        p1

细心的同学可能已经发现了其中的诡异。变量s1s2Person类的实例,修改了s2name属性,s1name也会改变;而p1p2作为People结构体的实例,修改了p1name属性,p2name并不会发生改变。这是为什么呢?总结中告诉你。

总结

关于枚举结构体的介绍这里仅仅是冰山一角,他们还有更加丰富的功能需要读者在阅读完本文后深入学习。了解这些基础内容,可以帮助我们在Swift开发中更熟练的使用他们。这里根据官方文档介绍结合自己的理解简单的做一下总结:

文章最后

以上就是本人前段时间学习心得,示例代码在Swift3.0语法下都是编译通过的,知识点比较少,部分描述引自官方的文档。如果文中有任何纰漏或错误欢迎在评论区留言指出,本人将在第一时间修改过来;喜欢我的文章,可以关注我以此促进交流学习; 如果觉得此文戳中了你的G点请随手点赞;转载请注明出处,谢谢支持。

下一篇:Swift中协议的简单介绍

上一篇下一篇

猜你喜欢

热点阅读