结构体和类对比

Swift 中类和结构体有很多共同点。共同处在于:
* 定义属性用于存储值
* 定义方法用于提供功能
* 定义下标操作使得可以通过下标语法来访问实例所包含的值
* 定义构造器用于生成初始化值
* 通过扩展以增加默认实现的功能
* 实现协议以提供某种标准功能

与结构体相比,类还有如下的附加功能:
* 继承允许一个类继承另一个类的特征
* 类型转换允许在运行时检查和解释一个类实例的类型
* 析构器允许一个类实例释放任何其所被分配的资源
* 引用计数允许对一个类的多次引用
注意
* 构体总是通过被复制的方式在代码中传递,不使用引用计数。

定义语法

类和结构体有着类似的定义方式。我们通过关键字class和struct来分别表示类和结构体,并在一对大括号中定义它们的具体内容:

class SomeClass {// 在这里定义类
}
struct SomeStructure {// 在这里定义结构体
}

注意
在你每次定义一个新类或者结构体的时候,实际上你是定义了一个新的 Swift 类型。因此请使用UpperCamelCase这种方式来命名(如SomeClass和SomeStructure等),以便符合标准 Swift 类型的大写命名风格(如String,Int和Bool)。相反的,请使用lowerCamelCase这种方式为属性和方法命名(如framerate和incrementCount),以便和类型名区分。

以下是定义结构体和定义类的示例:

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

类和结构体实例

Resolution结构体和VideoMode类的定义仅描述了什么是Resolution和VideoMode。它们并没有描述一个特定的分辨率(resolution)或者视频模式(video mode)。为了描述一个特定的分辨率或者视频模式,我们需要生成一个它们的实例。

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

结构体和类都使用构造器语法来生成新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如Resolution()或VideoMode()。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。构造过程章节会对类和结构体的初始化进行更详细的讨论。

属性访问

通过使用点语法,你可以访问实例的属性。其语法规则是,实例名后面紧跟属性名,两者通过点号(.)连接:

print("The width of someResolution is \(someResolution.width)")
// 打印 "The width of someResolution is 0"

在上面的例子中,someResolution.width引用someResolution的width属性,返回width的初始值0。

你也可以访问子属性,如VideoMode中Resolution属性的width属性:

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is 0"
你也可以使用点语法为变量属性赋值:someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is now
1280"

注意
与 Objective-C 语言不同的是,Swift 允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了someVideoMode中resolution属性的width这个子属性,以上操作并不需要重新为整个resolution属性设置新值。

结构体类型的成员逐一构造器

所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:

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

与结构体不同,类实例没有默认的成员逐一构造器。

结构体和枚举是值类型

值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。
在 Swift 中,所有的基本类型:整数(Integer)、浮点数(floating-point)、布尔值(Boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,并且在底层都是以结构体的形式所实现。

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

这里,将会显示cinema的width属性确已改为了2048:

print("cinema is now  \(cinema.width) pixels wide")
// 打印 "cinema is now 2048 pixels wide"
然而,初始的hd实例中width属性还是1920:print("hd is still \(hd.width) pixels wide")
// 打印 "hd is still 1920 pixels wide"

在将hd赋予给cinema的时候,实际上是将hd中所存储的值进行拷贝,然后将拷贝的数据存储到新的cinema实例中。结果就是两个完全独立的实例碰巧包含有相同的数值。由于两者相互独立,因此将cinema的width修改为2048并不会影响hd中的width的值。

类是引用类型

与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。

请看下面这个示例,其使用了之前定义的VideoMode类:

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

因为类是引用类型,所以tenEight和alsoTenEight实际上引用的是相同的VideoMode实例。换句话说,它们是同一个实例的两种叫法。

下面,通过查看tenEighty的frameRate属性,我们会发现它正确的显示了所引用的VideoMode实例的新帧率,其值为30.0:

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0"

需要注意的是tenEighty和alsoTenEighty被声明为常量而不是变量。然而你依然可以改变tenEighty.frameRate和alsoTenEighty.frameRate,因为tenEighty和alsoTenEighty这两个常量的值并未改变。它们并不“存储”这个VideoMode实例,而仅仅是对VideoMode实例的引用。所以,改变的是被引用的VideoMode的frameRate属性,而不是引用VideoMode的常量的值。

恒等运算符

因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)

如果能够判定两个常量或者变量是否引用同一个类实例将会很有帮助。为了达到这个目的,Swift 内建了两个恒等运算符:

等价于(===)
不等价于(!==)
运用这两个运算符检测两个常量或者变量是否引用同一个实例:

