[翻译]Swift编程语言——类和结构体

来源:互联网 发布:linux怎么拷贝文件夹 编辑:程序博客网 时间:2024/06/13 11:06

类和结构体

类和结构体是常用的、灵活的结构用来组织你的代码。你可以在你的类和结构体内定义属性、方法来增加功能,使用常量、变量和函数的语法是一样的。
不同于其他语言,Swift不要求你去创建单独的接口和实现文件来定制类或者结构体。Swift中,你在单独文件中定义一个类或者结构体,关联到这个类或者结构体的外部接口会自动生效,供其它代码使用。

NOTE
一个类的引用通常被当作一个对象(Object)。然而,同其他语言相比,Swift的类和结构体在功能方面更加密切,本章的大部分会介绍可以用在类或者结构体实例上的功能。因此,实例(instance)是个常用的术语。

比较类和结构体

Swift的类和结构体有很多共同处,它们都可以:
1:定义属性来保存数据;
2:定义方法来实现功能;
3:定义下标提供访问值
4:定义构造方法设置初始值
5:默认实现之外,可以扩展功能
6:遵循协议(protocol)来提提供标准功能
更多的信息,参见: Properties, Methods, Subscripts, Initialization, Extensions, 和Protocols.

类有的结构体没有:
1:一个类继承另一个类的特征;
2:类实例可以在运行时进行类型检查和判断;
3:类实例可以通过析构函数释放它分配到的资源;
4:引用计数允对于一个类的实例有多于一个引用。
更多信息,参见: Inheritance, Type Casting, Deinitialization, 和Automatic Reference Counting.

定义的语法

类和结构体有相似的定义语法。使用class和struct关键字分别定义类和结构体。剩下的定义内容在一对花括号内:

