Swift 类和结构体

embedded/2024/10/20 5:23:59/

类和结构体

  • 一、结构体和类对比
    • 1、类型定义的语法
    • 2、结构体和类的实例
    • 3、属性访问
    • 4、结构体类型的成员逐一构造器
  • 二、结构体和枚举是值类型
  • 三、类是引用类型
    • 1、恒等运算符
    • 2、指针

结构体和类作为一种通用而又灵活的结构,成为了人们构建代码的基础。你可以使用定义常量、变量和函数的语法,为你的结构体和类定义属性、添加方法。

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

注意

通常一个类的实例被称为对象。然而相比其他语言,Swift 中结构体和类的功能更加相近,本章中所讨论的大部分功能都可以用在结构体或者类上。因此,这里会使用实例这个更通用的术语。

一、结构体和类对比

Swift 中结构体和类有很多共同点。两者都可以:

  • 定义属性用于存储值
  • 定义方法用于提供功能
  • 定义下标操作用于通过下标语法访问它们的值
  • 定义构造器用于设置初始值
  • 通过扩展以增加默认实现之外的功能
  • 遵循协议以提供某种标准功能

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

  • 继承允许一个类继承另一个类的特征
  • 类型转换允许在运行时检查和解释一个类实例的类型
  • 析构器允许一个类实例释放任何其所被分配的资源
  • 引用计数允许对一个类的多次引用

类支持的附加功能是以增加复杂性为代价的。作为一般准则,优先使用结构体,因为他们更容易理解,仅在适当或必要时才使用类。实际上,这意味着你的大多数自定义数据类型都会是结构体和枚举。

1、类型定义的语法

结构体和类有着相似的定义方式。你通过 struct 关键字引入结构体,通过 class 关键字引入类,并将它们的具体定义放在一对大括号中:

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

注意
每当你定义一个新的结构体或者类时,你都是定义了一个新的 Swift 类型。请使用 UpperCamelCase这种方式来命名类型(如这里的 SomeClassSomeStructure ),以便符合标准 Swift 类型的大写命名风格(如 StringIntBool)。请使用 lowerCamelCase 这种方式来命名属性和方法(如 frameRateincrementCount),以便和类型名区分。

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

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

在上面的示例中定义了一个名为 Resolution 的结构体,用来描述基于像素的分辨率。这个结构体包含了名为 widthheight 的两个存储属性。存储属性是与结构体或者类绑定的,并存储在其中的常量或变量。当这两个属性被初始化为整数 0 的时候,它们会被推断为 Int 类型。

在上面的示例还定义了一个名为 VideoMode 的类,用来描述视频显示器的某个特定视频模式。这个类包含了四个可变的存储属性。第一个, resolution,被初始化为一个新的 Resolution 结构体的实例,属性类型被推断为 Resolution。新 VideoMode 实例同时还会初始化其它三个属性,它们分别是初始值为 falseinterlaced(意为“非隔行视频”),初始值为 0.0frameRate,以及值为可选 Stringname。因为 name 是一个可选类型,它会被自动赋予一个默认值 nil,意为“没有 name 值”。

2、结构体和类的实例

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

创建结构体和类实例的语法非常相似:

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

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

3、属性访问

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

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

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

你也可以访问子属性,如 VideoModeresolution 属性的 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"

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

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

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

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

二、结构体和枚举是值类型

值类型是这样一种类型,当它被赋值给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。

