22Generics
模板使你能够写更富弹性,使用率更高的函数和类型。并且能以一种更清晰,抽象的方式表达你的意图。
模板是swift强大特征之一,大部分swift标准库都有模板代码。实际上你已经使用它很久了,Array和Dictionary都是模板类型。
22.1The Problem That Generics Solve
//交换两个整数的值
func swapTwoInts(inout a:Int,inout b:Int) {
let temporaryA = a
a = b
b = temporaryA
}
//交换两个字符串的值
func swapTwoStrings(inout a:String,inout b:String) {
let temporaryA = a
a = b
b = temporaryA
}
//交换两个Double数据的值
func swapTwoDoubles(inout a:Double,inout b:Double) {
let temporaryA = a
a = b
b = temporaryA
}
//我们发现这3个函数唯一的区别是数据类型,如果能够用一个函数来实现,岂不是很好,模板使你能够做到这一点。
22.2Generic Functions
//模板可以使用任意类型
func swapTwoValues<T>(inout a:T,inout b:T) {
let temporaryA = a
a = b
b = temporaryA
}
模板使用占位符类型名(这里的T),而不是实际类型名(Int,String,Double)。T的实际类型在函数被调用时决定。
<T>用来告诉swift T是个类型名占位符,这样swift就不会去寻找一个叫T的类型。
现在就可以用模板来试用下
var someInt =3
var anotherInt =107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString ="hello"
var anotherString ="world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
22.3Type Parameters
上面的T就是一个类型参数。
你可以在尖括号里指定多个类型参数,逗号隔开
22.4Naming Type Parameters
简单情况下,模板函数或模板类只有一个类型占位符,一般喜欢用单字符T表示类型参数。但是,你也可以用其他的类型参数名。
如果你定义一个复杂点的模板函数,或有多个参数的模板类型,那么最好使用描述性更好的类型参数名。比如swift中得Dictionary有两个类型参数,你可以把他们命名成KeyType和ValueType。
22.5Generic Types
不仅有模板方法,在swift中你可以定义模板类型。可以自定义类,结构,和枚举以操作任意类型,就像Array和Dictionary那样。
这一节介绍如何写Stack。栈
如果不用模板,一个整数类型的栈是这样的
struct IntStack {
var items = [Int]()
mutatingfunc push(item:Int) {
items.append(item)
}
mutatingfunc pop() ->Int {
returnitems.removeLast()
}
}
使用模板来实现
struct Stack<T> {
var items = [T]()
mutatingfunc push(item:T) {
items.append(item)
}
mutatingfunc pop() ->T {
returnitems.removeLast()
}
}
这里T不仅可以是Int,也可以是其他类型
22.6Extending a Generic Type
扩展模板类型
扩展模板类型时,并不提供类型参数列表。因为原始类型定义时已经有了。
下面对Stack模板进行扩展
extension Stack {
var topItem:T? {
returnitems.isEmpty ?nil :items[items.count -1]
}
}
22.7Type Constraints
上面swapTwoValus函数和Stack类型的类型参数可以是任意类型。但是,有时候需要对类型参数进行限制。类型限制指定类型参数必须继承于某个类,或遵循某些协议。
比如,swift中得Dictionary类就有对key类型进行限制,key必须是hashable。
22.7.1Type Constraint Syntax
func someFunction<T: SomeClass, U: SomeProtocol>(someT:T, someU:U) {
// function body goes here
}
此函数有两个类型参数,第一个类型参数T必须是SomeClass的子类。第二个参数必须实现SomeProtocol协议。
22.7.2Type Constraints in Action
//一个从数组中寻找匹配的字符串的函数
func findStringIndex(array: [String], valueToFind:String) ->Int? {
for (index, value)inenumerate(array) {
if value == valueToFind {
return index
}
}
return nil
}
let strings = ["cat","dog","llama","parakeet","terrapin"]
iflet foundIndex =findStringIndex(strings,"llama") {
println("The index of llama is\(foundIndex)")
}
// prints "The index of llama is 2"
//但是别的类型也可能需要用到类似功能,所已用模板改造下
func findIndex<T>(array: [T], valueToFind:T) ->Int? {
for (index, value)inenumerate(array) {
if value == valueToFind {//Cannot invoke '==' with an argument list of type '(T,T)'
return index
}
}
return nil
}
//但是这样写编译不了,因为并不是所有类型都可以用双等号来比较的。如果是你自己创建的类或是结构体等其他复杂类型,那swift是不知道如何用双等号来比较的。
//不过,swift标准库定义了Equatable协议,它要求任何遵循这个协议的类型都要实现(==,!=)这两个操作符。swift的标准类型也都实现了这个协议
//所以上面的模板再改造下
func findIndex<T: Equatable>(array: [T], valueToFind:T) ->Int? {
for (index, value)inenumerate(array) {
if value == valueToFind {
return index
}
}
return nil
}
let doubleIndex =findIndex([3.14159,0.1,0.25],9.3)
// doubleIndex is an optional Int with no value, because 9.3 is not in the array
let stringIndex = findIndex(["Mike","Malcolm","Andrea"],"Andrea")
// stringIndex is an optional Int containing a value of 2
22.8Associated Types
在定义协议时,声明一个或多个辅助类型有时是有必要的。辅助类型为协议提供一种类型别名。实际类型要等到协议被适配时才能定下来。辅助类型采用typealias关键字。
22.8.1Associated Types in Action
//定义一个容器协议,并声明一个辅助类ItemType
protocol Container {
typealias ItemType
mutatingfunc append(item:ItemType)
var count:Int {get }
subscript(i:Int) ->ItemType {get }
}
//协议并没有指定容器该如何保存细项,也没有指明细项的类型。
//实现这个协议的类必须指定它保存的类型。而且,必须确保只有正确的类型才能被保存,下标返回的类型必须正确。
//为了定义这些要求,协议需要一种方式在不知道实际类型的情况下来说明容器将要保存的元素类型。协议需要说明append方法的参数类型,必须和容器的元素类型一致,和下标的返回值类型一致。
//为了做到这些,容器协议定义了一个辅助类型ItemType。协议并没有定义ItemType代表什么类型,这个信息留给实现类来说明。因此,ItemType别名提供一种方式来说明容器内包含的类型,append函数的参数类型,和下标的返回类型。以此来说明容器所需要的要求。
//一个非模板方式的整形栈,实现Container协议
struct IntStack:Container {
// original IntStack implementation
var items = [Int]()
mutatingfunc push(item:Int) {
items.append(item)
}
mutatingfunc pop() ->Int {
returnitems.removeLast()
}
// conformance to the Container protocol
typealias ItemType =Int
mutatingfunc append(item:Int) {
self.push(item)
}
var count:Int {
returnitems.count
}
subscript(i:Int) ->Int {
returnitems[i]
}
}
//IntStack指明了这里的ItemType是Int类型。
//因为swift可以进行类型推算,所以实际上你并不需要写typealias ItemType = Int
//也可以用模板栈类型来实现容器协议
struct Stack<T>:Container {
// original Stack<T> implementation
var items = [T]()
mutatingfunc push(item:T) {
items.append(item)
}
mutatingfunc pop() ->T {
returnitems.removeLast()
}
// conformance to the Container protocol
mutatingfunc append(item:T) {
self.push(item)
}
var count:Int {
returnitems.count
}
subscript(i:Int) ->T {
returnitems[i]
}
}
//这时,占位类型参数T用来作为append方法的参数类型,下标的返回值也是T。swift就可推算出这里的T就是要取代ItemType的实际的类型
22.8.2Extending an Existing Type to Specify an Associated Type
你可以扩展一个类型以实现某个协议,即使这个协议有辅助类型。
Array类型已经提供了容器协议所需要的方法属性下标,所以你只需要简单声明一下就可以使Array适配容器协议。
extension Array:Container {}
22.9Where Clauses
之前我们介绍了,类型限制是你能够为模板函数或模板类型的类型参数定义一些限制。
有时给辅助类定义一些限制也是有必要的。你可以定义where 语句作为类型参数的一部分。where语句是你能够要求辅助类型必须适配某个协议,也可以指定类型参数和辅助类型必须一样。
//下面例子定义一个模板函数来检查两个容器包含的内容是否相同
//这两个容器不必是同一类型,但他们包含的内容的类型必须是同一类型,这一要求就是通过合成类型和where语句来表达的。
func allItemsMatch<
C1: Container, C2: Container
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
(someContainer:C1, anotherContainer:C2) ->Bool {
// check that both containers contain the same number of items
if someContainer.count != anotherContainer.count {
returnfalse
}
// check each pair of items to see if they are equivalent
for iin0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
returnfalse
}
}
// all items match, so return true
return true
}
/*
上面的类型参数列表表达了如下要求
1)C1必须实现Container协议(C1: Container)
2)C2也要实现Container协议(C2: Container)
3)C1的ItemType和C2的ItemType相同(C1.ItemType == C2.ItemType)
4)C1的ItemType必须实现Equatable协议(C1.ItemType: Equatable)
这样的要求也就意味着
1)someContainer是C1类型的容器
2)anotherContainer是C2类型的容器
3)someContainer和anotherContainer包含着相同类型的item
4) someContainer的item项可以拿来作对比
这也意味着anotherContainer的item项也是可对比的。
*/
var stackOfStrings =Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno","dos","tres"]
if allItemsMatch(stackOfStrings,arrayOfStrings) {
println("All items match.")
}else {
println("Not all items match.")
}
// prints "All items match."
23Access Control
访问控制限制了其他代码对你代码的访问。这是你能够隐藏你代码的实现细节,而通过接口来被访问。
除了属性,方法,构造器和下标,你也可以给类型(类,结构体,枚举)设置访问级别。协议可以限制在某个特定的范围,比如全局常量,变量和函数。
除了提供不同访问级别,swift为了减少显示的标注访问级别,为不同情况提供了默认的访问级别。实际上如果你写的是单target app,你可能不需要标注访问级别。
23.1Modules and Source Files
swift的访问控制是基于模块和源文件的概念的。
一个模块是一个发布单元—一个框架(framework)或应用(application),他们都是一个编译和封装好的单元,可以被其他模块导入。
每一个编译好的target(比如app bundle 或framework)都是一个独立的模块。
23.2Access Levels
swift提供3个不同的访问级别。这些访问级别是与源文件和所在模块相关的。
1)public 级别,可被同一模块中得其它文件访问,也可被import这个模块的其他模块访问。一般用于定义framework的公有接口。
2)Internal 级别,可被同一模块中得其它文件访问,但不能被其他模块访问。一般用于定义应用或framework的内部结构。
3)private 级别,只能被所定义的文件访问。用于隐藏函数的实现细节。
public是最高(最少限制)的访问级别,private是最低(最多限制)的访问级别。
23.2.1Guiding Principle of Access Levels
访问级别的总体原则:No entity can be defined in terms of another entity that has a lower (more restrictive) access level.
某一级别的访问入口不能组合更低级别的访问入口。
比如:
1)公有变量不能被定义成内部的或私有的类型。因为类型的有效范围比变量的有效范围小。
2)函数的访问级别不能比参数类型和返回值类型高。因为这样会导致函数被访问时,其参数或返回值在上下文中却是没定义的。
23.2.2Default Access Levels
所有的代码(除少数例外,后面会讲)如果没显示的标明级别,都有默认的访问级别internal。所以,很多情况下,你无须显示的标注访问级别。
23.2.3Access Levels for Single-Target Apps
当你写一个单target应用时,应用所需的代码一般都包含在应用里面,所以也无需对外提供应用模块的功能。默认的访问级别也就完全符合要求。因此你也不需要指定访问级别。然而,你也可能想私有化部分代码以隐藏他们。
23.2.4Access Levels for Frameworks
当开发frmework时,把对外接口标注成public,以便被其他模块访问。这个对外的接口就是framework的api.
注意:任何内部实现细节仍然可以是默认的内部访问级别,甚至是private级别。只有你想把它作为api时才需要把它标注成public。
23.3Access Control Syntax
定义时把访问级别修饰符写在定义前面。
publicclass SomePublicClass {}
internalclass SomeInternalClass {}
privateclass SomePrivateClass {}
publicvar somePublicVariable =0
internallet someInternalConstant =0
privatefunc somePrivateFunction() {}
前面说了,除非显示标注,不然就是internal。所以SomeInternalClass和someInternalConstant可以不必写访问级别。
class SomeInternalClass {} // implicitly internal
var someInternalConstant =0 // implicitly internal
23.4Custom Types
如果你想给自定义类型显示标注访问级别,那么在定义的时候标注。那么新类型就可在它有效的范围内使用。比如,你定义一个私有类,那么这个类就只能在所定义的源文件中作为属性的类型,或函数的参数或返回值使用。
类型的访问级别同时影响着成员(属性,方法,构造器,下标)的访问级别。如果你把类型定义成私有的,那么它的成员也都是私有的。如果你把类型定义成内部或公有的,那么成员的访问级别就是内部的。
注意:公有类型的成员默认是内部的,不是公有的。如果你想让他变成公有的,你必须显示标注成公有的。这么做是为了确保类型对外的api确实是你要发布的。避免不小心把internal部分发布出去。
publicclass SomePublicClass { // explicitly public class
publicvar somePublicProperty =0 // explicitly public class member
var someInternalProperty =0 // implicitly internal class member
privatefunc somePrivateMethod() {} // explicitly private class member
}
privateclass SomePrivateClass { // explicitly private class
var somePrivateProperty =0 // implicitly private class member
func somePrivateMethod() {} // implicitly private class member
}
23.4.1Tuple Types
tuple类型的访问级别是其包含类型中限制最多的那个。比如:如果有两种类型,一个是internal,另一个是private,那么他们组合起来的tuple将是private。
注意:tuple并不像类,结构体,枚举,函数那样,有独立的定义。tuple的访问级别是在使用时自动推算出来的。没法显示标注。
23.4.2Function Types
函数类型的访问级别是从参数类型和返回值类型中计算出限制最多的那个访问级别。如果计算出来的访问级别不匹配默认的,那么你要显式标注访问级别。
下面这个例子定义全局函数someFunction,没有显示标注访问级别。你可能以为访问级别就是默认的Internal。
func someFunction() -> (SomeInternalClass,SomePrivateClass) {
// function implementation goes here
}
//Function must be declared private because its result uses a private type
这个函数不能标注成public或internal,或使用默认internal设置,因为返回值里面有私有类型。
23.4.3Enumeration Types
每个case的访问级别自动就是所属枚举类型的访问级别。没方法对单独的case另外设置访问级别。
下面的例子,CompassPoint的访问级别显式为public,则他的每个case North,South,East,West就都也是public.
publicenum CompassPoint {
case North
case South
case East
case West
}
23.4.3.1Raw Values and Associated Values
raw或associated值必须不低于枚举的访问级别。比如,你不能枚举类型是internal,而raw却是private。
23.4.4Nested Types
private类型内部定义的内嵌类型自动是private的。
public和internal类型内部定义的内嵌类型自动是internal.
如果你想在public类型内部定义public内嵌类型,你必须在内嵌类型定义时显式注明public。
23.5Subclassing
只要类能被访问到,你就可以进行子类化。子类不能比父类有更高的访问级别。比如,父类是internal,那么子类不能是public。
另外,特定情况下,可以覆写类任何成员(方法,属性,构造器,下标)
覆写类成员可以有更高的访问级别。
下例,类的访问级别降低了,但成员方法的访问级别升高了。
public class A {
privatefunc someMethod() {}
}
internal class B: A {
overrideinternalfunc someMethod() {}
}
因为A和B定义在同一个源文件中,所以B可以调用super.someMethod()。
23.6Constants, Variables, Properties, and Subscripts
常量,变量,或属性不能比他们的类型更public。不能属性是public,属性类型是private。同理,下标不能比index type或return type更public。
如果常量,变量,属性或下标使用private类型,那个他们也必须是private。
privatevar privateInstance =SomePrivateClass()
23.6.1Getters and Setters
常量,变量,属性和下标的getter和setter方法自动跟其对应的常量,变量,属性和下标具有相同的访问级别。
你可以给setter设置更低的访问级别,以便限制读写范围。
注意:这个规则不仅适用于计算属性,还适用于存储属性。虽然你并没有给存储属性写getter,setter方法,swift也是有给你隐式生成getter和setter方法。使用private(set)和internal(set)来改变其访问级别。
下面结构体例子用来记录字符串被修改的次数。
struct TrackedString {
private(set)var numberOfEdits =0
var value:String ="" {
didSet {
numberOfEdits++
}
}
}
var stringToEdit =TrackedString()
stringToEdit.value ="This string will be tracked."
stringToEdit.value +=" This edit will increment numberOfEdits."
stringToEdit.value +=" So will this one."
println("The number of edits is\(stringToEdit.numberOfEdits)")
// prints "The number of edits is 3"
请注意,如果需要的话,你可以显示的给getter和setter都标注访问级别。
下面的结构体是pubic,成员也标注成public,nunerofEdits属性读是public,写是private。
publicstruct TrackedString {
publicprivate(set)var numberOfEdits =0
publicvar value:String ="" {
didSet {
numberOfEdits++
}
}
publicinit() {}
}
23.7Initializers
自定义构造器的访问级别可以小于等于他们创建的类型。一个例外是required构造器。required构造器的访问级别必须与对应类的访问级别相同。
与函数和方法的参数类似,构造器的参数类型不能比构造器的访问级别更低。
23.7.1Default Initializers
如果结构体和基类的属性都有默认值,且没有显示的构造器,那么swift会提供默认的不带参数的构造器。这在Default Initializers中讲过。这个默认构造器的访问级别与它所要构造的类型的访问级别相同。
注意:如果类型的访问级别是public,默认构造器将是internal。如果你想让一个public类型在其他的模块中能被不带参的初始化,你必须提供public不带参的构造器。
23.7.2Default Memberwise Initializers for Structure Types
如果结构体的存储属性有private的,那么它的Memberwise Initializer将是private。否则,是internal。
如果你希望一个public结构体的构造器能在其它模块中被初始化,你必须提供public memberwise initializer。
23.8Protocols
可以在定义协议时显示的标明协议的访问级别。
协议中的要求(方法)的访问级别自动就是协议的访问级别。你不能把要求(方法)的访问级别设置成别的级别。这确保了所有的要求(方法)在适配时与协议有相同的访问级别。
注意:如果你把协议定义成public,那么协议的要求(requirement)也需要是public的。这与其它的类型不同,当其它类型为public时,它的成员一般为internal。
23.8.1Protocol Inheritance
如果你定义一个新协议继承于一个旧协议,那么新协议的访问级别不能高于就协议。比如,你不能定义一个public协议继承于一个internal协议。
23.8.2Protocol Conformance
一个类型可以适配一个访问级别比自己低的协议。比如,一个public类型可以适配internal协议。
假如一个类型适配了某个协议,那么这个适配的影响范围将是类型和协议的访问级别的最小值。即:假如类型是public,协议是internal,那么类型对协议的适配也将是internal。(鄙人注:意思是在internal内,人家知道你这个类型适配了那个协议,在internal外,人家不知道那个协议,也就无法知道你这个类型适配了那个协议)
当你写或者扩展一个类型来适配一个协议,你必须保证类型对协议的各个要求(方法)的实现至少跟类型对协议的适配有相同的访问级别。比如,public类型适配internal协议,那么类型对协议的要求的实现也必须至少是“internal”。
注意:在swift中,与Objective-C一样,协议适配是全局的——一个类型不可能用两种方式来适配同一个协议。
23.9Extensions
可以扩展class,structure,enumeration。在扩展中加入的成员与原始类型中声明的成员具有相同的访问级别。比如,扩展一个public类型,新加入的类型成员具有默认的访问级别internal。
当然了,你也可以标注扩展的访问级别(比如:private extension)来设置扩展中成员的默认访问级别。甚至独立标注各个成员的访问级别。
23.9.1Adding Protocol Conformance with an Extension
如果你是用扩展来增加对协议的适配,那么你将不能在扩展时显示指定访问级别。因为,协议的访问级别会被用来给每个协议要求(方法)实现提供默认的访问级别。the protocol’s own access level is used to provide the default access level for each protocol requirement implementation within the extension.
23.10Generics
模板类型或模板函数的访问级别是模板类型或函数自己的访问级别和他们的参数的访问级别的最小者。
23.11Type Aliases
你定义的类型别名在访问级别上可被当做一个独立的类型。
类型别名的访问级别可以比原类型的访问级别低,最多相等。
比如,一个peivate类型别名可以别一个private,internal,或public类型的名,但是一个public类型别名不能别internal或private类型的名。
注意:在辅助类型上这个规则同样适用。
24Advanced Operators
除了基础操作外,swift提供了许多高级复杂的操作,包括按位操作和位移操作。
与C中的算数操作不同,swift中的算数操作默认不会溢出。溢出操作会被挑出并报告错误。如果要选择溢出操作,使用swift的辅助算术操作集(second set of arithmetic operators that overflow by default),他们是允许溢出的。比如溢出加(&+)。
当你定义自己的结构体,类,枚举时,为他们提供标准swift操作的实现或许是有用的。在swift中,为这些操作定制实现变得非常简单。
在swift中,你不仅可以用前面定义的操作符,你还可以定义你自己的前缀,后缀,中缀和赋值操作符,定义他们的优先级和结合性。这些操作符可以和其它预定义操作符一样在你的代码中使用。你甚至可以扩展已有类型来支持自定义操作符。
24.1Bitwise Operators
按位操作使你操作数据结构中得元数据位。这经常在低层编程中使用,比如图片编程和设备驱动创建。按位操作在你需要操作外部资源时也是很有用的,比如编码和解码通讯中得数据。
24.1.1Bitwise NOT Operator
按位not操作反转了数字中得所有位
let initialBits:UInt8 =0b00001111 //15
let invertedBits = ~initialBits // equals 11110000 240
24.1.2Bitwise AND Operator
按位and操作
如果两个数的这一比特位都是1,那么结果才是1,否则就是0
let firstSixBits:UInt8 =0b11111100
let lastSixBits:UInt8 =0b00111111
let middleFourBits =firstSixBits &lastSixBits // equals 00111100
24.1.3Bitwise OR Operator
按位or操作
如果两个数的这一比特位有一个是1,那么结果就是1,否则是0
let someBits:UInt8 =0b10110010
let moreBits:UInt8 =0b01011110
let combinedbits =someBits |moreBits // equals 11111110
24.1.4Bitwise XOR Operator
按位xor操作
如果两个数的这一比特位值相同则是0,不同则是1
let firstBits:UInt8 =0b00010100
let otherBits:UInt8 =0b00000101
let outputBits =firstBits ^otherBits // equals 00010001
24.1.5Bitwise Left and Right Shift Operators
按位左移和按位右移将移动一个数的所有比特位。
按位左移和右移有乘2或除2的效果。左移一位相当于乘以2,右移一位相当于除以2。
24.1.5.1Shifting Behavior for Unsigned Integers
无符号整形的位移规则:
1)已有的比特位左移或右移所需的比特位。
2)丢弃任何超出存储范围的比特位。
3)移动过程中所空出来的比特位用0补足。
这种操作称为逻辑移位。
let shiftBits: UInt8 = 4 // 00000100 in binary
shiftBits <<1 // 00001000
shiftBits <<2 // 00010000
shiftBits <<5 // 10000000
shiftBits <<6 // 00000000
shiftBits >>2 // 00000001
let pink:UInt32 =0xCC6699
let redComponent = (pink &0xFF0000) >>16 // redComponent is 0xCC, or 204
let greenComponent = (pink &0x00FF00) >>8 // greenComponent is 0x66, or 102
let blueComponent =pink &0x0000FF // blueComponent is 0x99, or 153
24.1.5.2Shifting Behavior for Signed Integers
有符号整形的位移操作会比无符号更麻烦,这是由有符号整形的二进制的表示方式导致的。
有符号整形使用第一比特位(符号位)来表示这个数是正数还是负数,符号位1表示负数。
其他比特位(数值位)存储实际的值。正数的存储方式与无符号整数相同,从0开始。
然而负数的存储是不一样的。它们值的存储从第二位到第n位(n为这个数的比特位数)。
负数的编码我们称为两部分法。别以为这很不正常,这很有好处。
第一,比如-1加-4,你可以直接用标准二进制加法来对他们进行操作(包括符号位),并丢弃超出部分。如下图:
第二,两部分法让你能够像整数那样左移右移,并且左移时变2倍,右移时变一半。只不过,在右移时这里需要增加一条规则:
在右移有符号整形时,应用与无符号相同的规则,但在填充空比特位时使用符号位填充。
这样确保了位移后与位移前有相同的符号,这称为算术移位。
因为正数和负数存储的特殊方式,右移将使他们趋于0。
移动过程中保持符号位意味着负数在趋0过程中将一直保持负数。
24.2Overflow Operators
如果给一个整形常量或变量增加一个值,那它可能会溢出,默认地swift会报告错误而不允许你这么做。这样做是为了在你操作太大数据或太小数据时多一份安全。
比如,Int16的值范围是-32768到32767。试图给Int16常量或变量赋值超过这个范围的数将导致错误:
var potentialOverflow =Int16.max
// potentialOverflow equals 32767, which is the largest value an Int16 can hold
potentialOverflow +=1
// this causes an error Arithmetic operation '32767 + 1' (on type 'Int16') results in an overflow
处理边界值时提供错误处理,并提供了更多的操作选择。
如果你知道会溢出,并就是要这么做时,你可以使用swift提供的5个算术溢出操作符。
Overflow addition(&+)
Overflow subtraction (&-)
Overflow multiplication (&*)
Overflow division (&/)
Overflow remainder (&%)
24.2.1Value Overflow
无符号整形值可溢出情况下的操作结果。
var willOverflow =UInt8.max
// willOverflow equals 255, which is the largest value a UInt8 can hold
willOverflow = willOverflow &+1
// willOverflow is now equal to 0
24.2.2Value Underflow
值也可能太小而溢出
最小的Uint8是0。如果你使用可以出减操作对它减1,这值将溢出到最大值255
var willUnderflow =UInt8.min
// willUnderflow equals 0, which is the smallest value a UInt8 can hold
willUnderflow = willUnderflow &-1
// willUnderflow is now equal to 255
有符号整形也是类似的。所有有符号整形减也是二进制直接减,其符号位被作为值的一部分直接减。
最小的Int8是-128,即二进制的10000000,减1之后变成二进制的01111111,变成127。
var signedUnderflow =Int8.min
// signedUnderflow equals -128, which is the smallest value an Int8 can hold
signedUnderflow = signedUnderflow &-1
// signedUnderflow is now equal to 127
24.2.3Division by Zero
除以0,或对0取摸将导致错误:
let x =1
let y =x /0
但如果用可溢出除,将得到0
let x =1
let y =x &/0
// y is equal to 0
let z =7 &%0 //z is 0
24.3Precedence and Associativity
优先级和结合性
24.4Operator Functions
类和结构体可以提供已有操作符的实现。这就是所谓的已有操作符重载。
下面例子显示如何实现自定义结构体算术加运算符。算术加运算符是二元操作符,操作两个对象并且是中缀运算符。
例子定义了一个2维向量Vector2D结构体,并定义了一个操作符函数来对Vector2D结构体进行相加。
struct Vector2D {
var x =0.0, y =0.0
}
func + (left:Vector2D, right:Vector2D) ->Vector2D {
returnVector2D(x: left.x + right.x, y: left.y + right.y)
}
操作符函数被定义成一个全局的函数,名字更被重载的操作符一样(+)。因为算术加操作符是二元操作符,这个操作符函数也带两个入参,类型是Vector2D,返回一个Vector2D。
在这里,入参名为left和right,他们将分别在+操作符的左右两边。
函数被定义成全局的,而不是Vector2D结构体的方法,所以它可以作为Vector实例的中缀。
let vector =Vector2D(x:3.0, y:1.0)
let anotherVector =Vector2D(x:2.0, y:4.0)
let combinedVector =vector+anotherVector
// combinedVector is a Vector2D instance with values of (5.0, 5.0)
24.4.1Prefix and Postfix Operators
上面的例子展示了二元中缀操作符的自定义实现。
类和结构体也可以有标准一元操作符的实现。
一元操作符操作一个单一对象。有前缀和后缀。
在func关键字前写prefix或postfix修饰符来实现一个前缀或后缀一元操作符。
prefixfunc - (vector:Vector2D) ->Vector2D {
returnVector2D(x: -vector.x, y: -vector.y)
}
这个例子实现了Vector2D的一元负操作符。一元负操作符是前缀操作符,所以这个函数需要prefix修饰。
对于简单的数值,一元负操作符把正值转换成负值,把负值转成正值。Vector2D则是把这样的操作应用在x和y属性上:
let positive =Vector2D(x:3.0, y:4.0)
let negative =-positive
// negative is a Vector2D instance with values of (-3.0, -4.0)
let alsoPositive =-negative
// alsoPositive is a Vector2D instance with values of (3.0, 4.0)
24.4.2Compound Assignment Operators
组合运算符组合了赋值符(=)和一个操作符。比如,加赋值把加和赋值组合成一个操作符。
你需要把组合赋值操作符中得左入参标记为inout,因为这个值在函数中将被修改。
func += (inout left:Vector2D, right:Vector2D) {
left = left+ right
}
因为加操作符在前面已经实现过,这边直接用即可。
var original =Vector2D(x:1.0, y:2.0)
let vectorToAdd =Vector2D(x:3.0, y:4.0)
original +=vectorToAdd
// original now has values of (4.0, 6.0)
你可以组合前缀操作符或后缀操作符,下面实现前缀自增操作符:
prefixfunc ++ (inout vector:Vector2D) -> Vector2D {
vector+=Vector2D(x:1.0, y:1.0)
return vector
}
var toIncrement =Vector2D(x:3.0, y:4.0)
let afterIncrement =++toIncrement
// toIncrement now has values of (4.0, 5.0)
// afterIncrement also has values of (4.0, 5.0)
注意:无法重载默认的赋值操作符(=)。只有组合赋值操作父可以被重载。同理,三元条件操作符(a ? b : c)也不能被重载。
24.4.3Equivalence Operators
相等操作符
自定义类和机构体并不默认实现相等操作符,即(==)和(!=)。swift是没法知道你自定义的类什么时候被认为是相等的。
如果想用相等操作符来检查你自定义的类型是不是等价,你需要更其他中缀操作符一样提供相等操作符的实现
func == (left:Vector2D, right:Vector2D) ->Bool {
return (left.x == right.x) && (left.y == right.y)
}
func != (left:Vector2D, right:Vector2D) ->Bool {
return !(left== right)
}
let twoThree =Vector2D(x:2.0, y:3.0)
let anotherTwoThree =Vector2D(x:2.0, y:3.0)
if twoThree==anotherTwoThree {
println("These two vectors are equivalent.")
}
// prints "These two vectors are equivalent."
24.5Custom Operators
除了标准的操作符外,你还可以自定义你自己的操作符。可以用来自定义的字符列表见Operators章节。(笔记里面没有)
新的操作符需要被定义成全局,并使用operator关键字,需用prefix,infIx或postfix修饰:
prefix operator +++ {}
这个例子定义一个新的前缀运算符,因为不是标准操作符,所以,他将被赋予一个新的意思。在这里把它定义成成倍自加运算符。
prefixfunc +++ (inout vector:Vector2D) -> Vector2D {
vector+= vector
return vector
}
var toBeDoubled =Vector2D(x:1.0, y:4.0)
let afterDoubling =+++toBeDoubled
// toBeDoubled now has values of (2.0, 8.0)
// afterDoubling also has values of (2.0, 8.0)
24.5.1Precedence and Associativity for Custom Infix Operators
自定义中缀操作符也可以指定优先级和结合性。参见precedence and Associativity。
The possible values for associativity are left, right, and none. Left-associative operators associate to the left if written next to other left-associative operators of the same precedence. Similarly, right-associative operators associate to the right if written next to other right-associative operators of the same precedence. Non-associative operators cannot be written next to other operators with the same precedence.
(不知所云)
结合律默认为none。优先级默认100
下面定义一个新的中缀运算符+-,左结合,优先级140:
infixoperator +- { associativity left precedence140 }
func +- (left:Vector2D, right:Vector2D) ->Vector2D {
returnVector2D(x: left.x + right.x, y: left.y - right.y)
}
let firstVector =Vector2D(x:1.0, y:2.0)
let secondVector =Vector2D(x:3.0, y:4.0)
let plusMinusVector =firstVector+-secondVector
// plusMinusVector is a Vector2D instance with values of (4.0, -2.0)
注意:在定义前缀或后缀操作符时不能标注优先级。但是,如果你给一个操作数同时标上前缀和后缀,那么后缀操作符先运算。