【Swift学习】Enum、Struct、Class的学习及异同

来源:互联网 发布:office for mac 激活 编辑:程序博客网 时间:2024/05/18 02:15

Swift初学者,刚过完一遍Swift Apprentice,期间配合着《Swift 3.0官方教程中文版》看,对于几个比较重要又较为复杂的知识点,通过查查资料,写写博客的方式系统化知识并加深理解。


enum、struct、class都是Swift提供的“named types”(protocol将单独介绍),它们之间有很多相同之处,又有各自的特性,存在即合理,在正确的时刻选择正确的“named types”算是初学者的必修课了吧,下面将从最基础的语法讲起,同时介绍各自的特性以及异同。




枚举(Enumerations)


定义:枚举为一组相关的值定义了一个共同的类型,使你可以以类型安全的方式来使用这些值



枚举的语法


定义一个枚举类型:
enum CompassPoint {    case north    case south    case east    case west}
用enum关键字来表示枚举,大括号中用case关键字来定义新的枚举成员值。


Swift也提供了更简洁的语言,省略多余的case,将多个成员值用逗号隔开,如下:

enum CompassPoint {    case north, south, east, west}


实例的创建及使用:

//将枚举中的某个值赋值给变量var compassPoint = CompassPoint.north//当系统已经知道变量的枚举类型时,可以省略类型名compassPoint = .south




原始值(Raw values)


枚举的成员可以被默认值(原始值)预填充,这些原始值的类型必须相同。


定义一个原始值为Int的枚举类型Month。

enum Month: Int {    case january , february, march, april, may, june, july, august, september, october, november, december}


当原始值的类型为整数时,系统将隐式地对成员值赋值且值依次递增1,若第一个成员值没有设置原始值,则默认为0。

var month = Month.januarymonth.rawValue   //0,调用rawValue显示原始值,未设置原始值默认为0//修改Month枚举如下enum Month: Int {    case january = 1 , february, march, april, may, june, july, august, september, october, november, december}month.rawValue   //此时january = 1month = .aprilmonth.rawValue // 4, 系统隐式赋值


当原始值的类型为String类型时,每个枚举成员的隐式原始值是该成员的名称,继续修改之前的CompassPoint:

enum CompassPoint: String {    case north, south, east, west}let compassPoint = CompassPoint.north.rawValue // "north"


使用原始值初始化变量或常量:

let fourMonth = Month(rawValue:4) //april, fourMonth为Month?类型,是可选的,因为//并非所有的rawValue都有对应的成员值,若匹配不到时将赋值为nil



关联值(Associated values)


枚举除了可以定义枚举成员(同类型)并为其提供原始值之外,还可以指定任意类型的关联值存储到枚举成员中。即可以在枚举中定义一组相关的枚举成员,每个枚举成员都可以有不同的类型关联值。

关联值相较原始值的一些特性:
1. 每一个枚举成员可有以一个或多个关联值
2. 每个枚举成员的关联值可以是不同的数据类型
3. 定义关联值与函数的参数类似


直接来看例子吧,以Swift官方教程为例:


一个商品可以有两种不同的条形码来跟踪商品,分别是UPC格式的一维条形码,用数字表示,另一种是DR码格式的二维码,用字符串表示,于是定义如下枚举类型:

enum Barcode {    case upc(Int, Int, Int, Int)    case qrCode(String)}
我们可以看到,Barcode枚举类型有两个成员值upc、qrCode,其中upc具有元组类型的(Int, Int, Int, Int)关联值,qrCode具有String类型关联值。


而当枚举成员有了关联值之后,就不能再有原始值了,即关联值与原始值不能同时存在。

var barcode = Barcode.upc(8,85909, 51226, 3) //创建了一个变量,并赋值为Barcode.upcbarcode = .qrCode("ABCDEFGHIJQ") //此时barcode已经是Barcode类型,因此我们可以省略//枚举类型名,barcode被赋值为一个不同类型的条形码
barcode可以存储一个.upc或者.qrCode类型的值,但是同一时间只能存储其中的一个。这也是enum与接下来要讲的struct和class的一个区别:当一个变量或常量被声明为一个枚举类型,它只能是枚举类型中的某一个成员值,而一个变量被声明为struct或class时,包含了其中所含有的所有属性(properties)和方法(methods)。


因此,什么时候应该使用enum呢,让我们重新看看枚举的定义:为一组相关的值定义了一个共同的类型,使你可以以类型安全的方式来使用这些值。


即当要使用一组相关的值的时候,如上述例子中的月份(january, february, may ...),我们就可以为这组相关的值定义一个枚举类型。




