swift语法
swift初见
第一个程序输出hello,world!
print("hello,word!")
在swift中这行代码就算一个完整的程序。你不需要写一个main函数,因为在swift中左边的文件main.swift就是程序的入口,所以swift中的这个文件的文件名必须是main,否则会报错。如果将文件名改为其他名字,程序就找不到入口而报错
在swift中也不需要写分号,每一个换行就代表一句话的结束(注意:上面的打印语句是没有写分号的)。但如果两句话写在一行则需要写分号:
print("hello,");print("world!")
第一章:常量和变量
常量和变量的声明
使用let来声明常量,使用var来声明变量。一个常量的值,在编译的时候并不需要有明确的值,但是你只能为它赋值一次。swift中尽量使用常量,因为系统会对常量进行优化,而变量则不会
let constant1: Int
let bconstant2 = 20
var variable = 30.5
variable = 50
常量和变量的类型和你赋给它的值一样。然而声明时类型是可选的,声明的同时赋值的话,编译器会自动推断类型。在上面的例子中,编译器推断出==constant2==是一个整数,==variable==是一个浮点数。如果初始值没有提供足够信息(或者没有初始值),那么久需要在变量或者常量后面声明类型,用冒分割,正如==constant1== 。
作用域:
变量和常量只在使用它的花括号里起作用,作用域越小越好,能用常量的地方就不要使用变量(编译器会优化常量,使代码效率更高)
类型转换
swift 中没有隐式转换,如果需要把一个值转换为其他类型,必须显示转换,例如
let a = 5
//必须将 a转化为浮点型才能与5.5相加,否则会报错
let b = (float)a + 5.5
将数字转换为字符串
let name = "王大锤"
let age = 25
let information = name + string(age) + "岁"
最后会输出”王大锤25岁"
其实还有一种把值转换成字符串的方法:把值写到括号中去,并且在值的前面加反斜杠==(\ )==
例如:
let apples = 5
let appleSumary = "我有(\apples)个苹果"
常见的类型
swift中常见的类型有整型、浮点型、字符串
整型分为有符号整型和无符号整型
在64位中,有符号整型表示的范围是 - 2^32 ~ 2^32 - 1 ;无符号整型所能表示的范围是0 ~ 2^64 - 1
第二章:运算符
认识基本运算符
运算符有一元、二元和三元运算符:
一元运算符对单一操作对象操作(如-a)。一元运算符需紧排操作对象之前(如!b)
二元运算符操作两个操作对象(如2 + 3),是中置的,因为它们出现在两个操作对象之间。
三元运算符操作三个操作对象,和 C 语言一样,Swift 只有一个三元运算符,就是三元条件运算符(a ? b : c)。
常见的运算符有+, -, *, /, %, *, =, +=, -=, *=, /=, ==, >, <, >=, <=, ..., ||, &&, !
swift中舍弃了自增 ++ 和自减 --
swift中的运算符其实都是函数
1.赋值运算符 : = += -= /= = %=
2.算数运算符 :+ - / %
关系运算符 :== != < <= > >=
逻辑运算符 :&& || !
三元条件运算符: ? :
其他运算符: 点运算. 下标运算[] 可空类型? 保证有值 ! 其他??
可空类型 ?? other :意思是如果前面的可空类型是空,那么久执行后面代码,如果前面的可空类型有值,就执行前面的可空类型
易错知识点:
1.浮点数求余运算:
swiftz中可以对浮点数进行求余运算。
例如:9.0 除 2.5等于 3余 1.5,所以结果是一个Double值1.5
2.三元条件运算(Ternary Conditional Operator)
三元条件运算的特殊在于它是有三个操作数的运算符,它的原型是: 问题?答案1:答案2。如果问题成立,返回答案1的结果; 如果不成立,返回答案2的结果。三元运算可以替换if - else语句,使代码更加精简明了
3.区间运算符
闭区间运算符(a...b)定义一个包含从a到b(包括a和b)的所有值的闭区间。c常在for-in循环中使用
第三章:流程控制
使用==if==和switch来进行条件操作,使用==for - in==、==while==、==repeat - while==来进行循环。其中条件和循环变量的括号可以省略
if语句的使用示例
1、随便写一个年分判断是否是闰年
let year = 1999
if year % 4 == 0 && year % 100 != 0 || year % 4 == 0 {
print("\(year)是闰年")
}else{
print("\(year)不是闰年")
}
2、给出某年某月,打印该月的天数
let year = 2004
let mouth = 2
var days
if mouth = 2{
days = year % 4 == 0 && year % 100 != 0 || year % 4 == 0 ? 29: 28
}
else if mouth = 4 || mouth = 6 || mouth = 9 || mouth = 11 {
days = 30
}else{
days = 31
}
print(day)
同样,switch语句(开关语句)同样可以实现分之结构,我更倾向于把它理解为一个开关,它就像一个拥有多个闸门的开关一样,选择性的接通某个通道
switch使用示例
随机生成一个整数,用switch判断这个整数是什么
let number = Int(arc4random_uniform(3)) + 1
swith number {
case 1:
print("a")
case 2:
print("b")
default:
print(c)
}
以上为一个简单的开关语句,在swift中以下几点需要注意:
1、switch语句不能穿透,必须写==default==
2、switch运行到匹配的句子之后,程序退出switch语句,并不会继续往下执行,所以不需要在每个句子后写==break== 。
for循环的使用示例
利用==for - in== 来进行循环操作
==示例1:==求1000以内的偶数和
let n = 1000
var result = 0
for i in 2...n{
if i % 2 == 0 {
result += i
}
}
==示例2:==经典的猴子吃桃问题
let days = 10
let result = 1
for _ in 1..<10{
result = 2 * (result + 1)
}
print(result)
在上述两个示例中,for - in 的用法有两处不同。如果不需要用循环次数做操作的时候,in 前面就可以用下划线 _ 代替,并不会影响循环的执行。in 后面代表的是循环次数,== ... == 代表闭区间,== ..< == 代表左闭右开区间。
while循环的使用
使用while来重复运行一段代码直到不满足条件。循环条件既可以在开头,也可以在结尾。repeat - while与c语言的do - while 用法相同,当程序执行一次之后再判断是否满足条件。swift 中do作为其他关键字,所以用repeat。
var n = 2
while n < 100 {
n = n * 2
}
print(n)
var m = 2
//do {
// m = m * 2
//} while m < 100
repeat {
m = m * 2
} while m < 100
print(m)
while循环的使用示例
示例1:打印1到100以内的素数
//第一步:找出素数
func isPrime(n: Int) -> Bool{
var i = 2
//在2~根号n之间找有没有n的因子
while i <= Int(sqrt(Double(n))){
if n % i == 0{
return false
}
i += 1
}
return true
}
//第二步:打印素数
for n in 1...100{
if isprime(){
print(n)
}
}
示例二:找两个数的最大公约数和最小公倍数
//最大公约数
func gcd (x: Int, y: Int) -> Int {
let i = x < y ? x: y
while{
if x % i ==0 && y % i == 0{
return i
}
i -= 1
}
return 1
}
//最小公倍数
func lcm (x: Int, y: Int) -> Int{
return x * y / gcd(x, y: y)
}
运用递归找最大公约数效率更高
// 短除法(欧几里得算法)
// x和y的最大公约数跟y%x和x的最大公约数是一样的
// Greatest Common Divisor
func gcd(x: Int, _ y: Int) -> Int {
if x > y {
return gcd(y, x)
}
else if y % x != 0 {
return gcd(y % x, x)
}
else {
return x
}
}
循环综合练习
switch的嵌套使用
人机猜拳游戏
var computer = arc4random_uniform(3) + 1
var human: Int
repeat{
print("请出拳,(1剪刀,2石头,3布)",terminator: "")
human = inputNum()
}while human < 1 || human > 3
switch computer{
case 1:
print("计算机出了剪刀")
switch human{
case 1:
print("平局")
case 2:
print("你赢了")
case 3:
print("你输了")
default:
break
}
case 2:
print("计算机出了石头")
switch human{
case 1:
print("输了")
case 2:
print("平局")
case 3:
print("赢了")
default:
break
}
case 3:
print("计算机出了布")
switch human{
case 1:
print("赢了")
case 2:
print("输了")
case 3:
print("平局")
default:
break
}
default:
break
}
for循环的嵌套使用
一:百钱买白鸡问题(穷举法:穷尽所有的可能性,找出正确答案)
问题描述: 公鸡一只5钱,母鸡一只3钱,小鸡三只1钱,用一百钱买100只鸡,算每种鸡各多少只?
//公鸡最多可以买20只,母鸡最多买33只
for x in 0...20{
for y in o...33{
//小鸡总数
let z = 100 - x - y
if z / 3 + x + y == 100 && x + y + z == 100{
print("公鸡\(x)只,母鸡\(y)只,小鸡\(z)只")
}
}
}
for 与while 嵌套使用
二:五人分鱼问题^
var total = 1
var isEnough = ture
while {
//当次循环时total作为鱼的总数
for _ in 1...5{
if (total - 1) % 5 == 0{
//如果够分,就扔掉一条再乘以五分之四作为下一次分鱼的总数
total = (total - 1) / 5 * 4
}else{
//如果不够分就直接退出for循环,让初始总数加一
isEnough = false
break
}
}
if isEnough {
print(total)
break
}
total += 1
}
章节练习
练习题1:输出九九乘法表
练习题2:双骰子赌博游戏
练习题3:打印斐波拉契数列
第四章:数组
数组的创建与赋值
//创建数组的几种方式
var array1 = [Int]()
var array2: [Int] = []
var array3 = [12, 23,34]
var array4 = [Int](count: 10, repeatedValue: 1)
var array5: [String] = []
var array6 = ["王", "大", "锤"]
//数组的赋值
var array7 = array6 //可以直接将某个数组给另一个数组赋值
var array8 = array6[0...1] //也可将某个数组的某个几个元素选择性的给另一个数组赋值
获取数组元素个数
array6.count //获取array6的数组元素个数
对数组元素进行遍历
在循环中可以对数组元素进行修改
for i in 0..<array3.count{
if i == 1{
array3[i] = 10
}
}
只读循环,不可以修改数组元素
//示例一:
for str in array6{
print(str)
}
//示例二:
for (index, value) in array6.enumerate(){
print("\(index), \(value)")
}
向数组中添加或删除元素
//添加元素
array6.append("葵")
array6.append("宝")
array6.insert("花", atIndex: 4)
array6.insert("典", atIndex: array6.count)
//删除元素
//使用array6.remove方法
数组做加法(将多个同类型数组合并起来)
let array9 = array6 + array6
数组的排序
//系统默认升序排列,但可以通过传参数 > 或 < 实现升降序排列。
let array = [23, 43, 15, 50, 46]
let newArray1 = array.sort() //默认升序排列,可省略 >
let newArray2 = array.sort(<) //传参数 < 实现降序排列
//以上两种排序方式都是重新定义的两个数组,不影响array数组
就地排序
array.sortInPlace()
//以上这种方式是直接给原数组排序,会改变原数组
关于排序的几种方法(了解)
简单的选择排序(按从大到小排列)
思想:将两个数组元素两两比较,如果后一个元素大于前一个元素,就用索引记录下该元素的下标,再利用元组赋值将其交换
var array = [92,43,87,34,65]
var minIndex = 0
for i in 0..<array.count - 1{
var minIndex = 0
for j in i + 1...array.count - 1{
if array[j] < array[minIndex] {
minIndex = j
}
(array[minIndex], array[j]) = (array[j], array[minIndex])
}
}
print(array)
冒泡排序(从小到大排序)
像气泡一样大的往上升
for i in 0..<array - 1{
for j in 0..< array.count - 1 - i {
if array[j] > array[j + 1]{
(array[j], array[j + 1]) = (array[j + 1], array[j])
}
}
}
冒泡排序升级版
思想:当经过某一轮比较后没有发生两两交换,就表示已经排序好了,那就终止循环,不用再排序了
var array = [92,43,87,34,65]
for i in 0..<array.count - 1{
var swapped = false
for j in 0..<array.count - 1 - i{
if array[j] > array[j + 1]{
(array[j], array[j + 1]) = (array[j + 1], array[j])
swapped = true
}
}
if swapped == false {
break
}
}
print(array)
数组的重要应用(也是后面的闭包知识)
let array = [12, 32, 54, 83, 34, 23, 97, 76, 45, 68]
//一:过滤:筛选出需要的数据
//过滤出值大于50的元素(保留50以上的值)
let newArray1 = array.filter{ $0 > 50}
print(newArray1)
//过滤出偶数(取偶数)
let newArray2 = array,filter{ (x: Int) -> Bool in
return x % 2 == 0
}
print(newArray2)
//二:映射:通过某种映射公式计算出映射值
let newArray3 = array.map { (x: Int) -> Int in
return x * x
}
let newArray4 = array.map{$0 * $0}//上面也可以也成这种形式,结果一样
let newArray5 = array.map{sqrt(Double($0))}
print(newarray3, newArray4, newArray5)
//三:缩减: 通过某种方式得出最终想要的值
//通过将所有值加到一起实现数组的缩减(加减乘除等运算符都是函数)
let newArray6 = array.reduce(0,combine: + )
print(newArray6)
//通过取最大值将数组缩减
let newArray7 = array.reduce(array[0]) { $1 > $0 ? $1 : $0 }
print(newArray7)
集合与字典
集合
集合的定义:
????????????????
字典
字典:存放键值对组合的容器
字典是由两部分组成,其中冒号前面是键,后面是值。通过键获取后面与之对应的值,字典是可空类型的,因为所给的键可能没有与之对应的值。数组是有序的,字典是无序的.
字典的基本使用
//创建一个dict字典
var dict: [String: String] = ["a": "阿西吧", "y": "亚麻得"]
//向字典里添加元素
dict["shit"] = "狗屎"
dict["ouba"] = "欧巴"
//向字典里删除元素a所对应的值
dict.removeValueForKey("a")
//将字典里的某一个key的值赋值为nil,也可以实现删除元素的功能
dict["y"] = nil //这样”亚麻得“就被删除了
//修改元素
dict["ouba"] = "牛粪"
//字典的遍历
//1.遍历字典的value
for value in dict.value{
print(value)
}
//2.遍历字典的key
for key in dict.keys{
print(key)
}
//3.通过元组获得字典中的键和值
for (key, value) in dict{
print("\(key) ---> \(value)")
}
函数与闭包
函数
函数的定义和调用
函数的定义
func 函数名(参数列表)-> 返回值{ 代码块 }
参数名有内部参数名与外部参数名之分,如果不写外部参数名那么内部参数名也是外部参数名,且第一个外部参数名省略。外部参数名可以用下划线代替,表示内部参数名与外部参数名一样,且省略外部参数名省略。如果写明外部参数名,那么外部参数名就不可以省略
函数声明:
函数名1(外部参数名 内部参数名: 类型, 外部参数名 内部参数名: 类型)-> 返回类型
函数名2(参数名:类型, 参数名: 类型)-> 返回类型
函数的调用:
函数名1(外部参数名: 传值, 外部参数名: 传值)
函数名2(传值, 内部参数名(也就是外部参数名): 传值)
函数的基本使用
//打印 hello XXX
func sayHello(personName: String) -> String{
let greeting = "hello," + personName + "!!!"
//如果函数的返回类型不是void,那么函数中一定有return语句
return greeting
}
//调用上述函数,参数名personNam被省略
print(sayHello("王大锤"))
func myMax(a x: Int, b y: Int){
return x > y ? x : y
}
//在调用上述函数的时候,由于定义函数的时候没有写外部参数名所以所以第一个外部参数名省略,第二个外部参数名与内部参数名默认一致并且要写
print(myMax(2, y: 5))
func myMin(a x: Int, b y: Int){
return x > y ? y : x
}
//在调用上述函数的时候,由于定义函数的时候写清楚了外部参数名所以所以外部参数名都要写
print(myMin(a: 2, b: 5))
//定义函数的时候可以给函数设定默认值
//如果调用函数的时候没有给函数赋值,那么就直接使用默认值
func sum(a: Int = 1, b: Int = 2, c: Int = 3) -> Int{
return a + b + c
}
//直接使用默认值的情况
print(sum())
//重新给函数赋值,就会按新的值计算
print(sum(4, b: 4, c: 5))
//参数是可变列表(函数参数个数可以是任意多个)
func sum(nums: Int ... ) -> Int{
var total = 0
for num in nums{
total += num
}
return total
}
let a = sum(2,3,4)
let b = sum(3,4,5,6)
print(a , b)
//使用元组来让函数一次性返回多个值
//定义一个返回最小值与最大值的函数
func minMax(array: [Int]) -> (min: Int, max: Int)? {
//如果数值为空,就返回nil,所以元组为可空类型,需要写问号 ?
if array.isEnpty{ return nil }
//设数组的第一个值是最大值,也是最小值
var currentMin = array[0]
var currentMax = array[0]
//从数组的第二个值开始依次与最小值和最小值(第一个值)比较
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
}else if value > currentMax{
currentMax = value
}
}
return (currentMin, currentMax)
}
/函数可以嵌套使用。被嵌套的函数可以访问外侧函数的变量,可以使用嵌套函数来重构一个太长或者太复杂的函数
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
//函数调用的传参只是传值
//正常情况下,函数不能修改外部参数
//inout - 输入输出参数(不仅将数据传入函数还要从函数中取出数据)
//inout既可以传参进去,也要传出来(用于修改外部参数)
func createX(inout x: Int){
x = 100
}
var x = 1
//将外部参数x修改后传出来,注意要写取地址 & 符号
createX(&x)
print(x)
//???????????????????
//函数是第一等类型,这意味着函数可以作为另一个函数的返回值
func makeIncrementer() -> (Int -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
//函数也可以当做参数传入另一个函数
func hasAnyMatches(list: [Int], condition: Int -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
//hasAnyMatches(numbers, lessThanTen)
hasAnyMatches(numbers, condition: lessThanTen)
示例程序
//设计一个函数传人两个正整数m和n,计算从m加到n的和
var total = 0
func sum(m: Int, _ n: Int) -> Int{
let (a, b) = m > n ? (n, m) : (m, n)
for i in a...b{
total += i
}
return total
}
print(sum(3, 5))
//设计一个函数输入三条边的长度,判断能不能构成三角形
func isTriangle(a: Int, b: Int, c: Int) -> Bool{
return a + b > c && a + c > b && b + c > a
}
//设计一个函数,传人年月日,返回该日期是这一年的第几天
func dayOfYear(year: Int, month: Int, day: Int) -> Int{
var totalDays = 0
for i in 1..<month{
let days = monthDays(year, month: i)
totalDays += days
}
totalDays += day
return totalDays
}
func monthDays(year: Int, month: Int) -> Int{
var days = 31
if month == 4 || month == 6 || month == 9 || month == 11{
days = 30
}else if month == 2{
if isLeapYear(year){
days = 29
}else{
days = 28
}
}
return days
}
func isLeapYear(year: Int) -> Bool{
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0
}
print(dayOfYear(2016, month: 12, day: 31))
//设计一个函数计算组合数C(m, n) = m! / n! / (m - n)!
func combine(m: Int, n: Int) -> Int{
//断言,如果不满足条件,程序运行时就会死在这里并提醒函数的使用者
assert(m > n, "m必须大于n")
return factorial(m) / factorial(n) / factorial((m - n))
}
//求阶乘
func factorial( n: Int) -> Int{
var facN = 1
for i in 1..<n{
facN *= i
}
return (facN)
}
函数也是一种类型,也可以用常量或变量来定义。可以将函数当作另一个函数的参数活着返回值来使用
func sum (a: Int, _b: Int) -> Int{
return a + b
}
func mul(a: Int, b: Int) -> Int{
return a * b
}
//用一个变量来保存函数,可以实现一个函数拥有多项功能,可以将不同的运算函数赋给fn,那么将来如果要使用其他运算也可以由fn代替了
var fn = sum
//将函数fn当参数传入foo函数
func foo(array: [Int], fn: (Int, Int) -> Int) -> {
var sum = array[0]
for x in array[0..<array.count]{
sum = fn(sum, x)
}
return sum
}
//调用函数的时候传入函数参数的时候直接写函数名就可以了
let a = [1, 3, 5, 7, 8]
print(foo(a, fn: fn))
知识小结:
当调用上述foo函数时,第二个参数可以传什么?
1.传入所有的自定义的==(Int, Int) -> Int==类型的函数
==print(foo(a, fn: mul))==
2.传入二元运算符:+-*/ 因为二元运算符就是函数
==print(foo(a, fn: +))==
3.传入==匿名函数(闭包)==
如果函数的最后一个参数是闭包可以写成尾随闭包的形式, 也就是将闭包放到函数参数的圆括号外面写在一对花括号中, 如果函数后面有尾随闭包且函数的圆括号中没有参数, 那么函数的圆括号也可以省略(仅限于有尾随闭包的场景)
//还是接上一个示例程序
//3.1闭包的完整写法
foo(a, fn: {(a, b) -> Int in
return a + b
})
//3.2省略掉类型和不必要的括号
foo(a, fn:{ a, b in a + b})
//3.3省略参数名
foo(a, fn: {$0 + $1})
//3.4尾随闭包
foo(a){(a, b) -> Int in
return a + b
}
闭包
闭包是一种匿名函数,跟 C ,OC 中的 block 以及 C# , C++, Java中的 lambda 表达式相似,代码的终极原则是高内聚,低耦合,而函数的闭包就是为了降低代码的耦合性而生的
//给数组排序
var array = ["game", "cat", "abacus", "good", "dislike", "dog"]
//1.传入二元运算符
array.sortInPlace(>)
//如果函数参数的最后一个参数是闭包,可以写成尾随闭包的形式,也就是将闭包放到函数的圆括号外面,写在花括号中。如果函数后面有尾随闭包且函数的括号中没有参数,那么函数的圆括号也可以省略
//2.尾随闭包写法1
array.sortInPlace(){$0 > $1}
//3.尾随闭包写法2
array.sortInPlace{$0 > $1}
//按字母个数排序
array.sortInPlace{(one, two) -> Bool in
//characters:代表字符串里的所有字符
return one.characters.count < two.characters.count
}
函数的递归调用
函数自己调用自己。需要注意的时必须判断函数什么时候停下来,否则函数就会一直调用,直到内存耗尽。递归调用通常只用于循环不能解决的问题,比如迷宫,汉若塔等。递归效率太低,一般不用
使用函数递归必须合理使用以下两点
1.递归公式
2.收敛条件
//计算1到 n 的和
func sum(n: Int) -> Int {
if n == 1 {
return 1
}
return n + sum(n - 1)
}
//汉若塔问题
func hanoi(n: Int, _ a: String, _ b: String, _ c: String){
if n > 0 {
//第一步:将n - 1个盘子移动到c柱子
//第二步:将第n个盘子移动到b柱子
//第三步:将n - 1个柱子移动到b柱子上
hanoi(n - 1, a, c, b)
print("\(a) --> \(b)")
hanoi(n - 1, c, b, a)
}
}
面向对象编程
面向对象编程步骤:
一:定义类(如果需要的类苹果官方已经提供,就可以直接进入第二步)
二:创建对象(创建对象必须调用初始化方法)
三:给对象发消息
面向对象编程的基本常识
- 定义类就可以创建出新类型
- 变量定义在类的外面就叫做变量 - variable
- 变量定义在类中就叫做属性 - property
- 函数写在类的外面就叫做函数 - function
- 函数写在类的里面就叫做方法- method
- 数据抽象(找名词,找到与该类相关的属性)与行为抽象(找动词,找到与该类相关的方法)
- 类的属性都需要经过初始化方法init初始化后才能使用,初始化方法应该保证所有的存储属性都被初始化(有值)。但如果数据抽象出来就给他赋值,就不需要初始化(但不推荐这样写,因为这样会多消耗内存。有的属性还没用到就已经给它赋值了,就会占用不必要的内存)
- 计算属性 - computational property (通过对存储属性做运算得到的属性),通常获得某个计算出来的值的方法都可以设计成计算属性。计算属性可以用get和set方法获取或修改属性值
类与对象
类是对象的蓝图和模板,为对象提供了属性和方法。
那什么是对象呢?
万物皆为对象,只要是我们看得见,摸得着,或者感受得了的都可以当做对象。比如说空气就是一个对象,它属于气体类,拥有透明的颜色、压强等属性,空气能流动、能提供给动物呼吸,这就是它的行为(也称为方法)
对象都属于类,并且每个对象都是独一无二的。类是抽象的,对象是具体的
类的设计:
1.找出该类的相关属性(包括存储属性和计算属性),其中计算属性是通过对存储属性做运算得到的,计算属性通常属于只读属性,所以只有get,没有set
2.初始化存储属性:我们可以在一个类中定义多个初始化方法,其中初始化方法包括两种。
2.1便利初始化方法/便利构造器:调用了其他初始化方法飞初始化方法,需要在init前面加上 convenience 关键字
2.2指派初始化方法/指构造器: 被其他初始化方法调用的初始化方法,(主要用来初始化所有存储属性)
2.3 指派构造器前面加上required可以将构造器指定为必要构造器, 所谓的必要构造器意味着子类也要提供一模一样的构造器
3.找出与该类相关的方法
4.如果在特定的应用场景中发现现有的类缺少了某种功能,那么可以通过类扩展(extension)的方式现场添加这项功能
示例1:
知识点:
==便利构造器与指派构造器的使用==
1.一个类中可以存在多个初始化方法
2.利用关键字convenience可以定义一个便利初始化方法,这个方法调用的是其它初始化方法
3.创建对象的时候就可以选择调用需要的初始化方法
class Point {
var x: Double
var y: Double
// 便利初始化方法 / 便利构造器
convenience init() {
self.init(x: 0, y: 0)//调用初始化方法初始化坐标原点
}
//调用初始化方法初始化坐标点并且坐标点用元组表示
convenience init(point: (Double, Double)) {
self.init(x: point.0, y: point.1)
}
// 指派初始化方法 / 指派构造器
init(x: Double, y: Double) {
self.x = x
self.y = y
}
//计算与其他点的距离
func distanceTo(other: Point) -> Double {
let dx = x - other.x
let dy = y - other.y
return sqrt(dx * dx + dy * dy)
}
//移动到某个点
func moveTo(x: Double, _ y: Double) {
self.x = x
self.y = y
}
//移动多少距离的方法
func moveBy(dx: Double, _ dy: Double) {
x += dx
y += dy
}
}
必要构造器
指派构造器前面加上required可以将构造器指定为必要构造器,所谓的必要构造器就意味着子类也要提供一模一样的构造器
class Person {
var name: String
var age: Int
required init(name: String, age: Int) {
self.name = name
self.age = age
}
// 便利构造器(convenience)
convenience init() {
self.init(name: "无名氏", age: 20)
}
}
class Student: Person {
var major: String
required init(name: String, age: Int) {
major = "未知"
super.init(name: name, age: age)
}
convenience init(name: String, age: Int, major: String) {
// 下面的语句必须写在调用自己的初始化方法之后否则major属性会被赋上不正确的值
// self.major = major
self.init(name: name, age: age)
self.major = major
// 初始化的第一阶段
// 1. 初始化自己特有的属性
// self.major = major
// // 子类只能调用直接父类的构造器
// // 子类构造器必须调用父类的非便利构造器(指派构造器)
// // super.init() // compiler error
// // 2. 调用父类的初始化方法
// super.init(name: name, age: age)
// // 初始化的第二阶段
// // 此处可以调用对象的方法因为对象已经完成了初始化
// study()
}
func study() {
print("\(name)正在学习.")
}
}
示例2
知识点:
==类扩展(extension)==
给已有的类添加新的方法
//通过类扩展(extension)的方式给UIColor添加一项随机生成一种颜色的功能
//为了得到一个min~max范围内的随机整数
func randomInt(min: UInt32, _ max: UInt32) -> Int {
return Int(arc4random_uniform(max - min + 1) + min)
}
//通过extension给UIColor扩展一个randomColor()方法
extension UIColor {
static func randomColor() -> UIColor {
let r = CGFloat(randomInt(0, 255)) / 255.0
let g = CGFloat(randomInt(0, 255)) / 255.0
let b = CGFloat(randomInt(0, 255)) / 255.0
return UIColor(red: r, green: g, blue: b, alpha: 1)
}
}
示例3
知识点:
1==类的修饰符==
- public (公共的) 可以在其它项目中使用(只需要将文件拷贝到其它项目中即可)
- internal(内部的)系统默认的修饰符,只能在项目内实现访问
- private(私有的)只能在该类里面进行访问,一般用来保护数据
修饰符的使用一般遵循以下两点:
1.存储属性通常是private的,因为数据需要保护起来,不能让外界随意修改,并且有些属性还必须隐藏一部分信息,比如银行卡卡号和用户姓名等信息需要隐藏一部分
2.方法一般是public的,因为方法是对象接收的消息,如果自定义的类没有打算在其他项目里面使用,可以不写访问修饰符,直接使用默认的internal修饰符,表示在只本项目中公开,对其他项目私有。
2==文档注释==
一个好的程序员都应该有写注释的习惯,注释不仅能让别人看懂自己写的代码,还可以方便以后自己看懂(自己写的代码过一段时间自己都不一定能看懂)。一个有逼格的程序员,会用文档注释来使代码清晰直观。文档注释能够在其它调用它的地方通过alt来查看方法说明、参数说明和返回值。能让人一眼就能看懂这个类是干什么的
//创建一个学生类,要求对学生姓名设置隐私保护
// 学生
public class Student {
private var _name: String
private var _age: Int
/// 学生姓名隐去最后一个字符
//用get方法获取对象属性,并隐藏部分信息
public var name: String {
get {
let displayName = _name.substringToIndex(_name.endIndex.advancedBy(-1))
return displayName + "*"
}
}
// 学生的年龄
public var age: Int {
get { return _age }
}
/**
初始化方法
- parameter name: 姓名
- parameter age: 年龄
*/
public init(name: String, age: Int) {
_name = name
_age = age
}
/**
吃饭
- parameter food: 吃的东西
*/
public func eat(food: String) {
print("\(_name)正在吃饭.")
}
/**
学习
- parameter courseName: 课程的名称
- parameter hour: 学习时间
- returns: 学会了返回true否则返回false
*/
public func study(courseName: String, hour: Int) -> Bool {
print("\(_name)正在学习\(courseName).")
return hour > 180 ? true : false
}
/**
看片
*/
public func watchJapaneseAV() {
if _age >= 18 {
print("\(_name)正在观看岛国爱情动作片.")
}
}
}
示例4
知识点:
1.==短除法(欧几里得算法)==
如果 Y > X , X 和 Y 的最大公约数跟 Y % X 和 X 的最大公约数相等
2.==运算符重载==
swift中所有运算符都是函数,内置运算符能运算的类型太局限,如果我们自定义的某个类型也需要用到运算符,这时候就需要对运算符进行重载,来实现自己定义类型的运算。运算符重载后可以直接用运算符进行运算,避免了调用方法,能够使代码更直观
3.==级联式编程==
级联式编程是定义的方法的返回值是对象本身,如果多个方法都是返回对象本身,就可以同时调用,形成一个链状的代码,也称之为开火车式的编程
//短除法算最大公倍数,可以提高性能
func gcd(x: Int, _ y: Int) -> Int {
if x > y {
return gcd(y, x)
}
else if y % x != 0 {
return gcd(y % x, x)
}
else {
return x
}
}
class Fraction {
private var _num: Int
private var _den: Int
var info: String {
get {
return _num == 0 || _den == 1 ? "\(_num)" : "\(_num)/\(_den)"
}
}
init(num: Int, den: Int) {
_num = num
_den = den
simplify()
normalize()
}
//以下四个方法是分别对分数做加减乘除的方法。对两个数做加减乘除运算本可以不用返回对象本身,但这里对对象运算后都返回了对象本身,就是为了实现开火车式的编程
func add(other: Fraction) -> Fraction {
return Fraction(num: _num * other._den + other._num * _den, den: _den * other._den)
}
func sub(other: Fraction) -> Fraction {
return Fraction(num: _num * other._den - other._num * _den, den: _den * other._den)
}
func mul(other: Fraction) -> Fraction {
return Fraction(num: _num * other._num, den: _den * other._den)
}
func div(other: Fraction) -> Fraction {
return Fraction(num: _num * other._den, den: _den * other._num)
}
//将分数正规化,如果分母为0,分子分母同时乘以 -1
func normalize() -> Fraction {
if _den < 0 {
_num = -_num
_den = -_den
}
return self
}
//将分数化简
func simplify() -> Fraction {
if _num == 0 {
_den = 1
}
else {
let x = abs(_num)
let y = abs(_den)
let g = gcd(x, y)
//分子分母同时除以最小公倍数,使其化简
_num /= g
_den /= g
}
return self
}
}
// 运算符重载(为自定义的类型定义运算符)
func +(one: Fraction, two: Fraction) -> Fraction {
return one.add(two)
}
func -(one: Fraction, two: Fraction) -> Fraction {
return one.sub(two)
}
func *(one: Fraction, two: Fraction) -> Fraction {
return one.mul(two)
}
func /(one: Fraction, two: Fraction) -> Fraction {
return one.div(two)
}
继承和多态
继承
继承是从已有的类创建新类的过程。提供继承信息的类称为父类(超类/基类),得到继承信息的类称为子类(派生类/衍生类)。
通常子类除了得到父类的继承信息还会增加一些自己特有的东西,所以子类的能力一定比父类的功能强大。继承的意义在于子类可以复用父类的代码并且增强系统现有的功能
示例程序:
定义一个父类 Person :
enum Sex {
case Male
case Female
}
class Person {
var name: String
var age: Int
var sex: Sex
//初始化Person的属性
init(name: String, age: Int, sex: Sex){
self.name = name
self.age = age
self.sex = sex
}
//吃的方法
func eat () {
print("吃")
}
}
定义一个继承 Person 的子类 Student :
//在 Student 后面加上一个冒号,然后写上另一个类,就表示前面一个类继承后面的类
class Student: Person {
var major: String
//子类的初始化方法
init(name: String, age: Int, major: String, sex: Sex){
//先初始化自己的特有的属性
self.major = major
//再初始化父类属性
super.init(name: name, age: age, sex: sex)
}
//Student的学习行为
func study(corseName: String) {
print("\(name)是\(major)专业的学生")
print("\(sex == .Male ? "他":"她") 正在学习\(corseName)")
}
}
定义一个继承 Person 的子类 Teacher :
class Teacher: Person {
//teacher的职称属性
var title: String
//初始化
init(name: String, age: Int,title: String, sex: Sex){
self.title = title
super.init(name: name, age: age, sex: sex)
}
//teacher的教书行为
func teach(corseName: String) {
print("\(name)是\(title)")
print("\(sex == .Male ? "他":"她") 正在教\(corseName)")
}
}
main函数:
//创建对象
let p1 = Person(name: "大傻子", age: 20, sex: .Male)
p1.eat() //p1对象只有eat行为
let stu = Student(name: "二傻子", age: 15, major: "计算机计算", sex: .Male)
stu.eat()
stu.study("计算机科学")//stu对象除了有eat行为,还增加了study行为
let t = Teacher(name: "老傻子", age: 50, title: "叫兽", sex: .Male)
t.eat()
t.teach("c语言") //t对象除了有eat行为,还增加了teach行为
//以上可以看出,父类对象的功能比子类对象的少,子类除了得到父类的继承信息还会增加一些自己特有的东西
//注意:
//我们可以将子类型的对象赋值给父类型的变量(因为子类型跟父类型直间的关系是 is - a 关系)
//学生是人,老师是人,所以学生对象和老师对象可以直接赋值给人类型的变量
let person1: Person = Student(name: "张三", age: 20, major: "计算机网络", sex: .Male)
let person2: Person = Teacher(name: "李四", age: 50, title: "叫兽", sex: .Male)
//如果要将父类型的变量转换为子类型,需要用as运算符进行类型转换
//如果能确定父类型的变量就算某种子类型对象,可以用as!进行转换
//如果不能确定父类型的变量是哪种子类型可以用as?尝试转换
(person1 as! Student).study("计算机网络")//确定person1是Student类型,用as!进行转换
(person1 as! Teacher).teach("计算机网络")//程序会在这里崩溃,因为感叹号 ! 代表person1肯定是Teacher类型,系统会将它当做Teacher使用,然而person1并没有teach方法,所以程序会在这里崩溃
//为了解决当不知道对象属于哪种类型的问题,使用as?进行判断
if let temp = person2 as? teacher{
print("你是老师,来教书吧")
temp.teach("C语言")
}else{
print("你不是老师,滚吧")
}
多态
什么是多态?
同样的对象类型,接收相同的消息(调用相同的方法),但可以实现做不同的事情,这就是多态。
实现多态的关键步骤:
1.方法重写:子类在继承父类的过程中对父类已有的方法进行重写,而且不同的子类给出不同的实现版本
2.对象造型:将子类对象当做父类型来使用,然后再通过 if + as?将父类型安全的转换为子类型,然后再调用子类特有的方法
示例程序:
定义一个父类 宠物 - Pet
//定义一个父类对象(动物Pet)
class Pet {
var nickname: String
var gender: Gender
var age: Int
init(nickname: String, gender: Gender, age: Int) {
self.nickname = nickname
self.gender = gender
self.age = age
}
func eat() {
print("\(nickname)正在吃东西.")
}
func play() {
print("\(nickname)正在玩耍.")
}
func shout() {
print("\(nickname)发出了叫声.")
}
}
定义一个子类 猫-Cat
// Cat和Pet之间是IS-A关系(继承)
class Cat: Pet {
var hairColor: String?
// 父类有的方法子类可以重新实现 这个过程叫方法重写
// 需要在方法前添加override关键字
// 重写有时也被称为置换/覆盖/覆写
override func play() {
super.play()
print("\(nickname)正在玩毛线球.")
}
override func shout() {
print("\(nickname): 喵喵喵……")
}
func catchTheMouse() {
print("\(nickname)正在抓老鼠.")
}
}
定义一个子类 狗-Dog
class Dog: Pet {
var isLarge: Bool//是否为大狗
init(nickname: String, gender: Gender, age: Int, isLarge: Bool) {
self.isLarge = isLarge
super.init(nickname: nickname, gender: gender, age: age)
}
override func play() {
super.play()
print("\(nickname)正在接飞碟.")
}
override func shout() {
print("\(nickname): 旺旺旺……")
}
func keepTheDoor() {
if isLarge {
print("\(nickname)正在看门.")
}
else {
print("\(nickname)太小了谁也打不过.")
}
}
}
定义一个子类对象 情妇-Mistress
class Mistress: Pet {
override init(nickname: String, gender: Gender, age: Int) {
super.init(nickname: nickname, gender: gender, age: age)
}
override func play() {
print("\(nickname)正在购物.")
}
override func shout() {
print("此处省略一万字,少儿不宜!")
}
func makeTrouble() {
print("\(nickname): 我怀孕了……")
}
}
定义一个Person对象,Person与Pet之间是 has - a 关系(人拥有宠物)
class Person {
var name: String
var pet: Pet? //人可以拥有宠物,也可以不拥有,所以加个问号
init(name: Sting){
self.name = name
}
func adopt(pet: Pet) {
print("\(name)领养了\(pet.name)")
self.pet = pet //人的pet属性赋值为传入的pet(表示人现在的pet就是函数传进来的pet)
}
//人打宠物的行为
func strikePet(){
//判断人是否有宠物
if let pet = pet{
print("\(name)正在打\(pet.name)")
pet.shout() //人的宠物叫
}else{
print("\(name)正在自虐")
}
}
func play(){
//判断人是否有宠物
if let pet = pet{
print("\(name)正在和\(pet.name)玩耍")
}else{
print("\(name)正在自嗨")
}
}
}
main函数
let person = Person(name: "王大锤")
let petArray = [
Dog(nickname: "小黄", gender: .Male, age: 1, isLarge: true),
Cat(nickname: "小咪", gender: .Female, age: 1),
Mistress(nickname: "小美人", gender: .Female, age: 24)
]
//以下便是多态的应用
for pet in petsArray{
//三种宠物都是同样对象类型 - Pet
//三种宠物都有相同的行为,但打印结果是按照不同宠物量身定制的
//不同的动物执行相同的方法,结果不同
pet.eat()
pet.play()
pet.shout()
//判断是什么动物,不同的宠物执行不同的方法,通过 if - as? 将父类型安全的转换为子类型,然后再调用子类特有的方法
if let dog = pet as? Dog{
dog.keepTheDoor()
}else if let cat = pet as? Cat{
cat.catchTheMouse()
}else if let mis = pet as? Mistress{
mis.makeTrouble()
}
}
面向协议编程
protocol 协议名称:父类协议1, 父类协议2,...,{
方法的集合(计算属性相当于方法)
}
协议是方法的集合(计算属性相当于就时方法),可以把看似不相关的对象的公共行为放到一个协议中
协议在swift中大致有三种作用:
1.表能力:遵循协议就意味着具备了某种能力
2.表约定:遵循协议就一定要实现协议中的方法
3.表角色:一个类或者结构可以遵循多个协议,一个协议可以被多个类或结构遵循。遵循协议就意味着扮演了某种角色,遵循多个协议就意味着扮演多种角色
协议中可以声明方法和计算属性
接口(协议)隔离原则:协议的设计要小而专,不要大而全
协议的设计也要高度内聚
swift中继承是单一继承,即一个子类只能有一个父类。遵循协议可以遵循多个
协议扩展extension:可以在协议扩展中给协议的方法提供默认实现,也就是说如果某个类遵循了协议但没有实现这个方法就直接使用默认实现
协议中全是抽象概念(只有声明没有实现)遵协议的类可以各自对协议的方给出不同的实现版本,这样面向协议编程就可以把多态的优势发挥的淋漓尽致 可以写出更通用灵活的代码(符合开闭原则)
实现开闭原则的关键有两点:
1.抽象是关键
2.封装可变性(桥梁模式 - 将不同的可变因素封装到不同的继承结构中)
设计模式
-
代理模式
-
委托回调
一个对象想做某件事情,但是自身没有能力做这件事情就可以使用委托回调,具体步骤如下:- 设计协议,被委托方遵循协议,实现方法
- 委托方有一个属性是协议类型的,通过该属性可以调用协议中的方法
- 委托方的协议类型属性通常要写成weak引用,并且是可空类型
其他
- 协议组合:protocol<协议1, 协议2,... ,>
- 协议方法
- 协议扩展
堆和栈
计算机的硬件由五大部件构成:运算器控制器、存储器、输入设备和输出设备
运算器 + 控制器==>CPU(中央处理器)
存储器 ==>内存(RAM)
程序猿可以使用的内存大致分为五块区域:
堆(heap):创建的对象都是放在堆上的。堆的特点是容量大速度慢
栈(stack):定义的局部变量/临时变量都放在栈上。栈的特点是容量小速度快
静态区:
-静态段 - 全局变量
-只读数据段 - 常量
-代码段 - 函数和方法
结构的对象是值类型,类的对象是引用类型。值类型在赋值的时候会在内存中进行对象拷贝,引用类型不会进行对象拷贝只是增加了一个引用
我们在定义新类型时优先考虑使用类而不是结构,除非要定义一些底层的数据结构(保存其他数据类型)
泛型
泛型,让类型不再是程序中的硬代码
- 泛型函数
- 泛型类、结构、枚举
相关知识
- 泛型限定
- where子句
swift中的类、结构和枚举都可以使用泛型
//给数组排序的函数
<T: Comparable>限定T类型必须是遵循了Comparable协议的类型
func bubbleSort<T: Comparable>(array: [T]) -> [T] {
var newArray = array
for i in 0..<newArray.count - 1 {
var swapped = false
for j in 0..<newArray.count - 1 - i {
if newArray[j] > newArray[j + 1] {
mySwap(&newArray[j], &newArray[j + 1])
swapped = true
}
}
if !swapped {
break
}
}
return newArray
}
在该段代码中,数组类型是泛型,所以数组可以是任意类型
定义一个虚拟类型T调用函数时根据函数传入的参数类型来决定T到底是什么
错误处理
- throw
- throws/throws
- do
- catch
- try
其他
- ARC
- 正则表达式
- 嵌套类型
swift中可选类型与if - let 语法
var count: Int?
count = 100
if let validCount = count {//如果count有值的话,把count的值赋给validCount,且条件为true;如果count为nil,则执行else
"count is " + String(validCount) //count的值为100
} else {
"nil"
}
网友评论