您的位置:首页 > 移动开发 > Swift

Swift-下标脚本和继承(Subscripts and Inheritance)(十一)

2016-05-16 00:21 393 查看

前言

其实下标脚本和继承没有一毛钱的关系,唯有的几分关系就是都是Swift的知识点,并且他们在教材中的章节挨着,并且对他们的讲解都很少,并且我懒得分开写,得出结论,一锅炖了吧,加量不加价。

下标脚本

下标脚本语法

下标脚本用法

下标脚本选项

下标脚本可以定义在类、结构体和枚举这些目标中,而数组、字典等这些底层是使用结构体实现的,所以也是可以用下标脚本的。其实我们最常用的下表脚本就是根据索引值获取数组的第几个元素或者获取字符串的第几个字符。当然,下标脚本详细的知识点会稍微复杂点。

下标脚本语法

下标脚本允许通过在实例后面的方括号中传入一个或者多个索引值来对实例进行访问和赋值。这个“索引值”不仅仅可以是数字,也可以是字符串等,如
dictionary[key]
。定义下标脚本使用
subscript
关键字,显示声明传入参数(即下表语法需要的值类型,一个或者多个)和返回类型。与实例方法不同的是下标脚本可以设定为只读或者读写。这种方式有点像计算类型的
getter
setter
方法:

subscript(index: Int) -> Int {
get {
//  返回和传入参数匹配的Int类型的值
}
set {
//  执行赋值操作
//  使用默认的newValues访问新赋的值
}
}


与只读计算类型一样,当下表脚本方法只读时,可以讲
getter
中的代码直接写在
subscript
中:

//  只读脚本方法
subscript(index: Int) -> Int {
//  返回和传入参数匹配的Int类型的值
}


下面写两个实例代码,一个是单纯的整数型的下标语法,一种是超过一个的传入参数的,具体看注释:

//  情况一,下标语法中传入一个整数,返回这个整数的下标的倍数
struct MultipleNumber {
let original: Int
subscript(index: Int) -> Int {
return original * index
}
}
//  测试
let simpleTest = MultipleNumber(original: 20)
simpleTest[4]       //  80
//  情况二,输入一个人的编号、姓名,查看个人信息
struct PersonList {
let age: Int
let address: String
subscript(number: Int, name: String) -> (personAge: Int, personAddr: String) {
return (age + 2, "中国北京 - " + address)
}
}
//  测试
let complexTest = PersonList(age: 20, address: "朝鲜思密达")
complexTest[14, "张三"]       //  (.0 22, .1 "中国北京 - 朝鲜思密达")


下标脚本用法

其实上面的例子已经展示了下标脚本的基本用法,只不过仅仅是只读下标脚本的用法。以我们常用的数组、字典为例:

//  数组、字典展示下标脚本的用法
var subscrpitArray = ["哈哈","呵呵","哦哦","嘿嘿","啦啦"]
//  下标脚本  读取
subscrpitArray[3]      //  "嘿嘿"
//  修改
subscrpitArray[3] = "thanks"      //  "thanks"
//  字典
var subscriptDictionary = ["张三": 12, "李四": 22, "王二": 24, "马六": 9]
subscriptDictionary["王二"] = 99    //  正常查找
subscriptDictionary["老王"] = 34    //  如果找不到 “老王”,则添加
subscriptDictionary["addg"]        //  找不到匹配的值,返回nil
subscriptDictionary["张三"] = nil   //  不想要某个值了,置为nil即可
subscriptDictionary    //  ["王二": 99, "老王": 34, "李四": 22, "马六": 9]]


主要注意字典实例后面的注释,这点在以后开发中偶尔会很有帮助。

下标脚本选项

下标脚本允许任意数量的传入参数索引,并且每个参数的类型没有限制,下标脚本的返回值也可以是任何类型。如上面的示例代码中,我传入整型的编号和字符串类型的姓名作为传入参数,返回一个包含年龄和地址的元组。但是校表脚本的参数不能使用写入读出
in-out
参数或者给参数设置默认值,这些用法会直接导致编译错误,所以也不能太担心写错,只要出错的时候知道为什么错了就行。

