swift 学习- 10 -- 类和结构体

// '类和结构体' 是人们构建代码所使用的一种通用且灵活的构造体, 我们可以使用完全相同的语法规则来为 '类和结构体' 定义属性 (变量 和 常量) 和添加方法, 从而扩展 类和结构体 的功能

// 与其他编程语言不同的是, Swift 并不要求你为自定义 类和结构去创建独立的接口和实现文件. 你所要做的是在一个单一文件中定义一个 类或是结构体, 系统将会自动生成面向其他代码的外部接口

// 注意 : 通常一个类的实例被称为对象, 然而在 Swift 中, 类和结构体的关系要比在其他语言中要密切,

// 类和结构体 的对比

// Swift 中类和结构体有很多共同点, 共同出在于:

// 1 : 定义属性用于存储值

// 2 : 定义方法用于提供功能

// 3 : 定义下标操作使得可以通过下标语法来访问实例所包含的值

// 4 : 定义构造器用于生成初始化值

// 5 : 通过扩展以增加默认实现的功能

// 6 : 实现协议以提供某种标准功能

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

// 1 : 继承: 允许一个类继承另一个类的特征

// 2 : 类型转换: 允许在运行时检查和解释一个类实例的类型

// 3 : 析构器: 允许一个类实例释放任何其所被被分配的资源

// 4 : 引用计数: 允许对一个类的多次引用

// 注意 : 结构体总是通过被复制的方式在代码中传递, 不使用引用计数

// 定义语法

// 类和结构体 有着类似的定义方式, 我们通过 class 和 struct 来分别表示 类和结构体, 并在一堆大括号中定义他们的具体内容

class SomeClass{

// 这里定义类

}

struct SomeStructure{

// 在这里定义结构体

}

// 注意: 在你每次定义一个新类或者是结构体的时候, 实际上你是定义了一个新的 Swift 类型, 因此请使用 (如 SomeClass 和 SomeStructure 等), 以便符合标准 Swift 的大写命名风格, 相反的 , 请使用 '驼峰命名法' 为属性和方法命名, 以便和类型名区分

struct Resolution{

var width = 0

var height = 0

}

class VideoMode{

var resolution = Resolution()

var interlaced = false

var frameRate = 0.0

var name: String?

}

// 在上面的实例中我们定义了一个名为 VideoMode 的类, 用来描述一个视频显示器的特定模式, 这个类包含了四个变量存储属性, 第一个是 '分辨率' , 它被初始化为一个新的 Resolution 结构体的实例, 属性类型被推断为 Resolution , 新的 VideoMode 实例同时还会初始化其它三个属性.

// 类和结构体 实例

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

// 生成结构体和类实例的语法非常类似

let someResolution = Resolution()

let someVideoMode = VideoMode()

// 结构体和类都使用构造器来生成新的实例, 构造器语法的最简单形式是在结构体或者是类的类型名称后面跟随一堆空括号 (). 通过这种方式所创建的类或者结构体实例, 其属性均会被初始化为默认值

// 属性访问

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

print("The width of someResolution is \(someResolution.width)")

// 你也可以访问子属性

print("The width of someResolution is \(someVideoMode.resolution.width)")

// 你也可以使用点语法为变量属性赋值

someVideoMode.resolution.width = 1280

print("The width is someVideoMode is now \(someVideoMode.resolution.width)")

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

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

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

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

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

// 结构体和枚举是值类型

// 值类型 : 被赋予给一个变量,常量或者被传递给一个函数的时候, 其值会被拷贝

// 实际上, 在 Swift 中,所有的基本类型: 整数:(Integer =). 浮点数(floating-point), 布尔值 (Boolean), 字符串(String), 数组 (Array), 字典(Dictionary), 都是值类型, 并且在底层都是以结构体的形式所实现,

// 在 Swift 中, 所有的结构体和枚举类型都是值类型, 这意味着它们的实例, 以及实例中所包含的任何值类型属性, 在代码传递的过程中都会被复制

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

var cinma = hd

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

// 枚举中也遵循相同的规则

// 类是引用类型

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

// 请看下面的这个示例

let tenEighty = VideoMode()

tenEighty.resolution = hd

tenEighty.interlaced = true

tenEighty.name = "1080i"

tenEighty.frameRate = 25.0

// 以上示例中, 声明了一个名为 tenEighty 的常量, 其引用了一个 VideoMode 类的新实例,在之前的实例中, 这个视频模式 被 赋予 1920 * 1080 的一个拷贝 (即 hd 的实例), 同时设置为interlaced,命名为“1080i”。最后,其帧率是25.0帧每秒。

// 然后

let alsoTenEighty = tenEighty

alsoTenEighty.frameRate = 30.0

// 类是引用类型, 所以 tenEighty 和 alsoTenEighty 实际上引用的是相同的 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 被声明为 常量而不是 变量, 然而你依然可以改变 他们的属性值, 因为这两个常量的值并未改变, 因为他们并不存储 这个 videoMode 的实例, 而仅仅是对 VideoMode 实例的引用, 所以, 改变的是被引用的 VideoMode 的 frameRate 属性, 而不是引用 VideoMode 的常量的值

// 恒等运算符

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

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

// 1 : 等价于 (===)

// 2 : 不等价于 (!==)

// 运用这两个运算符检测两个常量或者变量是否引用同一个实例

if tenEighty === alsoTenEighty {

print("is same")

}

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

// '等价于' 表示两个类类型的 (class type) 常量或者变量引用同一个类型.

// '等于' 表示两个实例的值 '相等' 或 '相同', 因此相对于 '相等来说' ,这是一种更加合适的叫法

// 指针

// C语言, C++, OC 这些语言使用指针来引用内存中的地址, 一个引用某个引用类型实例的 Swift 常量 或 变量, 与 C语言 的指针类似, 但是并不是直接指向某个内存地址, 也不要求你使用星号 (*) 来表明你在创建一个引用, Swift 中这些引用于其它的常量 或 变量 的定义方式相同

// 类的结构体的选择

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

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

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

// 1 : 该数据结构的主要目的是用来封装少量相关简单数据值

// 2 : 有理由预计该数据结构的实例在赋值或传递是,封装的数据将会被拷贝而不是引用

// 3 : 该数据结构中存储的值类型属性, 也应该被拷贝, 而不是被引用

// 4 : 该数据结构不需要去继承另一个既有类型的属性或者行为

// 举例来说, 一下情景中适合使用结构体

// 1 : 几何形状的大小, 封装一个 width 属性和 height 属性, 两者均为 Double 类型

// 2 : 一定范围内的路径

// 3 : 三维坐标系内一点, 封装 x, y ,z 属性, 三者均为 Double 类型

// 实际中,大部分的自定义数据构造都应该是 类,而不会是 结构体

// 字符串,数组,和 字典类型的 赋值 与 复制 行为

// Swift 中. 许多基本的类型, 诸如 String ,Array, dictionary 类型均已结构体的形式实现,意味着 被赋值给新的变量或常量,他们的值会被拷贝

// OC 中 NSString, NSArray, NSDictionary 均以类的形式实现, 而不是结构体, 他们爱被赋值或者被传入函数或方法时, 不会发生值拷贝, 而是传递现有的实例引用

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