在之前的章节中,你已经大量使用了值类型。实际上,Swift 中所有的基本类型:整数(integer)、浮点数(floating-point number)、布尔值(boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,其底层也是使用结构体实现的。

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

注意

标准库定义的集合,例如数组,字典和字符串,都对复制进行了优化以降低性能成本。新集合不会立即复制,而是跟原集合共享同一份内存,共享同样的元素。在集合的某个副本要被修改前,才会复制它的元素。而你在代码中看起来就像是立即发生了复制。

请看下面这个示例,其使用了上一个示例中的 Resolution 结构体:

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

在以上示例中,声明了一个名为 hd 的常量,其值为一个初始化为全高清视频分辨率(1920 像素宽,1080 像素高)的 Resolution 实例。

然后示例中又声明了一个名为 cinema 的变量,并将 hd 赋值给它。因为 Resolution 是一个结构体,所以会先创建一个现有实例的副本,然后将副本赋值给 cinema 。尽管 hdcinema 有着相同的宽(width)和高(height),但是在幕后它们是两个完全不同的实例。

下面,为了符合数码影院放映的需求(2048 像素宽,1080 像素高),cinemawidth 属性被修改为稍微宽一点的 2K 标准:

cinema.width = 2048

查看 cinemawidth 属性,它的值确实改为了 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 实例中。结果就是两个完全独立的实例包含了相同的数值。由于两者相互独立,因此将 cinemawidth 修改为 2048 并不会影响 hd 中的 width 的值,如下图所示:
在这里插入图片描述
枚举也遵循相同的行为准则:

enum CompassPoint {case north, south, east, westmutating func turnNorth() {self = .north}
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()
print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// 打印 "The current direction is north"
// 打印 "The remembered direction is west"

rememberedDirection 被赋予了 currentDirection 的值,实际上它被赋予的是值的一个拷贝。赋值过程结束后再修改 currentDirection 的值并不影响 rememberedDirection 所储存的原始值的拷贝。

三、类是引用类型

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

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

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

以上示例中,声明了一个名为 tenEighty 的常量,并让其引用一个 VideoMode 类的新实例。它的视频模式(video mode)被赋值为之前创建的 HD 分辨率(1920*1080)的一个拷贝。然后将它设置为隔行视频,名字设为 “1080i”,并将帧率设置为 25.0 帧每秒。

接下来,将 tenEighty 赋值给一个名为 alsoTenEighty 的新常量,并修改 alsoTenEighty 的帧率:

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

因为类是引用类型,所以 tenEightalsoTenEight 实际上引用的是同一个 VideoMode 实例。换句话说,它们是同一个实例的两种叫法,如下图所示:
在这里插入图片描述
通过查看 tenEightyframeRate 属性,可以看到它正确地显示了底层的 VideoMode 实例的新帧率 30.0

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

这个例子也显示了为何引用类型更加难以理解。如果 tenEightyalsoTenEighty 在你代码中的位置相距很远,那么就很难找到所有修改视频模式的地方。无论在哪使用 tenEighty,你都要考虑使用 alsoTenEighty 的代码,反之亦然。相反,值类型就更容易理解了,因为你的源码中与同一个值交互的代码都很近。

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

1、恒等运算符

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

判定两个常量或者变量是否引用同一个类实例有时很有用。为了达到这个目的,Swift 提供了两个恒等运算符:

  • 相同(===
  • 不相同(!==

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

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

请注意,“相同”(用三个等号表示,===)与“等于”(用两个等号表示,==)的不同。“相同”表示两个类类型(class type)的常量或者变量引用同一个类实例。“等于”表示两个实例的值“相等”或“等价”,判定时要遵照设计者定义的评判标准。

当在定义你的自定义结构体和类的时候,你有义务来决定判定两个实例“相等”的标准

2、指针

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


http://www.ppmy.cn/embedded/43487.html

相关文章

go语言基准测试Benchmark 最佳实践-冒泡排序和快速排序算法基准测试时间复杂度对比

在go语言中Benchmark基准测试( 在后缀为_test.go的文件中,函数原型为 func BenchmarkXxx(b *testing.B) {}的函数 )可以用来帮助我们发现代码的性能和瓶颈, 其最佳实践 应该是我们最常用的 冒泡排序和快速排序的测试了,废话不说,直…

【HarmonyOS】View点击穿透,层叠View点击事件控制

【HarmonyOS】View点击穿透,层叠View点击事件控制 问题背景: 在HarmonyOS中,经常会有层叠的View的布局,当碰到需要穿透的布局需求。就需要能控制View对点击事件的处理。 方案一,使用touchable: 目前虽然函…

用大模型搭建一个自己的新闻小助手

背景 信息快速增长的时代,及时获取到有价值的资讯是一件很必要的事情。已经有各类新闻app和获取信息的渠道了,为什么还需要在构建一个小助手来获取新闻资讯呢?其实原因很简单各类新闻app服务的是具体一类人群,个人和人群还是有偏…

某勾求职网逆向分析

搜索目标: aHR0cHM6Ly93d3cubGFnb3UuY29tL3duL2pvYnM/cG49MSZweD1kZWZhdWx0JmZyb21TZWFyY2g9dHJ1ZSZrZD0lRTYlOTUlQjAlRTYlOEQlQUUlRTUlODglODYlRTYlOUUlOTA= 抓包分析 请求和返回都是加密的 请求头部也有未知参数 跟栈分析 请求和返回是一个AES加密,加密的KEY是session s…

三前奏:获取/ 读取/ 评估数据【数据分析】

各位大佬好 ,这里是阿川的博客 , 祝您变得更强 个人主页:在线OJ的阿川 大佬的支持和鼓励,将是我成长路上最大的动力 阿川水平有限,如有错误,欢迎大佬指正 前面的博客 数据分析—技术栈和开发环境搭建 …

列表元素添加的艺术:从单一到批量

新书上架~👇全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一、引言 二、向列表中添加单一元素 1. append方法 2. insert方法 三、向列表中添加批量…

React hooks - useRef

useRef 用法特点注意事项 用法 useRef 函数返回一个可变的 ref 对象,该对象只有一个 current 属性。可以在调用 useRef 函数时为其指定初始值。并且这个返回的 ref 对象在组件的整个生命周期内保持不变。 // 1. 导入 useRef import { useRef } from react // 2. 调…

ubuntu 配置用户登录失败尝试次数限制

前言: 通过修改pam配置来达到限制密码尝试次数! 1:修改 /etc/pam.d/login 配置(这里只是终端登录配置,如果还需要配置SSH远程登录限制,只配置下面的 /etc/pam.d/pam.d/common-auth 即可) vim…