开发者社区> JoanKing> 正文

Swift5.0 - day2-流程控制、函数、枚举(下)

简介: Swift5.0 - day2-流程控制、函数、枚举(下)
+关注继续查看

2.11、内联函数(Inline function)


  • 如果开启了编译器优化(Release模式默认会开启优化),编译器会默认将某些函数变成内联函数
    • 将函数调用展开成函数体


image.png

不会被内联的函数:函数体比较长、包含递归调用、包含动态派发......


  • 2.12、函数类型:每一个函数都是有类型的,函数的类型由:形式参数类型,返回值类型组成
    • 例子一:无参数无返回值


func test() {}
    • 例子二:有参数有返回值


func test(a: Int, b: Int) -> Int {
   return a + b
}
    • 例子三:调用的时候不需要传参数标签


func test(_ a: Int ,_ b: Int) -> Int {
    return a + b
}
test(2,3)  // 调用的时候不需要传参数标签
  • 2.13、函数类型作为函数参数


func sum(a: Int, b: Int) -> Int{
    return a + b
}
func difference(a:Int,b:Int) -> Int{
    return a - b
}
func printResult(_ mathFn:(Int,Int)->Int,_ a:Int,_ b:Int){
    print("result=\(mathFn(a,b))")
}
printResult(sum, 5, 2)  // 打印结果:result=7
printResult(difference, 5, 2) // 打印结果:result=3
  • 2.14、函数类型作为函数返回值


func next(_ input: Int) -> Int {
    return input + 1
}
func previous(_ input: Int) -> Int {
    return input - 1
}
func forward(_ forward: Bool) -> (Int) -> Int {
    return forward ? next:previous
}
print(forward(true)(3))  //  结果:4
print(forward(false)(3))  //  结果:2

提示:返回值是函数的函数,叫做高阶函数(Height-Order Function)


  • 2.15、typealias: 用来给类型起别名


typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64
  • 如下:


typealias Date = (year:Int,month:Int,day:Int)
func test(_ date:Date){
    print(date.year,date.month,date.day)
}
test((2019,6,19))
  • 例二:(重点👂👂👂👂👂👂👂👂👂👂)


typealias IntFn = (Int,Int) -> Int
func difference(v1:Int,v2:Int)->Int{
     return v1-v2
}
let fn:IntFn = difference
fn(20,10)
func setFn(_ fn:IntFn){}
setFn(difference)
func getFn() -> IntFn{
    return difference
}
  • 2.16、嵌套函数: 将函数定义在函数的内部


func forward(_ forward:Bool)->(Int)->Int{
    func next(_ input:Int)->Int{
  
         return input + 1
    }
    func previous(_ input:Int)->Int{
  
         return input - 1
    }
    return forward ? next:previous
}
forward(true)(3)  // 4
forward(false)(3) // 2


三、枚举



  • 3.1、枚举的基本用法


enum Direction {
    case north
    case south
    case east
    case west
}
enum Direction {
    case north,south,east,west
}
  • 使用如下:


var dircetion = Direction.north
dircetion = Direction.west
dircetion = .south
  
print("dircetion=\(dircetion)")
let dir = Direction.north
  
switch dir {
case .north:
      print("north")
case .south:
      print("south")
case .east:
      print("east")
case .west:
      print("west")
}
  • 3.2、关联值(Associated Values)
    • 有时会将枚举的成员值跟其他类型的关联存储在一起,会非常有用


enum Score {
    case points(Int)
    case grade(Character) 
}
var source = Score.points(96)
source = .grade("A")
switch source {
case let .points(i):
   print(i,"points")
case let .grade(i):
  print("grade",i)
}
    • 打印结果:grade A
    • 下面代码必要的时候 let 可以改为 var


enum Date{
     case digit(year:Int,month:Int,day:Int)
     case string(String)
 }
 var date = Date.digit(year:2019,month:6,day:21)
 date = .string("2019-6-21")
 switch date {
 case .digit(let year,let month,let day):
      print(year,month,day)
 case let .string(value):
      print(value)
 }


    • 打印结果:2019-6-21
    • 关联值举例


image.png

