8 函数与方法
[TOC]
函数与方法
Go中同时存在函数和方法
函数定义 func fun(){}
方法定义 func (obj object) fun(){}
函数
Go中函数是一种类型,函数类型变量可以像其它类型变量一样使用,可以作为其它函数的参数或返回值,也可以直接调用。
函数名不可重复。
函数支持多值返回、可变参数、闭包。
函数由 func关键字、函数名、形参、返回值类表、函数体组成。
无形参、返回值函数
func funName() {
fmt.Println("函数体")
}
多形参、多返回值函数
多同类型形参可以缩略类型声明
可变参数,可变参数要放在形参列表最后,可变形参在函数中是一个切片。
函数形参,值传递和引用传递
值传递
基本数据类型、string、数组、结构体都是形参拷贝。形参拷贝是低效的,尤其是数组传递会拷贝整个数组。 引用传递
指针、slice、map、chan是引用传递,即形参为引用时是指向内存空间的指针。
函数签名
函数签名或称函数类型,一个函数类型就是 func(所有形参的类型)(所有返回值类型)
通过函数名调用,函数是数据类型就可以赋值给别的变量再通过变量调用函数
函数可以作为形参或返回值
函数返回值取名
匿名函数
匿名函数可以看做是函数字面量,所有直接使用函数变量的地方都可以直接使用匿名函数替代,匿名函数可以赋值给变量,可作为实参、返回值或直接调用。
匿名函数赋值
匿名函数作为实参、返回值
匿名函数直接调用
defer关键字 配合匿名函数 延迟调用
defer关键字用于注册延迟调用,在函数返回前调用,调用顺序是先进后出的(FILO)。
defer语句常用于资源释放、资源回收、异常捕获。
defer 的执行顺序,先进后出
defer 注册的函数形参在注册时就会进行值拷贝
在os.Exit(int)下退出进程 defer 不会被执行
闭包
闭包最初的目的是减少全局变量,在函数调用的过程中隐式地传递共享变量。这种隐式地共享变量的方式是不利于代码可读性的,除非很有必要使用闭包,否则一般不使用。
对象是附有行为的数据,闭包是附有数据的行为。类在定义时已经显式地集中定义了行为,闭包中的数据没有显式地集中声明的地方,这种数据和行为耦合的模型不是一种推荐的模型,闭包仅仅是锦上添花的东西,不是不可缺少的。
方法
Go中有绑定到数据类型上的就是方法
作用在指定的数据类型上,就是和指定数据类型绑定,因此自定义数据类型都可以有方法,不仅仅是struct。
方法的调用传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的实例当做传实参也传递给方法。
方法和函数调用方式不同,方法调用时,调用者可以是指针也可以是具体值,但是这是编译器优化的结果,最终调用变量在方法中是什么取决于绑定的是指针还是具体变量。
函数与方法
在一个包中,函数名与方法名可以重名,方法名绑定在同一个自定义类型下是不可以重复的,但是在不同类型绑定同名方法是可以重复的。
函数栈
栈在不同的场景具有不同的含义,有时候指一种先入后出的数据结构,有时候指操作系统组织内存的形式。在大多数现代计算机系统中,每个线程都有一个被称为栈的内存区域,其遵循一种后进先出(LIFO,Last In First Out)的形式,增长方向为从高地址到低地址。
当函数执行时,函数的参数、返回地址、局部变量会被压入栈中,当函数退出时,这些数据会被回收。当函数还没退出就调用另一个函数时,形成了一条函数调用链。
每个函数在执行过程中都使用一块内存来保存返回地址、局部变量、函数参数等,我们将这一块区域被称为函数的帧栈(stack frame)。
当发生函数调用时,因为调用函数没有执行完毕,其栈内存中保存的数据还有用,所以被调用函数不能覆盖调用函数的栈帧,只能把被调用函数的栈帧压栈,等被调用函数执行完毕后再让栈帧出栈。这样,栈的大小就会随着函数调用层级的增加而扩大,随函数的返回而缩小,也就是说,函数的调用层级越深,消耗的栈空间越大。
因为数据是以后进先出的方式增加和删除的,所以基于堆栈的内存分配非常简单,并且通常比基于堆的动态内存分配快得多。另外,当函数退出时,堆栈上的内存会自动高效地回收,这是垃圾回收最初的形式。维护和管理函数的栈帧非常重要,对于编程语言来说,栈帧通常是隐藏的。如Go语言借助编译器,在开发中不用关心局部变量在栈中的布局与释放。
Go语言栈帧结构
高级语言为开发者隐藏了函数调用的细节,所以分析栈结构需要用一些特殊的手段,可以通过调试器或者打印汇编代码的方式进行分析。
在调试时关闭编译器的优化以及函数内联,使用命令:
go tool compile -S -N -l stackDemo.go
Last updated