class​ ​SomeClass​ {​ ​// class definition goes here​}​struct​ ​SomeStructure​ {​ ​// structure definition goes here​}

NOTE
只要是你定义了一个新的类或者结构体,你都定义了一个新的Swift的类型。为了和Swift的类型名称拼写保持一致,类的名字请采用大骆驼拼写方式(UpperCamelCase )。相反,属性和方法名采用小骆驼拼写方式(lowerCamelCase ),用来和类型的名称区分开来。

这里是一个结构体和一个类的定义的例子:

struct​ ​Resolution​ {​ ​var​ ​width​ = ​0​ ​var​ ​height​ = ​0​}​class​ ​VideoMode​ {​ ​var​ ​resolution​ = ​Resolution​()​ ​var​ ​interlaced​ = ​false​ ​var​ ​frameRate​ = ​0.0​ ​var​ ​name​: ​String​?​}

上面例子定义了一个叫做Resolutino的结构体,用来描述一个像素分辨率。这个结构体保存了两个存储属性(stored property)分别叫做width和height。存储属性(stored property)是被绑定和存储到类或者结构体的常量或者变量。这两个属性被推断是Int类型的,因为它们被初始化设置为整型0。

上面的例子同样的定义了一个新的类叫做VideoMode,用来描述一个特定的显示模式。这个类有四个变量的存储属性。第一个,resolution被初始化为了一个新的Resolution结构体引用,它被推测为Resolution类型。剩下的三个属性,interlaced(是否交叉) 被设置为false,帧率被设置为0.0,一个字符串可选类型的值叫做name。name属性被自动赋予了默认值nil,因为它是一个可选类型。

类和结构体的引用

Resolution 结构体的定义和VideoMode类的定义分别描述了一个分辨率和显示模式应该的样子。他们不描述一个具体的分辨率或者显示模式。要描述具体的,需要创建结构体或者类的引用。

创建结构体和类的引用语法非常相似:

let​ ​someResolution​ = ​Resolution​()​let​ ​someVideoMode​ = ​VideoMode​()

结构体和类都使用初始化语法创建新实例。最简单的初始化语法是使用结构体或类的名字跟随一个空的圆括号,就像Resolution()或者VideMode()一样。这中方式创建了类或者结构体的引用,其中的属性用他们的默认值初始化了。类和结构体的初始化更多的描述参见:Initialization。

访问属性

你可以使用点号访问一个实例的属性。引用名后跟一个点号再加上它的属性名就可以了,其间不要有任何的空白;

​println​(​"The width of someResolution is ​\(​someResolution​.​width​)​"​)​// prints "The width of someResolution is 0"

这个例子中,someResolution.width是someResolution的width属性的引用,返回默认的初始化值0.
你可以继续访问属性的子属性,比如一个VideoMode的resolution属性的width属性:

​println​(​"The width of someVideoMode is ​\(​someVideoMode​.​resolution​.​width​)​"​)​// prints "The width of someVideoMode is 0"

你可以通过点号给变量的属性赋值:

someVideoMode​.​resolution​.​width​ = ​1280​println​(​"The width of someVideoMode is now ​\(​someVideoMode​.​resolution​.​width​)​"​)​// prints "The width of someVideoMode is now 1280"

NOTE

和OC不同,Swift允许你可以直接对子属性赋值。上面的例子中,someVideoMode的属性resolution的属性width被直接赋值了,不需要你将整个resolution设置新值。

结构体的全体成员初始化函数

所有的结构体都有一项全员初始化的技能,你可以使用它初始化一个结构体的成员属性。新引用属性的初始值可以通过 属性的名称传递给全员初始化函数:

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

不同于结构体,类就没有这项技能。初始化的更多描述参见:Initialization.

结构体和枚举是值类型(value type)的

值类型的意思是这种类型的值被赋值给变量或者常量时,被当作参数传递给函数时会复制它的值。

前面的章节广泛使用了值类型。实际上Swift中所有的基本类型——整型、浮点型、布尔类型、字符串,数组和字典都是值类型,并且在后台以结构体的形式实现。

Swift中所有的结构体和枚举都是值类型的。这意味着你创建的任何结构体和枚举的引用、他们所有用的任意类型的属性,在代码中传递时都会被复制。

看看下面的例子,使用了上面讲到的Resolution结构体:

​let​ ​hd​ = ​Resolution​(​width​: ​1920​, ​height​: ​1080​)​var​ ​cinema​ = ​hd

这个例子定义了一个常量叫做hd,然后通过构造方法(传入HD视频(1920*1080 像素)的像素宽度和高度)得到了一个Resolution实例。

接着定义了一个变量叫做cinema,然后将hd当前的值赋值给它。因为Resolution是一个结构体,所以一份当前实例的副本被构造出来,新的副本被复制给cinema。尽管hd和cinema现在有同样的宽度和高度,但他们实际上是两个完全不同的引用。

接下来,cinema的width属性被修改为稍宽的2k标准宽度,来适应数字影院的投影(2048*1080像素)

cinema.width=2048

检查一下,cinema的width属性是不是真的变成了2048:

println​(​"cinema is now ​\(​cinema​.​width​)​ pixels wide"​)​// prints "cinema is now 2048 pixels wide"

然而,hd的width属性仍然是1920:

​println​(​"hd is still ​\(​hd​.​width​)​ pixels wide"​)​// prints "hd is still 1920 pixels wide"

当cinema被hd的当前值赋值时,存储在hd中的值被复制到了新的cinema实例中。结果就是有了两个完全独立的实例,仅仅是有相同的数据。因为他们是独立的实例,所以cinema的宽度修改为2048不会影响hd。

同样的行为应用到枚举上:

enum​ ​CompassPoint​ {​ ​case​ ​North​, ​South​, ​East​, ​West​}​var​ ​currentDirection​ = ​CompassPoint​.​West​let​ ​rememberedDirection​ = ​currentDirection​currentDirection​ = .​East​if​ ​rememberedDirection​ == .​West​ {​ ​println​(​"The remembered direction is still .West"​)​}​// prints "The remembered direction is still .West"

当remberedDirection被赋值为currentDirection的值,实际也是复制了一份值的副本。currentDirection值的改变不会影响remberedDirection中存储的原始值。

类是引用类型的

和值类型不同,引用类型当他们被赋值给变量或者常量、被当作函数的参数传递时他们不会被复制。取代复制的是,一个对同一个已经存在实例的引用。

这里是一个例子,使用了前文定义的VideoMode类:

​let​ ​tenEighty​ = ​VideoMode​()​tenEighty​.​resolution​ = ​hd​tenEighty​.​interlaced​ = ​true​tenEighty​.​name​ = ​"1080i"​tenEighty​.​frameRate​ = ​25.0

这里例子定义了一个新的常量叫做tenEighty,然后将一个VideoMode类的新引用赋值给它。视频的模式被赋值为一个HD分辨率的副本(前面定义的1920*1080);interlaced设置为了true;名字被命名为“1080i”;帧率被设置为每秒25帧。
接下来,tenEighty被赋给了一个新的常量,叫做alsoTenEighty,它的帧率被修改了:

​let​ ​alsoTenEighty​ = ​tenEighty​alsoTenEighty​.​frameRate​ = ​30.0

因为类是引用类型的,tenEighty和alsoTenEighty实际上是同一个VideoMode类的引用。直接一点说,他们是一个引用,只不过有不同的名字而已。

检查一下tenEighty的frameRate属性,它以经悄然改变成了30.0:

​println​(​"The frameRate property of tenEighty is now ​\(​tenEighty​.​frameRate​)​"​)​// prints "The frameRate property of tenEighty is now 30.0"

这里tenEighty和alsoTenEighty被定义为常量而不是变量。然而你依然能够修改tenEighty.frameRate 和alsoTenEighty.frameRate,因为tenEighty和alsoEighty他们自身的值没有发生变化。它们两个自身并不“存储”VideoMode引用,而是存储了一个到VideoMode实例的引用。不是到VideoMode引用的常量被改变,而是VideoMode的frameRate被修改了。

恒等运算符(Identity Operators)

因为类是引用类型的,所以可能出现多个常量或者变量指向同一个实例的情况。(结构体和枚举就不会这样,因为他们是值类型的)

有时需要判断两个变量或常量的引用是不是一个实例,为此Swift提供了两个恒等运算符:

恒等于(===)
非恒等于(!==)

使用着俩运算符判断是不是两个常量或变量的引用指向同一个实例:

​if​ ​tenEighty​ === ​alsoTenEighty​ {​ ​println​(​"tenEighty and alsoTenEighty refer to the same VideoMode instance."​)​}​// prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."

注意,恒等于(三个等于号,===)和等于(两等于号,==)不同的:

恒等于表示两个常量或变量的引用实际上是同一个实例。
等于表示两个实例比较值的情况下是相等、相当的。“相等”的意义需要在设计时定义。

当你定义了一个类和结构体,就有必要确定两个引用相等的条件。在 Equivalence Operators一节有专门讲述如果做这个工作。

指针(pointers)

如果你有C、C++或者OC的经验,你会知道这些语言使用指针(poointer)表示内存中的地址。一个Swift中的指向引用类型实例常量或者变量类似于C语言中的指针,但是不是一个直接指向内存地址的指针,也不需要用星号(*)表明你正在创建一个引用。而是像其他类型的常量或变量一样定义。

在类和结构体之间选择

你可以使用类或者结构体定义自己的数据类型。

然而,结构体总是传值的,而类实例总是传引用的。这意味着他们有各自的分工。根据你项目中的需要考虑数据结构和功能,决定数据结构是采用类还是结构体来定义。

作为通常的原则,如果有一条或一条以上的以下情形,考虑使用结构体:

1:结构的主要目标是封装一系列有关联的简单数据值;
2:赋值或传参数的时候期望传值而不是传引用;
3:属性也是值类型的;
4:不想从已有的类型中继承属性或者行为。

举一些适合采用结构体的例子:

1:描述一个几何形状的大小,或许可以封装宽度(width)和高度(height),而且它们都是Double类型的。
2:描述在一个序列中的范围的一种方式,或许可以封装一个开始(start)属性和一个长度(length)属性,而且它们都是整型。
3:描述一个在3D坐标系中的点,或许可以封装x,y,z属性,它们每一个都是Double类型的。

所有其他的情况,定义一个类吧。通过创建那个类的实例来操作它。实际上这意味着,多数的用户定制数据结构要采用类,而不是结构体。

字符串、数组、和字典的赋值和复制行为

Swift的字符串、数组和字典类型是以结构体类型来实现的。这意味着,字符串、数组、字典在经过赋值或者传参的时候会被复制。

这个行为和Foundation中的NSString、NSArray,NSDirctionary不同,他们是以类实现的,而不是结构体。它们三个的实例在赋值传值的时候都是使用的是引用,而不是复制。

NOTE
上面提到了字符串、数组和字典的“复制”问题。你在你代码看到地方复制就会发生。实际上,Swift只在有必要的时候才做实际的复制操作。Swift掌控了所有值复制,为了确保最佳表现,你不要试图掌控这个操作。

0 0
原创粉丝点击