enum Password{
   case number(Int,Int,Int,Int)
   case gesture(String)
}
var pwd = Password.number(3,5,7,8)
pwd = .gesture("12368")
switch pwd {
case let .number(n1,n2,n3,n4):
    print("number is \(n1) \(n2) \(n3) \(n4)")
case let .gesture(str):
    print("gesture is",str)
}
    • 打印结果:gesture is 12368
  • 3.3、原始值 (Raw Values):枚举成员可以使用相同类型的默认值预先关联,这个默认值叫做:原始值


enum PlayingCards : Character{
    case A = "a"
    case B = "b"
    case C = "c"
    case D = "d"
}
var suit = PlayingCards.A
print(suit)   // A
print(suit.rawValue) // a
print(PlayingCards.D.rawValue) // d
enum Grade : String{
   case perfect = "A"
   case great = "B"
   case good = "C"
   case bad = "D"
}
print(Grade.perfect.rawValue)   // A
print(Grade.great.rawValue)     // B
print(Grade.good.rawValue)      // C
print(Grade.bad.rawValue)       // D
  • 3.4、隐式原始值 (Implicitly Assignd RawValues):如果枚举的原始类型是Int、String 类型,Swift会自动分配原始值
    • String 类型


enum Direction {
     case north,south,east,west
}
print(Direction. north) // north
print(Direction. north.rawValue) // north
    • 等价于


enum Direction: String {
   case north =  "north"
   case south =  "south"
   case east =  "east"
   case west =  "west"
}
    • Int 类型


enum Season:Int{
   case spring,summer,autumn,winter
}
print(Season.spring.rawValue)   // 0
print(Season.summer.rawValue)  // 1
print(Season.autumn.rawValue)  // 2
print(Season.winter.rawValue)  // 3
    • Int 类型设置了默认原始值,设置了值之后 Swift会自动递增原始值


enum Season:Int{
   case spring = 2,summer,autumn = 5,winter
}
print(Season.spring.rawValue)   // 2
print(Season.summer.rawValue)  // 3
print(Season.autumn.rawValue)  // 5
print(Season.winter.rawValue)  // 6
  • 3.5、递归枚举 (Recursive Enumeration)


enmu ArithExpr{
    case number(Int)
    indirect case sum(ArithExpr,ArithExpr)
    indirect case difference(ArithExpr,ArithExpr)
}
let five = ArithExpr.number(5)
let four = ArithExpr.number(4)
let two = ArithExpr.number(2)
let sum = ArithExpr.sum(five,four)
let difference = ArithExpr.difference(sum,two)
func calculate(_ expr:ArithExpr) -> Int{
    switch expr {
    case let .number(value):
       return value
    case let .sum(left,right):
       return calculate(left) + calculate(right)
    case let .difference(left,right):
       return calculate(left) - calculate(right)
    }
}
calculate(difference)


提示:使用 indirect 关键字修饰的枚举值表示这个枚举是可以递归的,即此枚举值中的相关值使用其枚举类型本身。


  • 3.7、MemoryLayout: 获取数据类型占用的内存大小


enum Password{
    case number(Int,Int,Int,Int)  // 32 字节
    case other  // 1 个字节就搞定了
}
MemoryLayout<Password>.stride   // 40 分配占用的内存空间大小
MemoryLayout<Password>.size  // 33 实际用到的空间大小
MemoryLayout<Password>.alignment  // 8 对齐参数
var password = Password.number(2,3,4,5)
password = .other
MemoryLayout.stride(ofValue:password)   // 40
MemoryLayout.size(ofValue:password)  // 33
MemoryLayout.alignment(ofValue:password)  // 8


  • 提示1:stride:范围 与 size 的区别
    size: 分配占用的内存空间大小
    stride: 实际用到的空间大小
    内存对齐是 8个字节 ,所以上面是 32+1 = 40
  • 提示2:N个字节存储关联值(N取内存最大的关联值),比如number(Int,Int,Int,Int)就是 4*8=32 里面的元素是关联值,1个字节用来存储成员值(也就是case的个数)


  • 3.8、枚举拓展
    • 拓展一:枚举的关联值(3.2) 和 默认原始值(3.3) 的区别
      分析:枚举的关联值 的值 是写到枚举的内存中的,而枚举的原始值是固定死的,没有写入枚举的内存中
      举例如下:


enum Season:Int{
     case spring,summer,autumn,winter
}
MemoryLayout<Season>.stride   // 1
MemoryLayout<Season>.size  // 1
MemoryLayout<Season>.alignment  // 1
    • 拓展二:思考下面枚举变量的内存布局