结构体(Structures) & 类(Classes)


结构体与类在语法等方面非常相似,因此这部分的介绍就整合在一起,也更加方便对比,下文会更详细地介绍它们的不同之处。



类和结构体定义语法


struct Location {    let x: Double    let y: Double    func distance() -> Double {        return sqrt((x * x) + (y * y))    }}class Person {    var firstName: String    var lastName: String    init(firstName: String, lastName: String){        self.firstName = firstName        self.lastName = lastName    }    func fullName() -> String{        return "\(firstName) \(lastName)"    }}

此处分别定义了一个Location结构体以及一个Person类,可以看到它们除了关键字的差别,其他的定义方式基本是一样的。结构体和类都可以有属性和方法。



构造器(initializers)


可以看到上文代码比较明显的差别就是:class含有一个init(_:_:)方法,而struct没有。因为所有结构体会自动生成一个成员逐一构造器,用于初始化新结构体实例中的成员属性,因此我们可以直接声明一个实例并设置其各个属性的初始值:

let location = Location(x: 2, y: 4)


而类是没有默认的成员逐一构造器的,因此,若删除class中的init(_:_:)方法,Xcode将会报错:"Class

 Person has no initailizers",即Person类中没有构造器。因此我们在创建类的时候,必须创建一个及以上的构造器。


 当然我们也可以对构造器进行重载(overload),系统将会根据参数的类型和数据自动地调用对应的构造器。当在结构体中手动添加了init(_:_:)方法后,默认的成员逐一构造器将会失效。


 以结构体Location为例,我们可以之前的代码中加入多个构造器:

 struct Location {    let x: Double    let y: Double    func distance() -> Double {        return sqrt((x * x) + (y * y))    }    //构造器1    init(x: Double, y: Double) {        self.x = x        self.y = y    }    //构造器2    init(x: Double ) {        self.x = x        self.y = 0    }    //构造器3    init() {        x = 0        y = 0    }}//创建实例并调用let location1 = Location(x: 2, y: 4)  //此时系统将会自动调用我们定义的构造器1 init(_:_:)print(location1) //Location(x: 2.0, y: 4.0)let location2 = Location(x: 2) //此时系统将调用构造器2 init(_:)print(location2) //Location(x: 2.0, y: 0.0)let location3 = Location()   //此时系统调用无参构造器3 init()print(location3) //Location(x: 0.0, y: 0.0)



访问(Accessing)

结构体实例(instance)的创建的语法已经在上文出现过了,类实例的创建也是一样:

let john = Person(firstName: "Johnny", lastName:"Appleseed")


有了实例之后我们就可以访问结构体和类中的成员属性和方法:

john.firstName   //"Johnny", 访问属性firstNamejohn.fullName()  //"Johnny Appleseed", 访问方法fullName(_:_:)



结构体与类的异同及使用场景


以上只介绍了结构体和类简单的定义及使用,类还有一个很重要的功能:继承(inheriance),但这里就不展开细讲了。接下来就对结构体和类做一个比较:



类和结构体的异同


类和结构体有很多共同点。共同处在于:

  • 定义属性用于存储值
  • 定义方法用于提供功能
  • 定义下标操作使得可以通过下标语法来访问实例所包含的值
  • 定义构造器用于生成初始化值
  • 通过扩展以增加默认实现的功能
  • 实现协议以提供某种标准功能


与结构体相比,类还有如下的附加功能:

  • 继承
  • 类型转换:运行时检查和解释一个类实例的类型
  • 析构器:释放一个实例的所有资源
  • 引用计数


类和结构体在内存中的实现机制的不同:

  • 类存储在堆(heap)中,结构体存储在栈(stack)中
  • 类是引用类型,而结构体是值类型

实现机制的不同是我们判断使用类还是结构体的一个很重要的依据,这块比较复杂,将会在下一篇博客中详细描述。



类和结构体的使用场景


按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:

  • 该数据结构的主要用来封装少量相关简单数据值。
  • 希望数据结构的实例被赋值给另一个实例时是拷贝而不是引用,封装的数据及其中存储的值也是拷贝而不是引用
  • 该数据结构不需要使用继承


举例来说,以下情境中适合使用结构体:

  • 几何形状的大小,封装一个 width 属性和 height 属性,两者均为 Double 类型。
  • 三维坐标系内一点,封装 x , y 和 z 属性,三者均为 Double 类型。
类似于上述情形,我们将使用结构体,在其他多数案例中,我们将定义类,生成它的实例,并通过引用来管理和传递。


以上,就是enum、struct、class的入门学习,有任何不对的地方欢迎大家指正。







原创粉丝点击