if tenEighty === alsoTenEighty {print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
//打印 "tenEighty and alsoTenEighty refer to the same Resolution instance."

请注意,“等价于”(用三个等号表示,===)与“等于”(用两个等号表示,==)的不同:

“等价于”表示两个类类型(class type)的常量或者变量引用同一个类实例。
“等于”表示两个实例的值“相等”或“相同”,判定时要遵照设计者定义的评判标准,因此相对于“相等”来说,这是一种更加合适的叫法。
当你在定义你的自定义类和结构体的时候,你有义务来决定判定两个实例“相等”的标准。在章节等价操作符中将会详细介绍实现自定义“等于”和“不等于”运算符的流程。

指针

如果你有 C,C++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用指针来引用内存中的地址。一个引用某个引用类型实例的 Swift 常量或者变量,与 C 语言中的指针类似,但是并不直接指向某个内存地址,也不要求你使用星号(*)来表明你在创建一个引用。Swift 中的这些引用与其它的常量或变量的定义方式相同。

类和结构体的选择

在你的代码中,你可以使用类和结构体来定义你的自定义数据类型。

然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者适用不同的任务。当你在考虑一个工程项目的数据结构和功能的时候,你需要决定每个数据结构是定义成类还是结构体。

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

  • 该数据结构的主要目的是用来封装少量相关简单数据值。
  • 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。
  • 该数据结构中储存的值类型属性,也应该被拷贝,而不是被引用。
  • 该数据结构不需要去继承另一个既有类型的属性或者行为。

注意
以上是对字符串、数组、字典的“拷贝”行为的描述。在你的代码中,拷贝行为看起来似乎总会发生。然而,Swift 在幕后只在绝对必要时才执行实际的拷贝。Swift 管理所有的值拷贝以确保性能最优化,所以你没必要去回避赋值来保证性能最优化。

swift学习笔记(9)-结构体和类相关推荐

  1. Swift学习笔记-005结构体和类(疯狂swift讲义第二版)

    1.定义类及类相关的一切 定义类的一般语法 [修饰符] class 类名{ //类的语句 } 定义结构体的一般语法 [修饰符] class 类名{ //结构体的语句 } 定义存储属性的一般语法 [修饰 ...

  2. c语言如何宏定义枚举型结构体,C语言学习笔记--枚举结构体

    枚举 枚举是一种用户定义的数据类型,它用关键字enum以如下语法格式来声明: enum 枚举类型名字 {名字0,名字1,...,名字n}: 枚举类型名字通常并不真的使用,要用的是大括号里面的名字,因为 ...

  3. Swift 中枚举、结构体、类(enum、struct、class)

    Swift 中枚举.结构体.类(enum.struct.class) Swift中的枚举与OC相比不会自动分配初始值,值的类型不会限定为整数,可以给定关联值类型和具体值(整型.浮点型.字符型(Swif ...

  4. c语言学习笔记【结构体02】结构体指针变量与结构体变量的函数参数,C语言学习笔记结构体02结构体指针变量与结构体变量的函数参数.docx...

    C 语言学习笔记[结构体02]结构体指针变量与结构体变量 的函数参数 C 语言学习笔记之结构体指针变量一提指针,那可 是 C 语言的核心了,有多少学子曾拜倒在指针的脚下.单纯的说指针,其实并不难,但是 ...

  5. C语言学习笔记(15)——结构体程序设计

    前言 C语言的基本数据类型有整数型.实数型及字符型,使用这些基本数据类型可以构造数组类型,并且可以定义相关数据类型的指针.本节介绍的结构体类型区别于以上任何数据类型,它还能把各种不同类型的数据组合成一 ...

  6. C语言学习笔记---嵌套结构体

      结构体不仅可以单独使用,也可以在结构体中嵌套另一个结构体.如下面的例子: struct Date{int year;int month;int day;};struct book{char tit ...

  7. 结构体学习笔记6——结构体嵌套

    结构体嵌套就是 在当前的结构体内的一个成员是另一个整体的结构体变量! struct Stu {char name[10];int age; }; struct Teach {char TeachNam ...

  8. C/C++ 学习笔记:结构体中最后一个成员为[0]或[1]长度数组(柔性数组成员)的用法

    结构体中最后一个成员为[0]长度数组的用法:这是个广泛使用的常见技巧,常用来构成缓冲区.比起指针,用空数组有这样的优势:(1).不需要初始化,数组名直接就是所在的偏移:(2).不占任何空间,指针需要占 ...

  9. C++学习笔记25——结构体的定义和使用,结构体数组,结构体指针

    结构体 结构体的基本概念 结构体属于用户自定义的数据类型,允许用户存储不同的数据类型 结构体的定义和使用 语法:struct 结构体名 {结构体成员列表}: 通过结构体创建变量的方式有三种: stru ...

  10. [MATLAB学习笔记]matlab结构体A.B

    在学习matlab中据见到了如下的语句: function [ seginfo ] = segment( t,acc,vel,sprate) ... seginfo.sloper=p(1); ... ...

最新文章

  1. 【转】老程序猿给新程序猿的13点建议
  2. 如何查看抓包文件所使用的捕获过滤器
  3. mysql 子查询多个字段_MySql基础
  4. android状态机实现原理
  5. 使用ultramon调整任务栏高度
  6. JavaFX UI控件教程(八)之Choice Box
  7. java开发一款模拟写字板系统
  8. c# mysql app.config_60. C# -- 读取 appconfig文件配置数据库连接的方法
  9. TensorFlow 基本问题思考
  10. 你经历过最奇特的梦境是怎样的?
  11. 面向过程和面向对象的区别,通俗易懂
  12. 网页、app、小程序图标素材PNG格式
  13. 安装微软的消息队列服务器,MSMQ消息队列的安装、启用
  14. Angular ng命令
  15. 一本通1548【例 2】A Simple Problem with Integers
  16. Verilog语言学习
  17. 上传文件的几种主要方法。
  18. libuv编译Linux,OS X下 Android NDK 编译 libuv
  19. 什么是G.654.E光纤?适合使用在什么场景?
  20. 新一代高性能USB转串口芯片CH342与CH343

热门文章

  1. 谷歌浏览器无法安装插件解决方法分享
  2. OpenCV:模板匹配
  3. 每日一题 20230411
  4. POI中导出Excel单元格样式(居中,字体,边框等)
  5. Python「pytesseract」:中文识别模块
  6. recycleView瀑布流
  7. Jad Wahab就BSV LiteClient和Teranode的相关问题的解答
  8. winform生成exe俩秒钟就退出_《一秒钟》是给电影的情书,也是给普通人的情书...
  9. 目前的智能门锁联网 ,普遍存在哪些安全隐患?
  10. Java 从零开始学爬虫(gecco)