函数
1、函数的定义
1、带返回值函数
传递的形参(参数)只能是let
类型;
func sumfunc(v1:Int,v2:Int)->Int
{
return v1+v2
}
sumfunc(v1: 10, v2: 20)
// 返回值为Int类型
2、隐式返回
整个函数体,只有一个单一的表达式,函数可以使用隐式返回这个表达式
注意
:函数体内如果有其他代码,不仅仅只有表达式(如:v1+v2),那么需要使用return返回
func sumfunc(v1:Int,v2:Int)->Int
{
v1+v2
}
sumfunc(v1: 10, v2: 20)
// 返回值为Int类型
3、无返回值
func sayHello()->Void
{
print("Hello")
}
func sayHello() ->()
{
print("Hello")
}
func sayHello()
{
print("Hello")
}
// 这三种方式,都可以表达无返回值函数
4、返回元组,实现多返回值
func calculate(v1:Int,v2:Int)->(sum:Int,difference:Int,average:Int)
{
let sum = v1+v2;
return (sum,v1-v2,sum >> 1)
}
calculate(v1: 20, v2: 10)
// return值为:30,10,15
// >> 右移运算符,右移一个位置,值减半
// << 左移运算符,左移一个位置,值翻倍
2、函数的注释
/// 求和
///
/// 将2个数据相加,取和
///
/// - Parameter v1: 第一个参数
/// - Parameter v2: 第二个参数
/// - Returns 两数之和
///
/// - Note 传入两个整数
func sumfunc(v1:Int,v2:Int)->Int
{
return v1+v2
}
sumfunc(v1: 10, v2: 20)
选中函数,按住option
,就可以查看注释
3、参数标签、默认参数值
1、为了提高代码的可读性,Swift提供参数标签
func gotowork(at time:String)
{
print("go to work at\(time)")
}
gotowork(at:"09:00")
可以使用_(下划线)省略参数标签
func sum(_ v1:Int, _ v2:Int)->Int
{
v1 + v2
}
sum(10,20)
func sum1(_ v1:Int, _ v2:Int,v3:Int)->Int
{
v1 + v2 + v3
}
sum1(10,20,v3:20)
注意⚠️:
如果所有形参都使用省略标签,那么在函数调用时,需要给所有形参
赋值;如果只赋值1个值,系统将无法识别这个值要赋值给哪一个参数;
2、可以给参数设置默认值
func job(job:String="teacher")
{
print("my job is \(job)")
}
job()
func sum(_ v1:Int=0, _ v2:Int=0,v3:Int)->Int
{
v1 + v2 + v3
}
sum(10,v3:20)
如果其他参数有默认值,那么就是可选参数,可以给他传递值,也可以不传递,
比如上面的v2,我们可以选择不传入,那么它就会使用默认值;
4、可变参数
注意⚠️:
1、一个函数最多只能有一个可变参数;
2、紧跟在可变参数后面的参数,不能使用_(下划线)省略参数标签
;
func test(_ numbers:Int... , name:String, _ other:String)
{}
test(1,2,3,name:"QLY","笑笑")
//传入一个Int类型的可变参数numbers
5、输入输出参数 inout
inout
定义一个输入输出参数,可以在函数内部修改外部实参的值;
var value = 50
print(value) // 此时value值为50`
func increment(_ value: inout Int, _ length: Int = 10) {
value += length
}
increment(&value)
print(value) // 此时 value 值为60,成功改变了函数外部变量 value 的值`
注意⚠️:
1、可变参数不能标记为inout
;
2、inout
参数不能有默认值;
3、inout
参数只能传入可以被多次赋值的,也就是只能传入var(变量)类型的,不能传入let(常量)类型;
4、inout
参数传递的本质是地址传递,属于引用传递
,也就是引用类型
;\
6、函数重载
可以定义多个名字相同的函数,但要求:
1、函数名相同;
2、参数个数不同 || 参数类型不同 || 参数标签不同;
例子:
func sum(v1:Int, v2:Int) -> Int{}
func sum(v1:Int, v2:Double) -> Int{} //参数类型不同
func sum(v1:Int,v2:Int,v3:Int) -> Int{}//参数个数不同
func sum(_ v1:Int,_ v2:Int) -> Int{}//参数标签不同
注意:⚠️
1、函数重载与返回值无关;
2、默认参数与函数重载一起使用容易产生二义性;如下图
3、可变参数、省略参数标签和函数重载一起使用,容易产生二义性,编译
有可能
报错
7、函数类型
函数类型由形式参数类型
和返回值类型
组成;
func test() {
}// 函数类型:()->Void 或者 () -> ()
func sum(v1:Int, v2:Int) -> Int
{
v1+v2
}//函数类型:(Int,Int)-> Int
函数类型作为变量类型
func sum(v1:Int, v2:Int) -> Int
{//函数类型:(Int,Int)-> Int
v1+v2
}
var fn:(Int,Int)->Int = sum(v1:v2:)
fn(1,2)//结果为:3
函数类型作为函数参数
func sum(v1:Int, v2:Int) -> Int
{//函数类型:(Int,Int)-> Int
v1+v2
}
func result(_ sum:(Int,Int)->Int,v1:Int,v2:Int)->Int
{
return sum(v2, v1)
}
result(sum(v1: v2:), v1: 20,v2:30)
8、高阶函数
返回值是函数类型的函数,叫做高阶函数
func fnext(_ input:Int)->Int{
input+1
}
func fprevious(_ input:Int)->Int
{
input-1
}
func ff(_ bol:Bool)->(Int) -> Int
{
bol ? fnext(_:):fprevious(_:)
}
ff(true)(3)
ff(false)(5)
嵌套函数 将函数定义在函数内部
func ff(_ bol:Bool)->(Int) -> Int
{
func fnext(_ input:Int)->Int{
input+1
}
func fprevious(_ input:Int)->Int
{
input-1
}
return bol ? fnext(_:):fprevious(_:)
}
ff(true)(3)
ff(false)(5)
枚举
1、枚举的基本用法
1、常规枚举:
enum Passwork{
case number
case other
}
enum Passwork{
case number, other
}
使用:
var pass = Passwork.number
2、关联值枚举
enum Passwork{
case number(Int,Int,Int,Int)
case other
}
使用:
var pass1 = Passwork.number(9,8,7,6)
var pass2 = Passwork.number(19,82,17,36)
3、原始值枚举
枚举成员可以使用相同类型
的默认值预先设值,这个默认值就叫:原始值
注意⚠️:原始值,不占用枚举变量
的内存(往下有解释)
enum Section:Int{//隐式原始值枚举
case number1 = 1
case number2 = 2
case number3 = 3
case number4 = 4
}
var sec = Section.number1
print(sec) // 打印结果为:1
4、隐式原始值枚举
如果枚举原始值为Int、String类型,Swift会自动分配
原始值
enum Section:Int{//隐式原始值枚举
case number1,number2,number3,number4
}
//原始值分别为:0、1、2、3
2、MemoryLayout
可以使用MemoryLayout
来获取数据类型占用的内存大小;
例子:
enum Passwork{
case number(Int,Int,Int,Int)
case other
}
var pass1 = Passwork.number(9,8,7,6)
var pass2 = Passwork.number(19,28,17,21)
//实际占用多少内存
MemoryLayout<Passwork>.size //结果为:33
//分配多少内存
MemoryLayout<Passwork>.stride //结果为:40
//内存对齐方式
MemoryLayout<Passwork>.alignment //结果为:8
enum Section:Int {
case number1,number2,number3,number4
}
var section1 = Section.number1
//实际占用多少内存
MemoryLayout<Section>.size //结果为:1
//分配多少内存
MemoryLayout<Section>.stride //结果为:1
//内存对齐方式
MemoryLayout<Section>.alignment //结果为:1
Psaawork
枚举是关联枚举,它的关联值是由外部传入,可以传入不同的值进去, 所以每一个枚举变量,都需要有自己的内存去存储这些值;
所以比如说pass1、pass2 这些变量,需有自己的内存去存储它对应的关联值;
所以关联值枚举的特点就是:将传递进来的关联值,直接存储到枚举变量内存里;所以枚举变量的大小,与关联值大小有关;
Section是隐式原始值枚举,它的值是固定的,值与枚举成员是绑定在一起的; 但它的值,并不会存储到枚举变量里(比如说section1这个变量);
如果像关联枚举那样存储到变量里,那么就会造成内存浪费;
举例子:
var section1 = Section.number1 // 值=1,Int类型 占 8字节
var section2 = Section.number1 // 值=1,Int类型 占 8字节
var section2 = Section.number1 // 值=1,Int类型 占 8字节
那么section1、section2、section3,取同一个number1,就占用了 24字节,但是 number1 的值都是固定的,不会由外界修改,却占用24字节,不是很浪费吗?
所以编译器会针对这种类型的枚举做优化,它会给number1、number2、number3、 做一个标记,比如说:
number1 标记为 1;
number2 标记为 2;
number3 标记为 3;
所以number1 里存储的值是1,number2 里存储的值是2,number3 里存储的值是3;
如此section1、section2、section3、这三个枚举变量,内存各自占用 1 个字节;
如果拿到他们的值, 直接通过 section1.rawValue, 就会返回Int 8 字节的数据;
所以,原始值枚举,是不会将值存储到枚举变量里的;
枚举内存探索
定义一个关联枚举,使用MemoryLayout读取实际占用内存、系统分配内存、内存对其方式;
通过枚举地址,查看每一次关联枚举后其占用大小;
例子:
enum TestEnum{
case test1(Int,Int,Int)
case test2(Int,Int)
case test3(Int)
case test4(Bool)
case test5
}
print(MemoryLayout<TestEnum>.size); // 实际占用大小:25
print(MemoryLayout<TestEnum>.stride);//系统分配大小:32
print(MemoryLayout<TestEnum>.alignment);//字节对齐方式:8
var t = TestEnum.test1(1, 2, 3)
print(Mems.ptr(ofVal: &t))
t = TestEnum.test2(4, 5)
t = TestEnum.test3(6)
t = TestEnum.test4(true)
结果:
var t = TestEnum.test1(1, 2, 3)
内存:
// 01 00 00 00 00 00 00 00 //可以看出,01 对应存储的数据,就是1;
// 02 00 00 00 00 00 00 00 //02 对应存储的数据,就是2;
// 03 00 00 00 00 00 00 00 //03 对应存储的数据,就是3;
// 00 00 00 00 00 00 00 00
//那么这里?都是0,存储什么呢?如果不存储,为什么要开启8字节?
//不要说因为8字节内存对齐哦,因为上面存储到3,正好已经24字节了,满足8字节对齐
// 那么为什么这里还要继续开启8字节??? 我们后续讲解
t = TestEnum.test2(4, 5)
内存:
//04 00 00 00 00 00 00 00 //04 对应存储的数据,就是4;
//05 00 00 00 00 00 00 00 //05 对应存储的数据,就是5;
//00 00 00 00 00 00 00 00 //空
//01 00 00 00 00 00 00 00 //01 对应什么???为什么会出现01;我们后续讲解
t = TestEnum.test3(6)
内存:
//06 00 00 00 00 00 00 00 //06 对应存储的数据,就是6;
//00 00 00 00 00 00 00 00 //空
//00 00 00 00 00 00 00 00 //空
//02 00 00 00 00 00 00 00 //02 对应什么???为什么会出现02;我们后续讲解
t = TestEnum.test4(true)
内存:
//01 00 00 00 00 00 00 00 //01 对应布尔值true存储的数据
//00 00 00 00 00 00 00 00 //空
//00 00 00 00 00 00 00 00 //空
//03 00 00 00 00 00 00 00 //03 对应什么???
通过 MemoryLayout<TestEnum>.size,得出枚举实际占用25字节,
但是我们看到,传递进入到数据,最大也就是3字数字,也就24字节,那么多一个字节怎么来?
其实,每一个关联值枚举内存里,都有一个case 下标的内存标识:
第一个为:00;对应的下标为:0
第二个为:01;对应的下标为:1
第三个为:02;对应的下标为:2
第四个为:03;对应的下标为:3
如此用来区别不同的case;
这也就是为什么,占用25个字节;
最后一个字节,存储的是case的下标;
这也是为什么原始值枚举,占用内存只有 1 的原因。 这里的内存,只是case下标的内存而已
知识补充
1、实参、形参
实参:在传入函数之前已有明确定义的参数;
形参:参数只在此函数内有效并可使用,函数外不需要有明确定义;
2、值类型、引用类型
在Swift中,有两种参数传递方式:值类型
、引用类型
值类型:传递的是参数的一个副本,这样在调用参数的过程中不会影响原始数据。
引用类型:把参数本身引用(内存地址)传递过去,在调用的过程会影响原始数据。