一个类或者结构体可以根据自身需要提供多个下标脚本实现,在定义时通过不同的传入参数区分,使用下标脚本时会自动匹配合适的下标脚本运行,这就是下标脚本的重载。

教材中举了个例子,创建一个矩阵并赋值。其实本来没有多么重要的只是,不过鉴于用到了断言,已经方法之间的配合使用,还是有些参考价值的,我就恬不知耻的接着用了,,,比着教材稍微好点的是我写了相当足量的注释…

//  创建一个矩阵并赋值
struct Matrix {
//  定义行数和列数
let rows: Int, columns: Int
//  定义一个数字数组,装着矩阵中每一个位置对应的数
var grid: [Double]
//  初始化
init(row: Int, column: Int) {
self.columns = column
self.rows = row
//  数组创建并初始化,包含 rows * columns 个 0.0 的数组
grid = Array(count: rows * columns, repeatedValue: 0.0)
}
//  方法,传入使用的行列数判断是否有越界情况
func indexIsViableForRow(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
//  下标语法
subscript(row: Int, col: Int) -> Double {
get {
//  设置断言
assert(indexIsViableForRow(row, column: col), "矩阵数组取出时越界")
//  条件为Yes时,不执行断言
return grid[row * col + col]
}
set {
//  setter方法设置值
//  设置断言,如果设置新值的下标越界时
assert(indexIsViableForRow(row, column: col), "矩阵数组赋值时越界")
}
}
}
//  使用,创建一个三行四列的矩阵
var matrix = Matrix(row: 3, column: 4)
//  正常赋值
matrix[2, 3] = 54
//  越界赋值,此时会执行断言,取值时一样
matrix[-10, 0] = 44
matrix[100, 100] = 44


继承

定义一个基类(Base class)

子类生成(Subclassing)

重写(Overriding)

防止重写

一个类可以继承另一个类的方法、属性和其他特性。此时,继承类叫子类(
subclass
),被继承类叫超类(或 父类
superclass
)。在 Swift中,继承是区分类与其他类型的一个基本特征。

在Swift中,子类可以调用、修改父类的属性、方法、下标脚本等。Swift会重写定义在超类中是否有匹配的定义,以确保重写行为是正确的。

可以为类中继承来的任何属性添加属性观察器,不论是存储类型属性还是计算类型属性。这样一来,当属性改变时,就会通知到类。

定义一个基类

不继承于其他类的类,称之为基类。在Swift中,类并不是从一个通用的基类继承而来,
Objective-C
中类都是从通用基类
NSObject
继承而来,而Swift中,如果不为定义的类指定一个超类的话,这个类自己就会成为基类。

下面定义一个车辆的基类,包括速度属性、文字描述、车辆行为三部分:

//  定义一个车辆基类
class Vehicle {
var currentSpeed = 0.0         //  速度
var description: String {      //  描述
return "车辆目前速度 :\(currentSpeed) 公里每小时"
}
//  制造噪音方法
func makeNoise() {
print("一辆安安静静的车")
}
}
//  再次提醒,类是引用类型,结构体和枚举是值类型,创建实例
let vehicle = Vehicle()
vehicle.description      //  "车辆目前速度 :0.0 公里每小时"


子类生成

子类生成指的是在一个已有类的基础上创建一个新的类。子类继承超类的所有特性,并且可以优化或者改变它。还可以为子类添加新的特性。

为了指明某个类的超类,将超类的名字写到子类名的后面,用分号隔开,如此简单:

//  生成子类
class SomeSubclass: SomeSuperclass {
}


书接上文,定义一个自行车类继承上面的车辆基类,并添加新的属性,譬如是否有个篮子,也可以调用父类的属性和方法,同时,这个类也可以作为其他类的基类,就这么用啊用啊用下去~:

//  继承与基类的子类
class Bicycle: Vehicle {
//  添加新的属性
var hasBasket = false
}
let bicycle = Bicycle()
//  访问父类的属性、方法
bicycle.currentSpeed = 30
bicycle.description     //  "车辆目前速度 :30.0 公里每小时"
//  同时,Bicycle 本身也能作为基类
class DoubleBicycle: Bicycle {
//  继承父类所有的属性、方法等特性
}


重写(Overriding)

子类可以为继承来的实例方法,类方法,实例属性,或下标脚本提供自己定制的实现。我们把这种行为叫做 重写

如果要重写某个特性,在其前面加上关键字
override
即可,表明是想提供一个重写版本,而非错误的提供一个相同的定义。以外的重写会导致不可预知的错误,任何缺少关键字
override
的重写都会在编译的时候诊断为错误。

override
这个关键字会提醒Swift编译器去检查该类的超类(其中一个超类)是否有匹配重写版本的声明。这个检查可以确保我们的重写定义是正确的,因为如果匹配不到会编译错误的。

在子类中访问、重写超类的特性时,优势在重写的版本中使用已经存在的超类实现回答有裨益。在合适的地方,可以通过使用
super
前缀来访问超类版本的特性:

在方法
someMethod
的重写实现中,可以通过
super.someMethod()
来调用超类版本的
someMethod
方法。

在属性
someProperty
getter
setter
的重写实现中,可以通过
super.someProperty
来访问超类版本的这个属性。

在下标脚本的重写实现中,可以通过
super[someIndex]
来访问超类版本中的相同的下标脚本。

在子类中,可以重写继承来的方法,提供一个定制或者替代的方法实现:

//  重写方法
class Train: Vehicle {
override func makeNoise() {
print("hoyihoyi")
}
}
let train = Train()
train.makeNoise()     //  输出 hoyihoyi


关于属性继承,稍微麻烦一点,因为牵扯到
getter
setter
方法。有两点需要注意,一,重写一个属性时,必须将他的名字和类型都写出来;二,子类是对父类的扩展,所以可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供
getter
setter
即可,但是不能将一个继承来的读写属性重写成一个只读属性。

如下面的实例代码,将只读属性
currentSpeed
重写为读写属性,从写
description
方法:

//  重写属性
class Car: Vehicle {
var speed: Double = 0.0
//  重写只读属性 currentSpeed 为可读写属性
override var currentSpeed: Double {
get {
return speed
}
set {
speed = 2 * newValue
}
}
override var description: String {
return "car speed: \(speed) miles per hour"
}
}


可以在属性重写中为一个继承来的属性添加属性观察器,关于属性观察器前面说过了,一般使用
didSet
,当属性发生变化时,就会通知到类。但是继承自超类的常量储存类型或继承来的只读计算属性是不能添加属性观察器的,因为它们已经不可能被改变。如
currentSpeed
是只读属性,但是是只读存储属性,所以是能添加属性观察器的。还有,属性观察器和
setter
方法是冲突的,它们有着相同的作用,
setter
方法中可以观察到属性值的变化。

//  添加属性观察器
class Car: Vehicle {
var speed: Double = 0.0
//  重写只读属性 currentSpeed 为可读写属性
override var currentSpeed: Double {
didSet {
speed = 2 * currentSpeed
}
}
override var description: String {
return "car speed: \(speed) miles per hour"
}
}
let car = Car()
car.currentSpeed = 100
car.speed      //  200


防止重写

可以通过把方法、属性、下标脚本等特性标记为
final
来防止它们被重写。重写被
final
标记的特性时会编译错误。也可以在
class
前添加关键字
fianl
,这样的话这个类就是不能被继承的,任何试图继承该类的行为都会编译错误。

//  定义一个不可被继承的车辆基类
final class Vehicle {
//  此时,特性前面的 final 显得多余了,因为整个类都是不能被继承的,所以下面所有的特性都不可能被重写。没有 class 前的 final 时,下面的 final 才会起作用。
final var currentSpeed = 0.0         //  速度
final var description: String {      //  描述
return "车辆目前速度 :\(currentSpeed) 公里每小时"
}
//  制造噪音
func makeNoise() {
print("一辆安安静静的车")
}
}


结束语

周末万岁~~~~然而现在已经星期一了。。。不开心

今天看了王牌特工,其实是一部非常不错的特工电影,以前一直直观上认为不好看,估计是那海报没get到我的点。其实真的去看了,nice。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: