[翻译]Swift编程语言——类和结构体
类和结构体类和结构体是常用的、灵活的结构用来组织你的代码。你可以在你的类和结构体内定义属性、方法来增加功能,使用常量、变量和函数的语法是一样的。 NOTE 比较类和结构体Swift的类和结构体有很多共同处,它们都可以: 类有的结构体没有: 定义的语法类和结构体有相似的定义语法。使用class和struct关键字分别定义类和结构体。剩下的定义内容在一对花括号内: class? ?SomeClass? { ? ?// class definition goes here ?} ?struct? ?SomeStructure? { ? ?// structure definition goes here ?} NOTE 这里是一个结构体和一个类的定义的例子: 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. ?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帧。 ?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:结构的主要目标是封装一系列有关联的简单数据值; 举一些适合采用结构体的例子: 1:描述一个几何形状的大小,或许可以封装宽度(width)和高度(height),而且它们都是Double类型的。 所有其他的情况,定义一个类吧。通过创建那个类的实例来操作它。实际上这意味着,多数的用户定制数据结构要采用类,而不是结构体。 字符串、数组、和字典的赋值和复制行为Swift的字符串、数组和字典类型是以结构体类型来实现的。这意味着,字符串、数组、字典在经过赋值或者传参的时候会被复制。 这个行为和Foundation中的NSString、NSArray,NSDirctionary不同,他们是以类实现的,而不是结构体。它们三个的实例在赋值传值的时候都是使用的是引用,而不是复制。 NOTE 上面提到了字符串、数组和字典的“复制”问题。你在你代码看到地方复制就会发生。实际上,Swift只在有必要的时候才做实际的复制操作。Swift掌控了所有值复制,为了确保最佳表现,你不要试图掌控这个操作。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |