Contents
函数
函数就是将一堆代码进行重用的一种机制。函数就是一段代码,一个函数就像一个专门做这件事的人,我们调用它来做一些事情,它可能需要我们提供一些数据给它,它执行完成后可能会有一些执行结果给我们。要求的数据就叫参数,返回的执行结果就是返回值。
函数入栈出栈的过程
基本语法
func 函数名(){
函数体
}
- 例子
func main() {
var num,num1 int
for i := 0; i < 2 ; i++ {
fmt.Printf("输入第%d数字:\n",i)
fmt.Scanf("%d",&num)
if i == 1{
num1=num
}
}
add(num,num1)
}
不定参数列表
在定义函数的时候根据需求指定参数的个数和类型,但是有时候如果无法确定参数的个数呢?
- args后面跟了三个点,就是表示该参数可以接收0或多个整数值
-
args这个参数可以想象成是一个集合(类似数学中集合),可以存放多个值。
-
args有下角标如果用Python可以使用list来理解
例子一:
func add(args ...int) {
for i := 0; i <= len(args); i++ {
fmt.Printf("当前多少次:%d\n",i)
}
}
func main() {
add(1,2,3)
add(1,2,34,4,5,12,12,123,3,4532,234,234,234,234234,234,234,234)
例子二:
- range值管饱管够,args结束整个add函数结束
-
range会从集合中返回两个数,第一个是对应的坐标,赋值给了变量i,第二个就是对应的值,赋值了变量data
func add(args ...int) {
for i , data := range args {
fmt.Println("编号:",i)
fmt.Println("值:",data)
}
}
func main() {
add(1,2,3)
}
注意项
- 一定(只能)放在形参中的最后一个参数
-
在对函数进行调用时,固定参数必须传值,不定参数可以根据需要来决定是否要传值
-
func add(args …int) int{} //0个或者多个参数
-
func add(a int,args …int) int{} //一个或者多个参数
-
args -> 是一个slice,可以通过args[index]依次访问所有的参数通len(args)来判断传参的个数
实例二
- ( _ ),该下划线表示匿名变量,丢弃数据不进行处理,也就是任何赋予它的值都会被丢弃
函数嵌套调用
func add(args ...int) {
for i , data := range args {
fmt.Println("编号:",i)
fmt.Println("值:",data)
}
}
func Value(args ...int) {
add(args[2:] ...) //从编号2位置开始截取,编号0-2的不截取
}
func main() {
Value(1,2,3,4,5,6)
}
函数返回值
func add(args ...int) (sum int) { //定义返回值的名称和类型
for data := range args {
sum += data
}
return //如果不定义则需要reture xxx 给返回值
}
func main() {
sum:=add(1,2,3,4,5,6)
fmt.Println("值:",sum)
}
输出 -> 值: 15
返回多个值
func add(args ...int) (sum,sum1 int) { //定义返回值的名称和类型
for _,data := range args {
sum += data
sum1 = data
}
return //如果不定义则需要reture xxx 给返回值
}
func main() {
sum,sum1:=add(1,2)
fmt.Println("值:",sum,sum1)
}
输出 -> 值: 3 2
其他形式的函数
- 函数可以作为变量使用
依据Go 语言类型系统的概念,NewFuncType为新定义的函数命名类型,FuncLiteral为函数字面量类型,FuncLiteral为函数类型NewFuncType的底层类型。当然也可以使用type在一个函数类型中再定义一个新的函数类型,这种用法在语法上是允许的,但很少这么使用。
- 使用type Newrype oldType 语法定义一种新类型,这种类型都是命名类型,同理可以使用该方法定义一种新类型:函数命名类型,简称函数类型
func test() {
fmt.Println("不接受参数!")
}
func test1(a int,b int) int {
return a+b
}
type FUNCTYPE func() //函数的类型其实就是一个指针
type FUNKTYPE1 func(int,int) int // 定义这种类型的函数为FUNKTYPE,返回值类型为int
func main() {
var a FUNCTYPE //定义函数类型变量
a = test //赋值给a -> test 函数,如果不复制则提示内地址错误找不到指针
a() //调用函数打印变量
var b FUNKTYPE1
b = test1 //将一个函数赋值给一个变量
va := b(12,123)
fmt.Println(va)
}
函数作用域
局部变量
- 我们在main( )函数中定义变量a,在Test( )函数中也定义了变量a,但是两者之间互不影响,就是因为它们属于不同的函数,作用范围不一样,在内存中是两个存储区域。
全局变量
- 在函数外部的变量称为全局变量
-
既能在一个函数中使用,也能在其他的函数中使用,这样的变量就是全局变量. 也就是定义在函数外部的变量就是全局变量。全局变量在任何的地方都可以使用
总结
- (1)在函数外边定义的变量叫做全局变量。
-
(2)全局变量能够在所有的函数中进行访问
-
(3) 如果全局变量的名字和局部变量的名字相同,那么使用的是局部变量的
匿名函数和闭包
匿名函数
在一个函数中再定义一个函数,那么可以使用匿名函数,所谓匿名函数就是没有名字的函数
- 匿名函数不能独立存在,常作为函数参数、返回值,或者赋值给某个变量
-
匿名函数可以直接显式初始化
-
匿名函数的类型也是函数字面量类型func(int,int)int
func main() {
var num int = 9
f := func() {
num++
fmt.Println("匿名函数:",num)
}
type FUCK func()
//声明函数类型变量
var f1 FUCK
f1 = f
f1()
fmt.Println("main函数:",num )
}
匿名函数的调用方式
func main() {
var num int = 9
func() {
num++
fmt.Println("匿名函数:", num)
}()//括号直接调用匿名函数
}
匿名函数的参数和返回值
func main() {
var num int = 9
//定义函数(参数)返回值类型{}并且X,Y变量自动推到接受
x,y := func(a int,c string) (q int,w string) {
num++
fmt.Printf("参数一:%d\n参数二:%s\n",num,c)
q = num*2
w = c + "让我来帮助你"
return
}(num,"你好我是小娜")//直接执行函数并且传递两个参数值
fmt.Printf("返回值一:%d\n返回值二:%s",x,y)
}
闭包
闭包是由函数及其相关引用环境组合而成的实体,一般通过在匿名函数中引用外部函数的局部变量或包全局变量构成。
闭包=函数+引用环境
闭包对闭包外的环境引入是直接引用,编译器检测到闭包,会将闭包引用的外部变量分配到堆上。
如果函数返回的闭包引用了该函数的局部变量(参数或函数内部变量)
简而言之:所谓的闭包是指有权访问另一个函数作用域中的变量的函数,就是在一个函数内部创建另一个函数
也可以这样理解闭包:虽然不能在一个函数里直接声明另一个函数,但是可以在一个函数中声明一个函数类型的变量,此时的函数称为闭包(closure)
- (1)多次调用该函数,返回的多个闭包所引用的外部变量是多个副本,原因是每次调用函数都会为局部变量分配内存。
-
(2)用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每一次调用该闭包对该外部变量都有影响,因为闭包函数共享外部引用。
-
(3)使用闭包是为了减少全局变量,所以闭包引用全局变量不是好的编程方式
//可以通过匿名函数实现函数在栈区的本地化
func Test() func() int {//定义的Test函数返回值为一个函数
var x int
return func() int {
x++
return x
}
}
func main() {
f := Test()
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
fmt.Printf("返回值类型:%T",f)
}
闭包的价值
闭包最初的目的是减少全局变量,在函数调用的过程中隐式地传递共享变量,有其有用的一面;但是这种隐秘的共享变量的方式带来的坏处是不够直接,不够清晰,除非是非常有价值的地方,一般不建议使用闭包。
对象是附有行为的数据,而闭包是附有数据的行为,类在定义时已经显式地集中定义了行为,但是闭包中的数据没有显式地集中声明的地方,这种数据和行为耦合的模型不是一种推荐的编程模型,闭包仅仅是锦上添花的东西,不是不可缺少的。
延迟调用defer
defer关键字,可以注册多个延迟调用,这些调用以先进后出(FILO)的顺序在函数返回前被执行defer常用于保证一些资源最终一定能够得到回收和释放。
- defer的
应用场景
:文件操作,先打开文件,执行读写操作,最后关闭文件。为了保证文件的关闭能够正确执行,可以使用defer.
//创建操作文件的函数,保证有返回值和错误返回
func CopyFile(desName,srcNAME string) (written int64,err error){
src,err := os.Open(srcName)
if err != nil{
return
}
//保证即使出错也运行关闭文件,即延迟调用
defer src.Close()
des,err := os.Create(dstName)
if err != nil{
return
}
defer des.Close()
//正常则返回结果
return io.Copy(dst,src)
}
注意事项
- defer 后面必须是函数或方法的调用,不能是语句,否则会报expression in defer must be function cal1错误。
-
defer函数的实参在注册时通过值拷贝传递进去。实参的值在defer注册时通过值拷贝传递进去,后续语句实参++并不会影响 defer语句最后的输出结果。
func main() {
defer func() {
print("1\n")
}()
defer func() {
print("2\n")
}()
print("3\n")
}
输出值:3 2 1
- defer 语句必须先注册后才能执行,如果defer位于return之后,则defer因为没有注册,不会执行。
func main() {
defer func() {
println("1")
}()
a:=0
println(a)
return
defer func() {
println("2")//没有输出2
}()
}
输出的值:0 1
- 主动调用os.Exit(int)退出进程时,defer将不再被执行(即使defer已经提前注册)
func main() {
defer func() {
println("default")
}()
println("begin")
os.Exit(1)
}
输出值: begin
- defer语句的位置不当,有可能导致panic,一般defer语句放在错误检查语句之后。
defer也有明显的副作用:defer会推迟资源的释放,defer尽量不要放到循环语句里面,将大函数内部的defer语句单独拆分成一个小函数是一种很好的实践方式。另外,defer相对于普通的函数调用需要间接的数据结构的支持,相对于普通函数调用有一定的性能损耗。
func f1() (r int){
defer func() {
r++
}()
r=0
return
}
func main() {
i:= f1()
fmt.Println(i)
}
输出值:1
练习
func double(x int) int {
return x+x
}
func triple(x int) (r int) {
defer func() {
r += x
}()
return double(x)//先执行返回的这个函数x=6,后执行defer r+=x,最后返回r=9
}
func main() {
println(triple(3))
}
输出值: 9
递归函数
函数在内部不调用其它的函数,而是自己本身的话,这个函数就是递归函数能不使用尽量不要使用太消耗内存资源
func test(a int) {
if a ==0 {
return
}
a--
test(a)
fmt.Println(a)
}
func main() {
test(5)
}
输出值:0 1 2 3 4
- 算阶乘 n! = 1 * 2 * 3 * … * n
var sum int =1
func test(a int) {
if a == 1{
return
}
test(a-1)
sum*=a
}
func main() {
test(5)
println(sum)
}