十二 下标
1、下标语法
下标可以让你通过实例名后加中括号内一个或多个数值的形式检索一个元素。语法和方法语法和属性语法类似,通过使用subscript关键定义,一个或多个输入参数以及一个返回值。不同于实例方法的是,下标可以是可读写的或者只读的。这种行为通过一个getter和setter语句联通,就像是计算属性一样。
subscript(index: Int) -> Int { get { return an appropriate subscript value here } set(newValue) { perform a suitable setting action here } }
newValue的类型和下标返回的类型一样。和计算属性一样,你可以选择不指定setter的参数,因为当你不指定的时候,默认参数newValue会被提供给setter。
和计算属性一样,只读下标可以不需要get关键词
下面是一个只读下标的实现,定义了一个TimesTable结构来表示一个整数的倍数表:
在这个例子中,实例TimesTable被创建为3倍数表,这是通过在初始化的时候为multiplier参数传入的数值3设置的。
注意:
倍数表是根据特定的数学规则设置的,所以不应该为threeTimeTable[someIndex]元素设置一个新值,所以TimesTable的下标定义为只读。
struct TimesTable { let multiplier: Int subscript(index: Int) -> Int { return multiplier * index } } let threeTimesTable = TimesTable(multiplier: 3) print("six times three is (threeTimesTable[6])")
2、下标的使用
下标的具体含义由使用它时的上下文来确定。下标主要用来作为集合,列表和序列的元素快捷方式。你可以自由的为你的类或者结构定义你所需要的下标。
比如说,Swift中字典类型实现的下标是设置和检索字典实例中的值。可以通过分别给出下标中的关键词和值来设置多个值,也可以通过下标来设置单个字典的值:
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] numberOfLegs["bird"] = 2
上面的例子中定义了一个变量numberOfLegs,然后通过键值对初始化。numberOfLegs的类型是字典类型Dictionary<String, Int>。在字典创建之后,例子使用了下标赋值方法添加了一个类型为字符串的键”bird”和Int值2到字典中。
注意:
Swift中字典类型实现的键值对下标是可选类型。对于numberOfLges字典来说,返回的值是Int?,也就是可选Int值。字典的这种使用可选类型下标的方式说明不是所有的键都有对应的值。同样也可以通过给键赋值nil来删除这个键。
3、下标选项
下标可以接收任意数量的参数,参数的类型也可以各异。下标还可以返回任何类型的值。下标可以使用变量参数或者可变参数,但是不能够使用输入输出参数或者提供默认参数的值。
类或者结构可以根据需要实现各种下标方式,可以在需要的时候使用合适的下标通过中括号中的参数返回需要的值。这种多下标的定义被称作下标重载。
当然,最常见的下标用法是单个参数,也可以定义多个参数的下标。下面的例子演示了一个矩阵Matrix结构,它含有二维的Double值。矩阵结构的下标包括两个整形参数:
struct Matrix { let rows: Int, columns: Int var grid: [Double] init(rows: Int, columns: Int) { self.rows = rows self.columns = columns grid = Array(count: rows * columns, repeatedValue: 0.0) } func indexIsValidForRow(row: Int, column: Int) -> Bool { return row >= 0 && row < rows && column >= 0 && column < columns } subscript(row: Int, column: Int) -> Double { get { assert(indexIsValidForRow(row, column: column), "Index out of range") return grid[(row * columns) + column] } set { assert(indexIsValidForRow(row, column: column), "Index out of range") grid[(row * columns) + column] = newValue } } }
矩阵Matrix提供了一个初始化方法,使用两个参数rows和columns,然后建立了一个数组来存储类型为Double的值rows*columns。每个矩阵中的位置都被设置了一个初始值0.0。通过传递初始值0.0和数组长度给数组初始化方法完成上述操作
你可以传递两个参数row和column来完成Matrix的初始化:
var matrix = Matrix(rows: 2, columns: 2)
矩阵中的值可以通过使用包含row和column以及逗号的下标来设置:
matrix[0, 1] = 1.5 matrix[1, 0] = 3.2
矩阵下标的getter和setter方法都包括了一个断言语句来检查下标row和column是否有效。通过indexIsValid方法来判断row和column是否在矩阵的范围内:
如果访问的矩阵越界的时候,断言就会被触发:
let someValue = matrix[2, 2]
控制台打印:assertion failed: Index out of range: file <EXPR>, line 61
十三 继承
1、定义一个基类
任何一个不继承于其它类的类被称作基类
注意:Swift的类不是从一个全局基类继承而来。在你编写代码的时,只要是在类的定义中没有继承自父类的类都是基类。
下面的例子定义了一个叫Vehicle的基类。基类包含两个所有交通工具通用的属性numberOfWheels和maxPassengers。这两个属性被一个叫description的方法使用,通过返回一个String描述来作为这个交通工具的特征:
class Vehicle { var numberOfWheels: Int var maxPassengers: Int func description() -> String { return "(numberOfWheels) wheels; up to (maxPassengers) passengers" } init() { numberOfWheels = 0 maxPassengers = 1 } }
这个交通工具类Vehicle还定义了一个构造函数来设置它的属性。
通过构造函数可以创建一个类型的实例。尽管构造函数不是方法,但是它们在编码的时候使用了非常相似的语法。构造函数通过确保所有实例的属性都是有效的来创建一个新的实例。
使用构造函数语法TypeName和空的两个小括号来完成一个Vehicle实例的创建:
let someVehicle = Vehicle()
Vehicle的构造函数为属性设置了一些初始值(numberOfWheels = 0 然后 maxPassengers = 1)。
Vehicle类定义的是一个通用的交通工具特性,它本身没有太多意义,所以就需要冲定义它的一些属性或者方法来让它具有实际的意义。
2、产生子类
产生子类就是根据一个已有的类产生新类的过程。子类继承了父类的一些可以修改的特性。还可以为子类添加一些新的特性。
为了表明一个类是继承自一个父类,需要将父类的名称写在子类的后面,并且用冒号分隔
定义一个新的类叫Bicycle,它继承了Vehicle的特性:
class Bicycle: Vehicle { override init() { super.init() numberOfWheels = 2 } }
不仅属性是继承于Vehicle的,Bicycle还继承了父类的方法。如果你创建一个实例,然后调用了已经继承的description方法,可以得到该交通工具的描述并且看到它的属性已经被修改:
let bicycle = Bicycle() print("Bicycle: (bicycle.description())")
注意:子类只能够在构造的时候修改变量的属性,不能修改常量的属性。
3、重写方法
子类可以提供由父类继承来的实例方法,类方法,实例属性或者下标的个性化实现。这个特性被称为重写。
重写一个由继承而来的方法需要在方法定义前标注override关键词。通过这样的操作可以确保你所要修改的这个方法确实是继承而来的,而不会出现重写错误。错误的重写会造成一些不可预知的错误,所以如果如果不标记override关键词的话,就会被在代码编译时报错。
override关键词还能够让Swift编译器检查该类的父类是否有相符的方法,以确保你的重写是可用的,正确的。
访问父类方法,属性和下标
当在重写子类继承自父类的方法,属性或者下标的时候,需要用到一部分父类已有的实现。比如你可以重定义已知的一个实现或者在继承的变量中存储一个修改的值。
适当的时候,可以通过使用super前缀来访问父类的方法,属性或者下标
复写方法
你可以在你的子类中实现定制的继承于父类的实例方法或者类方法。
下面的例子演示的就是一个叫Car的Vehicle子类,重写了继承自Vehicle的description方法。
class Car: Vehicle { var speed: Double = 0.0 override init() { super.init() maxPassengers = 5 numberOfWheels = 4 } override func description() -> String { return super.description() + "; " + "traveling at (speed) mph" } }
Car中定义了一个新的Double类型的存储属性speed。这个属性默认值是0.0,意思是每小时0英里。Car还有一个自定义的构造函数,设置了最大乘客数为5,轮子数量是4.
Car重写了继承的description方法,并在方法名description前标注了override关键词。
在description中并没有给出了一个全新的描述实现,还是通过super.description使用了Vehicle提供的部分描述语句,然后加上了自己定义的一些属性,如当前速度。
let car = Car() print("Car: (car.description())")
复写属性
你还可以提供继承自父类的实例属性或者类属性的个性化getter和setter方法,或者是添加属性观察者来实现重写的属性可以观察到继承属性的变动。
重写属性的Getters和Setters
不管在源类中继承的这个属性是存储属性还是计算属性,你都可以提供一个定制的getter或者setter方法来重写这个继承属性。子类一般不会知道这个继承的属性本来是存储属性还是计算属性,但是它知道这个属性有特定的名字和类型。在重写的时候需要指明属性的类型和名字,好让编译器可以检查你的重写是否与父类的属性相符。
你可以将一个只读的属性通过getter和setter继承为可读写的,但是反之不可。
注意:如果你为一个重写属性提供了setter方法,那么也需要提供getter方法。如果你不想在getter中修改继承的属性的值,可以在getter中使用super.someProperty即可,在下面SpeedLimitedCar例子中也是这样。
下面的例子定义了一个新类SpeedLimitedCar,是Car的一个子类。这个类表示一个显示在40码一下的车辆。通过重写继承的speed属性来实现:
class SpeedLimitedCar: Car { override var speed: Double { get { return super.speed } set { super.speed = min(newValue, 40.0) } } }
重写属性观察者
你可以使用属性重写为继承的属性添加观察者。这种做法可以让你无论这个属性之前是如何实现的,在继承的这个属性变化的时候都能得到提醒。
注意:不能为继承的常量存储属性或者是只读计算属性添加观察者。这些属性值是不能被修改的,因此不适合在重写实现时添加willSet或者didSet方法。
注意:不能同时定义重写setter和重写属性观察者,如果想要观察属性值的变化,并且又为该属性给出了定制的setter,那只需要在setter中直接获得属性值的变化就行了。
下面的代码演示的是一个新类AutomaticCar,也是Car的一个子类。这个类表明一个拥有自动变速箱的汽车,可以根据现在的速度自动选择档位,并在description中输出当前档位:
class AutomaticCar: Car { var gear = 1 override var speed: Double { didSet { gear = Int(speed / 10.0) + 1 } } override func description() -> String { return super.description() + " in gear (gear)" } }
这样就可以实现,每次你设置speed的值的时候,didSet方法都会被调用,来看档位是否需要变化。gear是由speed除以10加1计算得来,所以当速度为35的时候,gear档位为4:
let car1:AutomaticCar = AutomaticCar() car1.speed = 35 car1.description()
4、禁止重写
你可以通过标记final关键词来禁止重写一个类的方法,属性或者下标。在定义的关键词前面标注final属性即可。
在子类中任何尝试重写父类的final方法,属性或者下标的行为都会在编译时报错。同样在扩展中为类添加的方法,属性或者下标也可以被标记为final。
还可以在类关键词class前使用final标记一整个类为final(final class)。任何子类尝试继承这个父类时都会在编译时报错。