前言
- Swift 全面支持 Unicode 符号。
- Swift 中的定义和实现是在同一个单元中的,通常一个 Swift 源代码单文件是以 “.Swift” 结尾的。
- Swift 不需要单独编写一个 main 函数作为入口,在 Swift 语言中函数是一等成员,编译器会自动将遇到的第一个函数作为入口。
- Swift 允许我们不用在行尾加分号 “;”。但如果在同一行有两个甚至多个表达式,需要在每个表达式后面加上分号。
- Playground 是一种编写代码时可以即时预览代码运行效果的功能。使用 Playground 后,在实际项目中可以为我们节省不少功能调试和函数测试时间,这些时间完全可以从事其他创造性的活动。
- Swift 常用标注:
// MARK:
添加注释说明,加 “-” 添加分割横线// FIXME:
表示此处有 bug 或者要优化// TODO:
一般用于写到哪了做个标记,然后回来继续
1、Swift 基本数据类型
- 1)变量与常量
- 在 Swift 语言中声明变量使用
var
关键字,声明常量使用let
关键字。// 定义 String 类型的字符串 str let str:String = "hello world"
- 声明时类型是可选的,如果在声明时没有指定类型且对变量赋了初值,编译器会自动推断常量或者变量的类型,这种机制被称为 “类型推断”。如果在声明时指定了类型又赋了初值,那么指定的类型必须和赋给它们的值一样,Swift 是一门强类型语言,不能将变量本身类型之外的值赋值给它。如果没有赋给初值,务必声明变量或者常量的类型,并用冒号充当分隔符,否则编译会报错。
- Swift 语言将具体的某种类型的值称之为类型字面量。例如
let num = 2.8
中的 "2.8" 就是浮点类型字面量。
- 在 Swift 语言中声明变量使用
- 2)整型
- Swift 语言拥有继承自 C 语言的有符号类型
Int、Int8、Int16、Int32、Int64
,以及无符号整形UInt、UInt8、UInt16、UInt32、UInt64
。其中Int
和UInt
类型的字长始终和当前平台的原生字长相同,即 32 位系统下声明获得的是 32 位的整型,64 位系统下获得的是 64 位的整型。 - 整型的取值范围:
- 最小值:
Int8.min
或INT8_MIN
- 最大值:
Int8.max
或INT8_MAX
- 最小值:
- 整型的声明:
- 隐式声明机制:
// 自动调用构造函数 let intNum:Int = 12
- 显式声明机制:
// 显式的调用初始化构造器 let intNum = Int.init(22)
- 隐式声明机制:
- 其他方法或属性:
// 计算两个数字之间的距离(两数之差) num.distanceTo(15) // 访问变量或常量的字符串版本 num.description 。。。。。
- Swift 语言拥有继承自 C 语言的有符号类型
- 3)浮点型
- Swift 语言为我们提供了两种有符号浮点数类型,
Float
和Double
。Float
是 32 位浮点数类型,Double
是 64 位浮点数类型。当使用类型推断声明一个浮点型变量或者常量时,变量或常量总是默认被推断为类Double
型。 - 浮点型的声明:
let floatNum:Float = 2.1 // 默认被推断为 Double 型 let doubleNum = 2.2`
- Swift 语言为我们提供了两种有符号浮点数类型,
- 4)布尔型
- Swift 语言中,布尔型只有两种值,
true
和false
。如果在 Swift 语言中直接使用零或者非零来表示逻辑真假,编译器一定会弹出异常。可以直接在布尔变量前加 “!”,来达到布尔值取反的作用。 - 布尔型的声明:
let boolNum:Bool = false
- Swift 语言中,布尔型只有两种值,
- 5)值类型/引用类型
- 在 Swift 语言中,所有的类型都可以被分为 “值类型” 或者 “引用类型”,可以将其理解为函数参数传递的方式。
- 从程序的角度来看,值类型和引用类型是相对的一个概念,其中的差别就在于:对新的对象产生赋值等指向性的操作之后,再次操作赋值对象或被赋值对象是否会同步于另外一个对象。
- 在 Swift 语言中,大多数类型都是值类型的,但是也有一些特殊情况,比如可以在函数参数定义中使用 inout 关键字将参数定义为引用类型。
// a,b 都是引用类型 func swapT<T>(inout a:T, inout b:T)
- 6)可选类型
- Swift 语言为我们提供了一种全新的、更加安全的类型 —— 可选类型。可选类型是使用范型枚举的形式来组织的,也就是说此特性可以运用于所有的类型、结构体、类或者其他复杂数据类型。
- 可选是指当一个变量、常量或者其他类中存储有值的时候返回里面存储的值,没有值的时候返回 nil。nil 不能用于非可选的变量或者常量,如果声明了一个可选的变量或者常量没有初始化,程序会默认赋值 nil。在 OC 中 nil 表示的是一个指向不存在对象的指针,而 Swift 中表示空的关键字为 “nil”,它没有其他含义。
- 可选的声明:
- 可选的标准声明形式是在程序中使用类型名紧跟 “ ? ”。
var value:Int? print("\(value)") // 或 print("\(value?.description)") // 输出为 nil
- 可选的显式声明形式。
var value:Optional<Int> print("\(value)") // 或 print("\(value?.description)") // 输出为 nil
- 可选的标准声明形式是在程序中使用类型名紧跟 “ ? ”。
- <1>、可选绑定(Optional binding):
var value:Optional<Int> if var maxValue = value { maxValue++ print("\(maxValue)") }
- 如果 value 值为 nil,则不执行变量 maxValue 的声明,同时也不执行 if 判断语句中第一个分支的代码段,这样程序会很容易被理解,而且只需要这样简单的两行代码就避免了因为使用值为 nil 的对象导致的程序异常。
- <2>、强制解析可选:
- 如果确定这个可选类型中的变量肯定包含值的时候,可以使用名称紧跟 “ ! ” 的方式强制获取类型可选中的值,从而省略判断步骤。但是如果这个变量中没有值,使用强制解析可选可能会在运行期弹出异常。这种机制叫做“强制解析可选”。
- <3>、隐式解析可选:
- 在某些程序架构中,在特定模块中可以确定某个可选变量总是有值的,这种时候可以使用隐式解析可选解析可选,隐式解析可选用于一个确定会有值的可选类型实例声明。可以将可选变量声明中的“ ? ”改为“ ! ” 来标注一个隐式解析可选。
var nullValue:String! = "Not Null String" print(nullValue)
- 在某些程序架构中,在特定模块中可以确定某个可选变量总是有值的,这种时候可以使用隐式解析可选解析可选,隐式解析可选用于一个确定会有值的可选类型实例声明。可以将可选变量声明中的“ ? ”改为“ ! ” 来标注一个隐式解析可选。
- <4>、可选运算符:
- 可选运算符 “ ?? ” 的执行逻辑是表达式 var value = a ?? b 中当操作数 a 不为 nil 时表达式返回操作数 a 的值,当操作数 a 为 nil 时表达式返回操作数 b 的值。
var value1:Int? var value2 = 3 var value = value1 ?? value2 print(value)
- 可选运算符 “ ?? ” 的执行逻辑是表达式 var value = a ?? b 中当操作数 a 不为 nil 时表达式返回操作数 a 的值,当操作数 a 为 nil 时表达式返回操作数 b 的值。
- <5>、可选链:
- 就是将可选的调用链接在一起形成一个链,如果任何一个节点为空(nil),将导致整个链失效。而不会引发强制解包可选时发生的错误。可选链可以多层可选。
- 7)泛型
- 使用同样的操作可以应用于不同的数据类型。泛型编程的实现是我们程序在另一种抽象层次上的提升。类是现实世界事物的抽象,而泛型则是现实世界行为的抽象。在 Swift 语言中,泛型可以说是用的最广最强大的特性之一,因为在 Swift 语言本身的语言底层大量的使用了泛型。
- 泛型同其他语言相通,用“< >” 符号来声明泛型。
func +<T : _IntegerArithmeticType>(lhs: T, rhs: T) -> T
- 泛型使得我们能够在编写好一份代码之后,应用于多种数据类型,甚至为了使用安全起见,我们还能限制响应的泛型数据类型,必须遵从某些约束。
func swapT<T>(inout a:T, inout b:T) struct Dictionary<Key : Hashable, Value> : CollectionType, DictionaryLiteralConvertible { }
- 8)元组
- 可以通过使用元组,把多个不同类型的值组合在一起,组成一个复合值。元组中的元素类型可以是相同的,也可以是不同的。元组的声明中也可以使用类型推断。元组可以用作函数返回值,它可以使函数能一次返回更多的信息。
- 元组的声明:
- 标准声明:
// 定义时指定元组名称、元素名称并且初始化 let myProject = (oneElement:"game", twoElement:2048)
- 匿名声明:
// 声明一个匿名的元组 let (appType, appName) = ("game", 2048)
- 标准声明:
- 元组中元素的访问:
- 标准声明的元组:
// 使用元素名访问 print(myProject.oneElement) // 使用元素在元组中的顺序下标访问 print(myProject.0)
- 匿名声明的元组:
// 使用元素名访问 print(appType)
- 标准声明的元组:
- 元组的声明:
- 可以通过使用元组,把多个不同类型的值组合在一起,组成一个复合值。元组中的元素类型可以是相同的,也可以是不同的。元组的声明中也可以使用类型推断。元组可以用作函数返回值,它可以使函数能一次返回更多的信息。
- 9)枚举
- 枚举是一种自定义的数据类型,在 Swift 中枚举类型拥有相当高的自由度。在 Swift 语言中枚举是一级类型,它拥有在其他语言中只有类才拥有的一些特性,比如实例方法,实例构造器等。枚举定义了一个常用的具有相关性的一组数据,并在你的代码中以一个安全的方式使用它们。
- 一个枚举通常包含多个枚举成员,枚举成员可以包括计算型属性、类型别名,甚至其它枚举、结构体和类。枚举声明中,每一个事件块都由一个 case 关键字开始。多个成员的值可以出现在一行上,用逗号分隔。
- 枚举是值类型,并且只有在赋予变量或常量,或者被函数调用时才被复制。
- <1>、标准定义格式:
enum enumerationName { case enumerationCase1 case enumerationCase2 ..... }
enum PointRect { case top case bottom case left case right }
- <2>、和 C 语言不同的是,标准的枚举定义方式成功定义枚举后,成员值并不会隐式被指定为 0、1、2、…… 这种形式。如果需要在定义时指定初始值,我们可以使用另一种形式。在声明的时候赋予的值叫做原始值(raw value),这些值的类型会自动进行判断,原始值必须是字面上的整数、浮点数、字符或者字符串。如果原始值类型变量被指定为整型,则不必为每个成员显示地指定值,它们会被隐式的被标为值 0、1、2 、…… 等。
- 带原始值的声明形式:
enum enumerationName: rawValueType { case enumerationCase1 = rawValue1 case enumerationCase2 = rawValue2 ..... }
enum PointRect:Int { case top case bottom = 2 case left case right } // 枚举原始值类型变量被指定为整型,只显示的指定一个值,其它会被隐式的指定值(top == 0, left == 3, right == 4) print(PointRect.top.rawValue)
- 带原始值的声明形式:
- <3>、另外可以通过为每个枚举成员设定一个或多个关联值,从而使用枚举来存储和维护一些特别的数据。
enum PointRect { case top(Int, Int) case bottom(Int, Int) case left(Double, Double) case right(Double, Double) }
- <4>、枚举与类和结构体的关系:
- 枚举与其他两者最大的相同之处就在于都可以定义方法。而其他的更多特性,对于枚举基本没有,没有属性,每一个枚举值都是常量。枚举中所定义的方法也基于对本身值的操作,无法定义一些无关的属性和操作。
- 10)结构体
- 结构体是值类型的,其实例将会在被赋予变量或者常量和被函数调用时被赋值。
- 标准定义格式:
struct structName { var 成员1: 数据类型 1 var 成员2: 数据类型 2 ..... }
struct BookInfo { var ID:Int = 0 var Name:String = "Default" var Author:String = "Default" var RootType:String = "Default" }
- 11)类型别名
- 在 Swift 语言中使用 typealias 定义类型别名。
typealias ShortInteger = Int8
- 在 Swift 语言中使用 typealias 定义类型别名。
- 12)类型转换
- 隐式类型转换:如 C 语言的类型转换
- 显式类型转换:Swift 语言是一种强类型语言,其整型的强制类型转换就是调用了参数类型对应的整形扩展构造方法,然后通过对应扩展构造方法的处理返回一个当前整形字长的整形值。
// 将字符型转换成整型 Int(12.4)
2、运算符
- Swift 语言支持大部分标准 C 语言的运算符,并且改进了许多特性来使我们的代码更加规范,其中主要包含算数运算符、区间运算符、逻辑运算符、关系运算符、赋值运算符、自增自减运算符、溢出运算符等。
- 1)组合赋值运算符:是将其他运算符和赋值运算符组合在一起执行的运算。算数自反赋值运算符属于组合赋值运算符。
- 要实现一个组合赋值符号需要把运算符的左参数设置成 inout 类型,从而使运算符函数体内部可以直接修改他的值。
func += (inout lhs: Int, rhs: Int) { lhs = lhs + rhs }
- 要实现一个组合赋值符号需要把运算符的左参数设置成 inout 类型,从而使运算符函数体内部可以直接修改他的值。
- 2)自定义运算符:自定义运算符是新的编程语言才支持的特性,不同于组合赋值运算符,你可以使用
/ = - + * % < > ! & | ^ ~
来组合构成新的运算符。- 自定义一个运算符通常需要先声明再实现其功能,声明自定义的运算符需要使用 operator 关键字。
operator :表示要进行运算符重载 infixpostfix :表示这是一个二元运算符,操作符在两个操作数中间。 prefix :表示这是一个一元运算符,操作符在操作数前边。 postfix :表示这是一个一元运算符,操作符在操作数后边。 associativity :结合性,包含 left(左结合)、right(右结合)和 none(自动),默认值为 none。 precedence :优先级,默认为 100,可省略。
// 声明自定义运算符 <> infix operator <> {associativity none precedence 100} // 实现自定义的运算符 <> func <> (lhs: Int, rhs: Int) -> Int { return (lhs + rhs) * (lhs - rhs) } // 输出值等于 20 let n1 = 6; let n2 = 4; let value = n1 <> n2; print(value)
- 自定义一个运算符通常需要先声明再实现其功能,声明自定义的运算符需要使用 operator 关键字。
- 3)运算符重载:让已有的运算符对自定义的类和结构进行运算或者重新定义已有运算符的运算规则,这种机制被称为运算符重载。同一个运算符在处理不同数据类型时,实现的是不同的功能。
// 声明运算符 infix operator >< { associativity left } // 实现运算符 func >< (inout leftValue:String, inout rightValue:String) -> String { var tmp = leftValue leftValue = rightValue rightValue = tmp return tmp }
- 默认的赋值符 “=” 和三目条件运算符( ? : )是不可重载的。
- Swift 语言和其他高级语言不同,其原生的关系运算符不能判断自定义的类型是否相等,所以我们需要重载自定义的类和结构的比较符 “==”或 “!=”。
func == (left: CenterPointer, right: CenterPointer) -> Bool { return (left.x == right.x) && (left.y == right.y) } func != (left: CenterPointer, right: CenterPointer) -> Bool { return !(left == right) }
- 4)运算符优先级和结合性:运算符的优先级使得一些运算符优先于其他运算符,从而使得高优先级的运算符会先被计算。结合性用于定义相同优先级的运算符在一起时和表达式结合或关联的规则。
- 结合性(associativity)包含 left(左结合)、right(右结合)和 none(自动),结合性的默认值为 none。优先级( precedence )默认为 100。
// 指定运算符的优先级和结合性 左结合 优先级 140 infix operator +- {associativity left precedence 140}
- 结合性(associativity)包含 left(左结合)、right(右结合)和 none(自动),结合性的默认值为 none。优先级( precedence )默认为 100。
3、表达式
- Swift 语言使用表达式来表示程序中的最小单位,通常一个表达式可以由数字、字符、运算符、变量、常量、函数调用等可以求得值的有意义的排列组成的组合。根据组合方式的不同,表达式可以分为基本表达式、多元表达式、前缀表达式、后缀表达式。
- 1)基本表达式:
- self 表达式:用于对当前类型或者类型实例自身进行引用,从而访问其内部成员。
self.menberFunc
- super 表达式:超类表达式,也可以理解为父类,用于访问当前类或者实例的父类成员或者方法。
super.menber
- 隐式成员表达式:用于在可以推断出类型的上下文中引用这个类型的成员。
var poNum = SomType.max poNum = .min
- 圆括号表达式:用于划分运算符优先级和创建元组,通常由一对圆括号和若干个自表达式和逗号共同构成。
(表达式1, lab2:表达式2, lab3:表达式3, ...)
- 通配符表达式:主要使用符号 “_” 来忽略表达式中的某个参数,这和正则表达式的通配符的概念是不同的。
(a, _) = (1, 2)
- self 表达式:用于对当前类型或者类型实例自身进行引用,从而访问其内部成员。
- 2)前缀表达式:
- 函数调用表达式:通常由函数名加上参数列表组成。
FuncName(value1, lab2:value2)
- 初始化函数表达式:即某个类型用于初始化其实例的函数表达式。
SomeClass.init
- 显式成员表达式:是显式的访问类型、元组或者其他模块成员变量的一种方式。
var cat:Tanimal() var iFoots = cat.hasfoot
- 后缀 self 表达式:通常有两种形式的后缀表达式。
- 1、
表达式.self
这种形式的表达式返回表达式的自身的值。 - 2、
类型实例.self
这种形式的表达式返回当前实例所属的类型,通常用于需要动态获取实例类型的场景中。
- 1、
- 动态类型表达式:专门用于动态获取类型的表达式。
- 标准形式是:
表达式.dynamicType
,其中表达式不能为类型名称。 - 可以通过使用
.dynamicType
获得当前实例对象所属的类型,并访问其类方法。
- 标准形式是:
- 附属脚本表达式:可以通过附属脚本表达式访问
getter/setter
的方法,他的基本形式是:表达式1 [index 表达式2]
- 强制取值表达式:使用 “!” 来强制获取某个不为 nil 的可选表达式的值。
- 可选链表达式:使用 “?” 来声明一个可选类型变量或者对象,当对象不为 nil 时就可以访问对象的方法或者成员。
- 在一个后缀表达式的子表达式中,有一个可选表达式,那么只有最外层的表达式返回的才是一个可选值。
- 函数调用表达式:通常由函数名加上参数列表组成。
4、控制流(控制结构)
- 指令的执行顺序在程序结构中,我们称之为控制流。控制流,也称为控制结构,通常包括:顺序结构、条件结构、循环结构、转向结构。
- 1)条件结构(分支结构):
- if 语句:
if <条件表达式> { 语句体 1 } else { 语句体 2 }
- switch 语句:
switch value { case value1: 语句体1 case value2: 语句体2 default: 默认语句体 }
- 和 OC 中的 switch 语句不同,在 switch 语言中你不需要在 case 块中显式的使用 break 语句跳出 switch,当匹配到的 case 块中的代码块中的代码执行完毕后,程序会终止 switch 语句,而不会继续执行下一个 case 块。
- 可以使用 fallthrough 在 switch 语句中使代码继续执行到下一个 case 中的代码,而不会检查它下一个将会落入执行的 case 中的条件是否匹配,从而达到和 C 语言标准中 switch 语句特性一样的效果。
- 在 switch 语言中每个 case 块后的匹配条件可以有多个,每个匹配条件之间用逗号隔开。switch 语句不会同时匹配大些字母和小写字母。 如:case 1, 2, 3, 4, 5,
- 在 switch 语言中每一个 case 块都必须包含至少一条语句。
- 可以使用元组在同一个 switch 语句中匹配多个值,元组中的元素可以是值,也可以是范围。
- switch 语句允许多个 case 匹配同一个值,不过如果存在多个可匹配分支的时候,只会执行第一个被匹配到的 case 块。
- 像 if 语句一样,switch 语句也支持值绑定,case 块允许将匹配的值绑定到一个临时的常量或变量,这个常量或变量在该 case 块里就可以被引用了。
- if 语句:
- 2)循环结构:
- for-in 循环语句:
for 循环变量 in <范围,集合,队列...> { 循环体..... }
- 当不需要使用范围内的每一项的值时,可以使用下划线 “_” 变量名来忽略对值的访问。
- 遍历字典时,字典的每项元素会以(key, value)元组的形式返回。
- 循环变量不需要定义。
for num in 0...10 { print(num) }
- for 循环语句:
for initialization; condation; increment { statements }
initialization
:初始化表达式,condation
:循环条件,increment
:改变循环条件的表达式。- 在 Swift 2.2 中 C 语言样式的 for 循环语句被废弃,C-style for statement is deprecated and will be removed in a future version of Swift。
- while 循环语句:
while <条件表达式> { statements } do { statements } while <条件表达式>
- 在 Swift 2.2 中 do-while 循环语句被废弃,使用 repeat-while 循环语句代替,'do-while' statement is not allowed; use 'repeat-while' instead。
repeat { statements } while <条件表达式>
- 在 Swift 2.2 中 do-while 循环语句被废弃,使用 repeat-while 循环语句代替,'do-while' statement is not allowed; use 'repeat-while' instead。
- for-in 循环语句:
- 3)控制转向语句:
continue
:会通知一个循环体立即停止本次循环,直接回到循环条件判断,重新开始下次循环。break
:会立即中断该循环体,然后跳转到表示循环体结束的大括号后的第一行代码,即跳出本层循环体。可以在 switch 和循环结构中使用。fallthrough
:在 switch 语句中使代码继续执行到下一个 case 中的代码,而不会检查它下一个将会落入执行的 case 中的条件是否匹配,从而达到和 C 语言标准中 switch 语句特性一样的效果。标签语句
:Swift 语言提供了更强大的跳出机制,你可以显式的指出需要跳出的是哪一层循环或 switch 结构。为了实现这个目的,我们可以使用标签来为循环体或者 switch 代码打上标记,当需要使用 break 或者 continue 时,带上这个标签就可以控制跳出或中断的是哪一个循环或 switch 结构。label loopName: for number in sArray { statements } // 跳出 loopName 循环 break loopName
5、函数
- 函数是执行特定任务的代码块,每个函数都有一个类型,可以像使用 Swift 语言中其他类型一样使用函数类型,将函数作为参数传递给其他函数,或者将函数类型当作返回类型。在 Swift 语言中没有主函数。在 Swift 语言中函数分为两类,一种是库和框架中的函数,一种是自定义的函数。
- 函数定义需要关键字 func,其一般格式为:
// 使用时第一个参数名(参数名1)会被省略 func 函数名 (参数名1:参数类型, 参数名2:参数类型 ...) -> 函数返回类型 { 函数体 ..... return 返回值 }
- 在 Swift 语言中,函数的形参和返回值是非常具有灵活性的,在需要的时候,可以定义一个或者多个甚至选择性的省略。实际上,在定义的时候忽略返回值等于隐式声明了函数的返回值类型为 void,而实际上函数还是返回了一个空的元组作为返回值。
- 1)外部形参:
- Swift 语言也能支持 OC 的函数参数标签模式,这种模式被称为外部形参。不过如果你为参数制定了外部形参名,那么在调用的时候就必须显式的使用。
- 定义格式:
// 使用时本地形参名(本地形参名1、本地形参名2 ...)会被省略 func 函数名 (外部形参名1 本地形参名1:参数类型, 外部形参名2 本地形参名2:参数类型 ...) -> 函数返回类型 { 函数体 ..... return 返回值 }
- 定义格式:
- 如果别人第一次阅读你的代码,使用外部形参名称可以使你要表达的意思更加明确,上下文更加清清晰。在写外部形参名时,完全可以只写一次名字,只需要用一个 hash 符号“#” 作为参数名称的前缀,从而告诉 Swift,我们使用了名称相同的本地形参名称和外部形参名称。
- 定义格式:
// Xcode 7.3.1 Swift 2.2 中不支持该种定义方式 func 函数名 (#参数名1:参数类型, #参数名2:参数类型 ...) -> 函数返回类型 { 函数体 ..... return 返回值 }
- 定义格式:
- Swift 语言也能支持 OC 的函数参数标签模式,这种模式被称为外部形参。不过如果你为参数制定了外部形参名,那么在调用的时候就必须显式的使用。
- 2)默认值形参:
- 在 Swift 语言中可以为任何形参定义默认值以作为函数定义的一部分,如果已经定义了默认值,那么调用函数时就可以省略该形参。为了避免遗漏参数或者参数传递的二义性,需在函数形参列表的末尾放置带默认值的形参,不要在非默认值的形参前放置。
- 定义格式:
func 函数名 (参数名1:参数类型, 参数名2:参数类型, sAge:String = "20") -> 函数返回类型 { 函数体 ..... return 返回值 }
- 定义格式:
- 在有定义默认值的情况下,当没有指定外部形参名称时,Swift 语言将为你定义的任何默认值形参提供一个自动外部形参名,这个自动外部形参名和本地形参名相同。
- 在 Swift 语言中可以为任何形参定义默认值以作为函数定义的一部分,如果已经定义了默认值,那么调用函数时就可以省略该形参。为了避免遗漏参数或者参数传递的二义性,需在函数形参列表的末尾放置带默认值的形参,不要在非默认值的形参前放置。
- 3)可变数量形参:
- 可变数量形参是指可接受零个或多个指定类型值的形参,可以用它来传递任意数量的输入参数。声明可变形参需要用到“...”,当参数传递进函数体后,参数在函数体内可以通过集合的形式访问。一个函数最多可以有一个可变参数,而且它必须出现在参数列表的最后。
- 定义格式:
func 函数名 (参数名1:参数类型, 参数名2:参数类型, numbers:Double...) -> 函数返回类型 { 函数体 ..... return 返回值 }
- 定义格式:
- 可变数量形参是指可接受零个或多个指定类型值的形参,可以用它来传递任意数量的输入参数。声明可变形参需要用到“...”,当参数传递进函数体后,参数在函数体内可以通过集合的形式访问。一个函数最多可以有一个可变参数,而且它必须出现在参数列表的最后。
- 4)可变值形参:
- Swift 语言函数的形参默认是常量,我们不能直接在函数体内部改变形参的值,也就是说函数的形参默认是值类型的。但是如果需要在函数体内部修改函数参数值,可以使用可变形参,要定义可变形参可以在参数名前使用 var 关键字。可变形参可以让你能够修改形参的值,它可以给函数体一个可修改的形参值副本,但这并不意味着可变形参就是引用类型的。
- 定义格式:
func 函数名 (var 参数名1:参数类型, var 参数名2:参数类型, ...) -> 函数返回类型 { 函数体 ..... return 返回值 }
- 定义格式:
- Swift 语言函数的形参默认是常量,我们不能直接在函数体内部改变形参的值,也就是说函数的形参默认是值类型的。但是如果需要在函数体内部修改函数参数值,可以使用可变形参,要定义可变形参可以在参数名前使用 var 关键字。可变形参可以让你能够修改形参的值,它可以给函数体一个可修改的形参值副本,但这并不意味着可变形参就是引用类型的。
- 5)引用类型形参:
- 在实际的编码中,我们往往需要在函数体内部修改形参值,并同时作用到实参本身,从而省去增加返回值数量的步骤。这时可以把形参定义为 in-out 类型,要定义 in-out 类型的参数,需要在参数名前使用 inout 关键字。当把变量传递给 in-out 形参时,必须在变量前添加 “&” 符号,以表明他被函数内部修改的是它本身的引用。
- 定义格式:
func 函数名 (inout 参数名1:参数类型, var 参数名2:参数类型, ...) { 函数体 ..... }
func *= (inout lhs: UInt, rhs: UInt)
- 定义格式:
- 使用 in-out 参数的同时有几条规则需要注意:
- 1、被标记为 inout 后不能将常量和字面量传递进函数。
- 2、不能同时将参数标记为 var、let、inout。
- 3、可变数量参数的参数不能标记为 inout。
- 4、函数不能有默认值。
- 在实际的编码中,我们往往需要在函数体内部修改形参值,并同时作用到实参本身,从而省去增加返回值数量的步骤。这时可以把形参定义为 in-out 类型,要定义 in-out 类型的参数,需要在参数名前使用 inout 关键字。当把变量传递给 in-out 形参时,必须在变量前添加 “&” 符号,以表明他被函数内部修改的是它本身的引用。
- 6)函数类型:
- 每一个函数都有特定的函数类型,函数类型通常由函数的形参类型和返回值类型组成。如果一个函数没有形参或返回值,那么这个函数的类型是 () -> () 。例如函数
func addString(s1:string, s2:string, s2:string) -> String { } 的类型就是 (String, String, String) -> String 。
- 可以在 Swift 语言中像使用其他任何类型一样的使用函数类型。例如可以定义一个函数常量或函数变量,并像一般数据类型指定初始值一样为他指定一个对应的函数。与其他类型一样,当你给函数赋一个变量或者常量时,你可以让 Swift 语言去推断函数的类型。
// 指定函数类型 var addSome:(String, String, String) -> String = addString // 推断函数类型 let anotherAddSome = addString
- 你也可以使用一个函数类型作为另一个函数的形参类型和返回值类型,使用方法和一般的数据类型相同。
- 每一个函数都有特定的函数类型,函数类型通常由函数的形参类型和返回值类型组成。如果一个函数没有形参或返回值,那么这个函数的类型是 () -> () 。例如函数
- 7)嵌套函数:
- 在一个函数体中定义另外一个函数体就称为嵌套函数。嵌套的函数默认对外是隐藏的,但仍可以通过包裹他们的函数调用和使用它们。
- 定义格式:
func 函数名1 (参数名1:参数类型, 参数名2:参数类型 ...) -> 函数返回类型1 { func 函数名2 (参数名3:参数类型, 参数名4:参数类型 ...) -> 函数返回类型2 { 函数体2 ..... return 返回值2 } 函数体1 ..... return 返回值1 }
- 定义格式:
- 在一个函数体中定义另外一个函数体就称为嵌套函数。嵌套的函数默认对外是隐藏的,但仍可以通过包裹他们的函数调用和使用它们。
6、断言
- 对每次运行都会出现的错误通常不会过于苦恼,可以使用断点调试或者 “try-catch”之类的方式判断并修复它。但是一些偶发(甚至是无数次运行才会出现一次)的错误单靠断点之类的方式是很难排除掉的,为此,引入一个不是很常用的调试工具函数:assert(condition: Bool, message: String),assert 是单纯地触发断言即停止程序,不会让你有机会将可能出错的设计走过它这一关。
- 在实际编码中,为了保证程序正常运行,只有在某些必要条件被满足的情况下才执行特定代码段,这种编程思想叫做防错性编程。
- 在 Swift 语言中可以调用全局的 assert 函数来增加一个断言,这里的全局意思是你可以将断言放在你程序的任何一个地方。程序在执行到 assert 时会判断其中的逻辑条件表达式参数是否为 true。如果条件判断为 true,代码运行会继续进行。如果条件判断为 false,程序将终止。通常,在为程序加入并触发断言后,Xcode 会精确定位到异常代码段,并反馈异常信息等修改 bug 必须的调试信息。
- 标准的断言格式:
assert(condition: Bool, message: String)
condition
判断条件,message
自定义调试信息,断言中的调试信息参数是可选的。
- 定义:
func assert(condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = default, file: StaticString = default, line: UWord = default) @inline(__always) func assertionFailure(_ message: @autoclosure () -> String = default, file: StaticString = default, line: UWord = default)
- 使用:
var usedate = -1 usedate = 2 // 当 usedate 大于 0 时,程序中断,进入断言函数打印调试信息 assert(usedate <= 0, "超出试用期,不能启动程序!")
- 系统在断言的源代码中加入了类似 “#if NDEBUG”这样的编译字,使其只能用于 debug 期,当你在发布 release 版本或者更新版的时候,编译器会使用一个编译字段将断言无效化,所以当你的产品在提交给用户之后还需要继续收集错误信息时,需使用其他方式。
- 断言函数中用到的“@autoclosure”属性,使用这种属性的参数意味着我们可以在这个参数的位置传入一个表达式,这个表达式会被自动封包成一个闭包,这也正是其字面的意思:“自动闭包”。在 assert 函数中它起到的作用也是非常明显的,如果在这里我们使用的是普通的布尔型参数,那么在执行到 assert 函数时,就会先运算条件表达式的值,而使用“@autoclosure”属性后,程序会先在 assert 函数内判断 debug 期的编译字是否存在,如果存在才会运算条件表达式的值,当然,这时条件表达式已经被自动封装成了一个闭包。
- 断言使用的几种场景:
- 验证参数的合法性。
- 将要使用一个对象,但是不确定其是否已经正确创建。
- 数组或者其他集合类、字典等复杂数据类型下标没有处于安全范围导致可能会越界。
- assert 函数的条件表达式参数最好一次只判断一个条件,因为如果判断多个条件,当断言被触发时,往往会无法直观的判断到底是哪一个条件不被满足。
7、闭包
- 闭包 在 Swift 中非常有用。通俗的解释就是一个 Int 类型里存储着一个整数,一个 String 类型包含着一串字符,同样,闭包是一个包含着函数的类型。有了闭包,你就可以处理很多在一些古老的语言中不能处理的事情。这是因为闭包使用的多样性,比如你可以将闭包赋值给一个变量,你也可以将闭包作为一个函数的参数,你甚至可以将闭包作为一个函数的返回值。它的强大之处可见一斑。在 Swift 的很多文档教材中都说函数是“一等公民”,起初我还不是很理解“一等公民”是什么意思,但当我理解了闭包以及它的强大功能后,我恍然大悟、茅塞顿开、醍醐灌顶。原来闭包的这些特性就是“一等公民”的特性啊!
- 闭包是功能性自包含模块,可以在代码中被传递和使用。一段程序代码通常由常量、变量和表达式组成,然后使用一对花括号“{}” 来表示闭合并包裹着这些代码,由这对花括号包裹着的代码块就是一个闭包。Swift 中的闭包与 C 和 Objective-C 中的 Block 以及其他一些编程语言中的 lambdas 比较相似。Block 和闭包的区别只是语法的不同而已,而且闭包的可读性比较强。
- 闭包是引用类型,无论你将函数/闭包赋值给一个常量还是变量,实际上都是在将常量/变量设置为对应函数/闭包的引用,这也意味着如果你将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包。
- 在 Swift 语言中有三种闭包形式:
- 全局函数:是一个有名字但不会捕获任何值的闭包。
- 嵌套函数:是一个有名字并可以捕获到其封闭函数域内的值的闭包。
- 匿名闭包:闭包表达式是一个利用轻量级语法所写的,可以捕获其上下文中变量或常量值。
- 1)函数形式:
func myConpare(s1:String, s2:String) -> Bool { return s1 > s2 } let namesArray:Array = ["Jill", "Tim", "Chris"] let names = namesArray.sort(myConpare)
- 2)一般形式:
{ (parameters参数) -> returnType返回类型 in statements }
- 可以使用常量、变量、inout、可变参数、元组类型作为闭包的参数,但不能在闭包参数中设置默认值,定义返回值和函数返回值的类型相同。
- 闭包表达式中的 in 关键字表示闭包的参数和返回值类型定义已经完成,这些参数和返回值都将在下面的闭包函数体中得到处理。
let namesArray:Array = ["Jill", "Tim", "Chris"] let names = namesArray.sort { (s1:String, s2:String) -> Bool in return s1 > s2 }
- 3)参数类型隐藏形式:
- Swift 中有类型推断的特性,所以我们可以去掉参数类型。
let namesArray:Array = ["Jill", "Tim", "Chris"] let names = namesArray.sort { (s1, s2) -> Bool in return s1 > s2 }
- Swift 中有类型推断的特性,所以我们可以去掉参数类型。
- 4)返回值类型隐藏形式:
- Swift 中有类型推断的特性,所以我们可以去掉返回值类型。
let namesArray:Array = ["Jill", "Tim", "Chris"] let names = namesArray.sort { (s1, s2) in return s1 > s2 }
- Swift 中有类型推断的特性,所以我们可以去掉返回值类型。
- 5)return 隐藏形式:
- 单行表达式的闭包可以通过隐藏关键字 return 来隐式地将单行表达式的结果作为返回值。
let namesArray:Array = ["Jill", "Tim", "Chris"] let names = namesArray.sort { (s1, s2) in s1 > s2 }
- 单行表达式的闭包可以通过隐藏关键字 return 来隐式地将单行表达式的结果作为返回值。
- 6)参数名省略形式:
- 闭包的使用非常的灵活,我们可以省略闭包参数列表中的参数的参数类型定义,被省略的参数类型会通过闭包函数的类型进行推断。同时,我们也可以在闭包函数体中通过使用闭包的参数名简写功能,直接使用
$0、$1、$2
等名字就可以引用的闭包参数值。如果同时省略了参数名和参数类型,那么 in 关键字也必须被省略,此时闭包表达式完全由闭包函数体构成。let namesArray:Array = ["Jill", "Tim", "Chris"] let names = namesArray.sort { $0 > $1 }
- 闭包的使用非常的灵活,我们可以省略闭包参数列表中的参数的参数类型定义,被省略的参数类型会通过闭包函数的类型进行推断。同时,我们也可以在闭包函数体中通过使用闭包的参数名简写功能,直接使用
- 7)trailing 闭包形式:
- 闭包可以做其他函数的参数,而且通常都是函数的最后一个参数。但是如果作为参数的这个闭包表达式非常长,那么很有可能会影响函数调用表达式的可读性,这个时候我们就应该使用 trailing 闭包。trailing 闭包和普通闭包的不同之处在于它是一个书写在函数参数括号之外(之后)的闭包表达式,函数会自动将其作为最后一个参数调用。
- 当函数有且仅有一个参数,并该参数是闭包时,不但可以将闭包写在 () 外,还可以省略 ()。Swift 2.2 中可以不管参数的个数完全省略 ()。
let namesArray:Array = ["Jill", "Tim", "Chris"] let names = namesArray.sort() { $0 > $1 }
- 8)闭包捕获:
- 闭包可以在其定义的上下文中捕获常量或变量,即使定义这些常量或变量的原作用域已经不存在,仍然可以在闭包函数体内引用和修改这些常量或变量,这种机制被称为闭包捕获。比如:嵌套函数就可以捕获其父函数的参数以及定义的常量和变量,全局函数可以捕获其上下文中的常量或变量。
func increment(amount: Int) -> (() -> Int) { var total = 0 func incrementAmount() -> Int { // total 是外部函数体内的变量,这里是可以捕获到的 total += amount return total } // 返回的是一个嵌套函数(闭包) return incrementAmount } // 闭包是引用类型,所以 incrementByTen 声明为常量也可以修改 total let incrementByTen = increment(10) incrementByTen() // return 10,incrementByTen 是一个闭包 // 这里是没有改变对 increment 的引用,所以会保存之前的值 incrementByTen() // return 20 incrementByTen() // return 30 let incrementByOne = increment(1) incrementByOne() // return 1,incrementByOne 是一个闭包 incrementByOne() // return 2 incrementByTen() // return 40 incrementByOne() // return 3
- 闭包可以在其定义的上下文中捕获常量或变量,即使定义这些常量或变量的原作用域已经不存在,仍然可以在闭包函数体内引用和修改这些常量或变量,这种机制被称为闭包捕获。比如:嵌套函数就可以捕获其父函数的参数以及定义的常量和变量,全局函数可以捕获其上下文中的常量或变量。
8、下标脚本
- 下标脚本 允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。语法类似于实例方法和计算型属性的混合。与定义实例方法类似,定义下标脚本使用 subscript 关键字,显式声明入参(一个或多个)和返回类型,每个输入参数的类型也没有限制,返回值可以是任何类型,并无限制。输入参数也可以使用可变参数,但使用输入/输出(in-out)参数或和给参数设置默认值都是不允许的。与实例方法不同的是下标脚本可以设定为读写或只读。这种方式又有点像计算型属性的 getter 和 setter 方法。
- 下标脚本就是对一个东西通过索引,快速取值的一种语法,例如数组的 a[0]。这就是一个下标脚本。通过索引 0 来快速取值。在 Swift 中,我们可以对类(Class)、结构体(structure)和枚举(enumeration)中自己定义下标脚本的语法。
- 重点:
- 下标脚本使用 subscript 关键字来定义。
- 下标脚本使用 get、set 来定义读、写属性,并不需要 2 个属性都有,可以只读,并且读必须有。
- 定义 set 属性时,传入的参数默认名称为 newValue。并且 newValue 的类型和 subscript 函数返回值相同。
- 1) 下标脚本的使用 1
- 下标脚本的定义
struct myString { var str:String = "" subscript(start:Int, length:Int) -> String { get { return (str as NSString).substringWithRange(NSRange(location: start, length: length)) } set { str = newValue } } }
- 下标脚本的使用
let str1 = myString(str: "hello world") let str2 = str1[2, 5] // 输出 hello world print(str1[0, 11]) // 输出 llo w print(str2) var str3 = myString() // [0, 0] 参数无意义 str3[0, 0] = "world" // 输出 world print(str3[0, 5])
- 下标脚本的定义
- 2) 下标脚本的使用 2
- 下标脚本的定义
class Student1 { var scores:[Int] = Array(count:5, repeatedValue:0) subscript(index:Int) -> Int { get { return scores[index]; } set { scores[index] = newValue } } subscript(indexs:Int...) -> [Int] { get { var values:[Int] = Array() for index in indexs { values.append(scores[index]) } return values } set { var i = 0 for index in indexs { scores[index] = newValue[i] i += 1 } } } }
- 下标脚本的使用
let stu1 = Student1() stu1[0] = 1 stu1[1] = 2 // 输出 a[0]:1, a[1]:2 print("a[0]:\(stu1[0]), a[1]:\(stu1[1])") let stu2 = Student1() stu2[1, 2, 3] = [5, 6, 7] // 输出 [0, 5, 6, 7, 0] print(stu2[0, 1, 2, 3, 4])
- 下标脚本的定义
时间: 2024-09-23 00:55:15