enum TestNum {
   case test1,test2,test3,test4
}
print( MemoryLayout<TestNum>.stride) // 1 分配占用的内存空间大小
print(MemoryLayout<TestNum>.size) // 1 实际用到的空间大小
print(MemoryLayout<TestNum>.alignment) // 1 对齐参数


  • 提示1:直接查看内存布局


image.png

image.png


提示2:我们还可以在Debug -> Debug Workflow -> View Memory 手动输入内存地址来查看内存布局,如下


image.png



var t = TestNum.test1
print( Mems.ptr(ofVal: &t))
t = .test2
t = .test3
t = .test4
    • 上面是采取一个字节来存储枚举变量的数据,一个字节 00 用来存储 test1、一个字节 01 用来存储 test2、一个字节 02 用来存储 test3、一个字节 03 用来存储 test4,其实也就是来区分成员变量的值
    • 按照上面的一个字节来区分的话,一个字节的范围是 0x00~0xFF,最大也就是 256,每一个case也就是一个成员变量,其实case test1,test2,test3,test4 相当于四个case


  • 拓展三:看下面一个复杂的枚举


enum TestNum {
  case test1(Int,Int,Int)
  case test2(Int,Int)
  case test3(Int)
  case test4(Bool)
  case test5
}
print(MemoryLayout<TestNum>.size)  //  25分配的内存大小
print(MemoryLayout<TestNum>.stride) // 32 实际使用的内存大小
print(MemoryLayout<TestNum>.alignment)  // 8 内存对齐的字节数


  • 分析:分配内存的规则:如果有多个case,那么必有一个字节存储来区分成员变量case,再看case关联值最多的那个有多少个关联值,N个字节存储关联值(N取内存最大的关联值),比如number(Int,Int,Int)就是 3*8=24 里面的元素是关联值,1个字节用来存储成员值(也就是case的个数),那么分配的内存是 32,实际用了 24+1= 25
  • 拓展四:看下面一个有意的的枚举


enum TestNum {
    case test1(Int,Int,Bool,Bool)
    case test2(Int,Int)
}
print(MemoryLayout<TestNum>.size)  //  实际使用的内存大小 18
print(MemoryLayout<TestNum>.stride) // 分配的内存大小 24
print(MemoryLayout<TestNum>.alignment) // 内存对齐的字节数 8
  • 分析:8+8+最后一个Bool【1(第一个bool)+1(区别成员变量) 】= 17 这是编译器的进一步优化,它把case标志放在最后一个Bool字节里去了,相当于最后一个Bool,它的那个字节里面包含了case成员信息。因为Bool类型只有2种取值,true是1,false是0,只会用到1个二进制位,所以Bool的1个字节还是有多余空间的,如果是Int这些,就有可能要把它自己的8个字节都用完。所以无法拿来放case,比如0xFF FF FF FF FF FF FF FF,占满了8字节,而Bool,你的8个二进制位,不管存储true还是false,都只需要用到1位


  • 拓展五:没有关联值


enum TestNum {
     case test1,test2,test3,test4
}
print(MemoryLayout<TestNum>.size)  //  实际使用的内存大小 1
print(MemoryLayout<TestNum>.stride) // 分配的内存大小 1
print(MemoryLayout<TestNum>.alignment) //  内存对齐的字节数 1


  • 分析:这里的 1 仅仅是为了区分 成员变量使用的

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
23523 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
22217 0
阿里云服务器安全组设置内网互通的方法
虽然0.0.0.0/0使用非常方便,但是发现很多同学使用它来做内网互通,这是有安全风险的,实例有可能会在经典网络被内网IP访问到。下面介绍一下四种安全的内网互联设置方法。 购买前请先:领取阿里云幸运券,有很多优惠,可到下文中领取。
21933 0
如何设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云安全组设置详细图文教程(收藏起来) 阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程。阿里云会要求客户设置安全组,如果不设置,阿里云会指定默认的安全组。那么,这个安全组是什么呢?顾名思义,就是为了服务器安全设置的。安全组其实就是一个虚拟的防火墙,可以让用户从端口、IP的维度来筛选对应服务器的访问者,从而形成一个云上的安全域。
18580 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
11974 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
36336 0
+关注
433
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载