本次的教程是基于Swift5.1版本
类和结构体是一种多功能且灵活的构造体。通过使用与现存常量、变量、函数完全相同的语法来在类和结构体当中定义属性和方法以添加功能。
不像其他的程序语言,Swift不需要你为自定义类和结构体创建独立的接口和实现文件。在 Swift 中,你在一个文件中定义一个类或者结构体, 则系统将会自动生成面向其他代码的外部接口。
定义语法
类与结构体有着相似的定义语法,你可以通过使用关键词 class 来定义类使用 struct 来定义结构体。并在一对大括号内定义它们的具体内容。
代码语言:javascript复制class SomeClass {
// class definition goes here
}
struct SomeStructure {
// structure definition goes here
}
无论你在何时定义了一个新的类或者结构体,实际上你定义了一个全新的 Swift 类型。请用 UpperCamelCase 命名法命名 (比如这里我们说到的 SomeClass和 SomeStructure)以符合 Swift 的字母大写风格(比如说 String , Int 以及 Bool)。相反,对于属性和方法使用 lowerCamelCase命名法 (比如 frameRate 和 incrementCount),以此来区别于类型名称。
这里有个类定义和结构体定义的例子:
代码语言:javascript复制struct Name{
var firstName = "lee"
var secondName = "dp"
}
class My{
var name = Name()
var height = 185
var weight = 130
var job:String?
}
类与结构体实例
创建结构体和类的实例的语法是非常相似的:
代码语言:javascript复制let name = Name()
let my = My()
结构体和类两者都能使用初始化器语法来生成新的实例。初始化器语法最简单的是在类或结构体名字后面接一个空的圆括号,例如 Name()或者 My()。这样就创建了一个新的类或者结构体的实例,任何属性都被初始化为它们的默认值。
访问属性
你可以用点语法来访问一个实例的属性。在点语法中,你只需在实例名后面书写属性名,用( .)来分开,无需空格:
代码语言:javascript复制print(name.firstName)
//输出:lee
你亦可以用点语法来指定一个新值到一个变量属性中:
代码语言:javascript复制my.name.firstName = "super"
print(my.name.firstName)
//输出:super
不同于 Objective-C,Swift 允许你直接设置一个结构体属性中的子属性。在上述最后一个栗子中, My的 name属性中的 firstName这个属性可以直接设置,不用你重新设置整个 name 属性到一个新值。
结构体类型的成员初始化器
所有的结构体都有一个自动生成的成员初始化器,你可以使用它来初始化新结构体实例的成员属性。新实例属性的初始化值可以通过属性名称传递到成员初始化器中:
代码语言:javascript复制let name = Name(firstName: "super", secondName: "man")
与结构体不同,类实例不会接收默认的成员初始化器。
结构体和枚举是值类型
值类型是一种当它被指定到常量或者变量,或者被传递给函数时会被拷贝的类型。
其实,在之前的章节中我们已经大量使用了值类型。实际上,Swift 中所有的基本类型——整数,浮点数,布尔量,字符串,数组和字典——都是值类型,并且都以结构体的形式在后台实现。
Swift 中所有的结构体和枚举都是值类型,这意味着你所创建的任何结构体和枚举实例——和实例作为属性所包含的任意值类型——在代码传递中总是被拷贝的。
代码语言:javascript复制struct MyPoint{
var x = 0
var y = 0
}
let point_one = MyPoint(x: 1, y: 2)
var point_two = point_one
point_two.x = 111
print("point_one= (point_one)")
print("point_two= (point_two)")
//point_one= MyPoint(x: 1, y: 2)
//point_two= MyPoint(x: 111, y: 2)
从输出的结果可以看出,改变了point_two的值对应的point_one的值并没有发生改变。这种行为规则同样适用于枚举。
类是引用类型
不同于值类型,在引用类型被赋值到一个常量,变量或者本身被传递到一个函数的时候它是不会被拷贝的。相对于拷贝,这里使用的是同一个对现存实例的引用:
代码语言:javascript复制class MyClass{
var x = 0
var y = 0
}
let myClass_one = MyClass()
let myClass_two = myClass_one
myClass_two.x = 111
print(myClass_one.x)
print(myClass_two.x)
//都输出:111
注意 myClass_one和 myClass_two都被声明为常量。然而,你仍然能改变 myClass_one.x 和myClass_two.x。因为myClass_one和 myClass_two常量本身的值不会改变。myClass_one和 myClass_two本身是并没有存储 MyClass实例。相反,它们两者都在后台指向了 MyClass实例。这是 MyClass的 x参数在改变而不是引用 MyClass的常量的值在改变。
特征运算符
因为类是引用类型,在后台有可能有很多常量和变量都是引用到了同一个类的实例。(相同这词对结构体和枚举来说并不是真的相同,因为它们在赋予给常量,变量或者被传递给一个函数时总是被拷贝过去的。)
有时候找出两个常量或者变量是否引用自同一个类实例非常有用,为了允许这样,Swift提供了两个特点运算符:
- 相同于 ( ===)
- 不相同于( !==)
利用这两个恒等运算符来检查两个常量或者变量是否引用相同的实例:
代码语言:javascript复制if myClass_one === myClass_two {
print("相同实例")
}
//输出:相同实例
注意”相同于”(用三个等于号表示,或者说 ===)这与”等于”的意义不同(用两个等于号表示,或者说 ==)。
- 相同于”意味着两个类类型常量或者变量引用自相同的实例;
- “等于”意味着两个实例的在值上被视作“相等”或者“等价”,某种意义上的“相等”,就如同类设计者定义的那样。
指针
如果你有过 C,C 或者 Objective-C 的经验,你可能知道这些语言使用可指针来引用内存中的地址。一个 Swift 的常量或者变量指向某个实例的引用类型和 C 中的指针类似,但是这并不是直接指向内存地址的指针,也不需要你书写星号( *)来明确你建立了一个引用。相反,这些引用被定义得就像 Swift 中其他常量或者变量一样。
字符串,数组和字典的赋值与拷贝行为
Swift 的 String , Array 和 Dictionary类型是作为结构体来实现的,这意味着字符串,数组和字典在它们被赋值到一个新的常量或者变量,亦或者它们本身被传递到一个函数或方法中的时候,其实是传递了拷贝。
这种行为不同于基础库中的 NSString, NSArray和 NSDictionary,它们是作为类来实现的,而不是结构体。NSString , NSArray 和 NSDictionary实例总是作为一个已存在实例的引用而不是拷贝来赋值和传递。
类和结构体之间的选择
在 Swift 中类和结构体有很多共同之处,它们都能:
- 定义属性用来存储值;
- 定义方法用于提供功能;
- 定义下标脚本用来允许使用下标语法访问值;
- 定义初始化器用于初始化状态;
- 可以被扩展来默认所没有的功能;
- 遵循协议来针对特定类型提供标准功能。
类有而结构体没有的额外功能:
- 继承允许一个类继承另一个类的特征;
- 类型转换允许你在运行检查和解释一个类实例的类型;
- 反初始化器允许一个类实例释放任何其所被分配的资源;
- 引用计数允许不止一个对类实例的引用。
结构体在你的代码中通过复制来传递,并且并不会使用引用计数。
事实上,大部分的自定义的数据结构应该是类,而不是结构体。