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

类和结构体

类和结构体是常用的、灵活的结构用来组织你的代码。你可以在你的类和结构体内定义属性、方法来增加功能,使用常量、变量和函数的语法是一样的。
不同于其他语言,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掌控了所有值复制,为了确保最佳表现,你不要试图掌控这个操作。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。