[TOC]
0x00 Go语言基础之指针 描述: Go 语言中的指针区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算
是安全指针
。
Go 语言中三个重要概念: 指针地址
、指针类型
以及指针取值
。
简单回顾: 任何程序数据载入内存后,在内存都有他们的地址这就是指针。而为了保存一个数据在内存中的地址,我们就需要指针变量。
比如,“永远不要高估自己”这句话是我的座右铭,我想把它写入程序中,程序一启动这句话是要加载到内存(假设内存地址0x123456),我在程序中把这段话赋值给变量A,把内存地址赋值给变量B。这时候变量B就是一个指针变量
, 通过变量A和变量B都能找到我的座右铭。
Go语言中的指针操作非常简单,我们只需要记住两个符号:&(取地址)
和 *(根据地址取值)
。
1.指针地址 描述: 每个变量在运行时都拥有一个地址,该地址代表变量在内存中的位置。
Go语言中使用&字符放在变量前面
对变量进行“取地址”
操作。
取变量指针(地址)的语法如下:
2.指针类型 描述: Go语言中的值类型(int、float、bool、string、array、struct)
都有对应的指针类型,如:*int、*int64、*string
等。
简单示例:1 2 3 4 5 6 7 func main () { a := 10 b := &a fmt.Printf("a:%d ptr:%p\n" , a, &a) fmt.Printf("*b:%d ptr:%p type:%T\n" ,*b, b, b) fmt.Printf("&b ptr:%p " ,&b) }
为了更好的理解指针地址,我们来看一下b := &a
的图示:
3.指针取值 描述: 在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用*操作,也就是指针取值,代码如下。
1 2 3 4 5 6 7 8 9 func main () { a := 10 b := &a fmt.Printf("type of b:%T\n" , b) c := *b fmt.Printf("type of c:%T\n" , c) fmt.Printf("value of c:%v\n" , c) }
输出结果:1 2 3 type of b:*inttype of c:intvalue of c:10
4.指针特性 描述: 通过上面的指标变量、类型、取值
的学习,我们了解到取地址操作符&
和取值操作符*
是一对互补操作符,其中&
取出地址,*
根据地址取出地址指向的值。
Tips : 变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
1.对变量进行取地址(&)操作,可以获得这个变量的指针变量(指针地址
)。
2.对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
例如:我们可以在局部函数中修改全局变量的值采用指针传值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func modify1 (x int ) { x = 100 } func modify2 (x *int ) { *x = 100 } func main () { a := 10 modify1(a) fmt.Println(a) modify2(&a) fmt.Println(a) }
5.内存地址分配 描述: 在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间
,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间
,是因为它们在声明的时候已经默认分配好了内存空间。 Tips :Go语言中new和make是内建的两个函数,他主要用来分配内存。
例如:执行下述例子中的代码会引发panic错误1 2 3 4 5 6 7 8 9 10 11 12 func main () { var a *int *a = 100 fmt.Println(*a) var b map [string ]int b["沙河娜扎" ] = 100 fmt.Println(b) }
New 函数 描述: new是Go语言的一置的函数它的函数签名如下:
其中,
Type
表示类型,new 函数只接受一个参数,这个参数是一个类型
*Type
表示类型指针,new 函数返回一个指向该类型内存地址的指针。
Tips :New 函数不太常用但由它可以得到一个类型的指针,并且该指针对应的值应该为该类型的零值。
1 2 3 4 5 6 7 8 9 func main () { a := new (int ) b := new (bool ) fmt.Printf("%T\n" , a) fmt.Printf("%T\n" , b) fmt.Println(*a) fmt.Println(*b) }
Tips : 指针作为引用类型需要初始化后才会拥有内存空间才可以给它赋值,所以需要按照下述方式使用内置的new函数对a进行初始化之后就可正常对其赋值了。
1 2 3 4 5 6 func main () { var a *int a = new (int ) *a = 10 fmt.Println(*a) }
make 函数 描述: make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。
函数签名如下:1 func make (t Type, size ...IntegerType) Type
Tips : Type 主要是 slice、map 以及channel类型,并且必须使用make进行初始化后,才能对它进行操作。
例如:1 2 3 4 5 6 7 8 9 func main () { var b map [string ]int b = make (map [string ]int , 10 ) b["WeiyiGeek" ] = 100 fmt.Println(b) }
总结:new 函数与 make函数的区别
二者都是用来做内存分配的。
make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
示例演示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 func normal (x int ) { x = 65535 fmt.Printf("Func Param &x ptr : %p \n" , &x) } func pointer (x *int ) { *x = 65535 fmt.Printf("Func Param x ptr : %p \n" , x) } func demo1 () { a := 1024 b := &a fmt.Printf("a : %d , a ptr: %p, b ptr : %v , *b = %d \n" , a, &a, b, *b) fmt.Printf("b type: %T, &b ptr : %p \n" , b, &b) fmt.Println() *b = 2048 fmt.Printf("Change -> a : %d , a ptr: %p, b ptr : %v , *b = %d \n" , a, &a, b, *b) fmt.Printf("b type: %T, &b ptr : %p \n\n" , b, &b) c := 4096 normal(c) fmt.Println("After Normal Function c : " , c) pointer(&c) fmt.Printf("After Pointer Function c : %v, c ptr: %p \n\n" , c, &c) var a4 *int fmt.Println("a4 ptr : " , a4) d := new (int ) fmt.Printf("%T ,%p, %v \n" , d, d, *d) *d = 8192 fmt.Printf("%T ,%p, %v \n\n" , d, d, *d) var b5 map [string ]string fmt.Printf("%T , %p , %v\n" , b5, &b5, *&b5) b5 = make (map [string ]string , 10 ) b5["Name" ] = "WeiyGeek" b5["Address" ] = "ChongQIng China" fmt.Printf("%T , %p , %v\n\n" , b5, &b5, b5) }
执行结果:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 a : 1024 , a ptr: 0xc00001a0d 8, b ptr : 0xc00001a0d 8 , *b = 1024 b type : *int , &b ptr : 0xc00000e028 Change -> a : 2048 , a ptr: 0xc00001a0d 8, b ptr : 0xc00001a0d 8 , *b = 2048 b type : *int , &b ptr : 0xc00000e028 Func Param &x ptr : 0xc00013a048 After Normal Function c : 4096 Func Param x ptr : 0xc00013a040 After Pointer Function c : 65535 , c ptr: 0xc00013a040 a4 ptr : <nil > *int ,0xc00001a130 , 0 *int ,0xc00001a130 , 8192 map [string ]string , 0xc00000e038 , map []map [string ]string , 0xc00000e038 , map [Address:ChongQIng China Name:WeiyGeek]
0x01 Go语言基础之函数 描述: 其实在前面的示例中我们都已经接触到了函数,例如Go语言的内置函数或者是您自己编写的函数,在本章之中我们将详细讲解Go语言函数的使用。
Why , 为啥各个编程语言都要要引入函数?
答: 函数是组织好的、可重复使用的、用于执行指定任务的代码块。 通用得说即减少代码量、增强可读性、代码复用、提高开发效率、节约资源等特点。
1.函数定义 描述: Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于一等公民
。
Go语言中定义函数使用func关键字,具体格式如下:1 2 3 func 函数名(参数) (返回值) { 函数体 }
其中:
函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。注意在同一个包内函数名也称不能重名
(包的概念详见后文)。
参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
函数体:实现指定功能的代码块。
示例1 1 2 3 4 func sayHello () { fmt.Println("Hello World, Let's Go" ) }
2.函数调用 描述: 定义了函数之后,我们可以通过函数名()
的方式调用函数。
例如.我们调用上面定义的函数代码如下:1 2 3 4 5 func main () { fmt.Println("Start" ) sayHello() fmt.Println("End" ) }
3.函数参数 描述: 通常我们需要为函数传递参数进行相应的处理以达到我们最终需要的产物。
函数参数类型
固定参数 常规参数类型 针对固定函数的参数我们需要制定其类型,例如1 2 3 func intSum (x int , y int ) { fmt.Println("x + y =" ,x+y) }
参数类型简写 函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:1 2 3 func intSum (x , y int ) { fmt.Println("x + y =" ,x+y) }
Tips : 上面的代码中,intSum函数有两个参数,这两个参数的类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型。
可变参数 描述: 可变参数是指函数的参数数量不固定
。Go语言中的可变参数通过在参数名后加 ...
来标识(是否似曾相识,我们在数组那章节时使用过它,表示自动判断数组中元素个数进行初始化操作)。
示例1: 1 2 3 4 5 6 7 8 func intSum2 (x ...int ) int { fmt.Println(x) sum := 0 for _, v := range x { sum = sum + v } return sum }
调用上面的函数:1 2 3 4 5 ret1 := intSum2() ret2 := intSum2(10 ) ret3 := intSum2(10 , 20 ) ret4 := intSum2(10 , 20 , 30 ) fmt.Println(ret1, ret2, ret3, ret4)
注意:可变参数通常要作为函数的最后一个参数。
固定参数搭配可变参数使用时,可变参数要放在固定参数的后面,
示例2 1 2 3 4 5 6 7 8 func intSum3 (x int , y ...int ) int { fmt.Println(x, y) sum := x for _, v := range y { sum = sum + v } return sum }
调用上述函数:1 2 3 4 5 ret5 := intSum3(100 ) ret6 := intSum3(100 , 10 ) ret7 := intSum3(100 , 10 , 20 ) ret8 := intSum3(100 , 10 , 20 , 30 ) fmt.Println(ret5, ret6, ret7, ret8)
Tips : 本质上,函数的可变参数是通过切片来实现的
。
4.函数返回 描述: 与其他编程语言一样,Go语言中通过return
关键字向外输出返回值。
单返回值 描述: Go语言中常规函数返回值。
举个例子:1 2 3 func sum (x, y int ) (res int ) { return x + y }
多返回值 描述: Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值
包裹起来。
举个例子:1 2 3 4 5 func calc (x, y int ) (int , int ) { sum := x + y sub := x - y return sum, sub }
函数调用并接收返回值:
返回值命名 描述: 函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。
举个例子:1 2 3 4 5 func calc (x, y int ) (sum, sub int ) { sum = x + y sub = x - y return }
函数调用并接收返回值:
Tips :如果使用返回值命令时,只要其中一个返回值命名则另外一个返回值也必须命名。
返回值补充 描述: 当我们的一个函数返回值类型为slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片。1 2 3 4 5 6 func someFunc (x string ) []int { if x == "" { return nil } ... }
5.函数中变量作用域 描述: 变量作用域分为全局变量作用域
和局部变量作用域
以及代码块作用域
全局变量 描述: 全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效, 在函数中可以访问到全局变量。
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "fmt" var num int64 = 10 func testGlobalVar () { fmt.Printf("num=%d\n" , num) } func main () { testGlobalVar() fmt.Printf("num=%d\n" , num) }
局部变量 描述: 局部变量由分为两类一种是在函数内部定义的局部变量,另外一种则是在函数内部代码块中定义的局部变量
类1.在函数内定义的变量无法在该函数外使用。
例如: 下面的示例代码main函数中无法使用testLocalVar
函数中定义的变量x1 2 3 4 5 6 7 8 9 10 func testLocalVar () { var x int64 = 100 fmt.Printf("x=%d\n" , x) } func main () { testLocalVar() fmt.Println(x) }
类2.在函数内的语句块定义的变量 描述: 通常我们会在if条件判断、for循环、switch语句上
使用这种定义变量的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func testLocalVar2 (x, y int ) { fmt.Println(x, y) if x > 0 { z := 100 fmt.Println(z) } } func testLocalVar3 () { for i := 0 ; i < 10 ; i++ { fmt.Println(i) } }
Tips : 如果局部变量和全局变量重名,则优先访问局部变量。
Tips : 函数中查找变量的顺序步骤,(1) 现在函数内部查找,(2) 在函数上层或者外层查找, (3) 最后在全局中查找(此时如果找不到则会报错
)
6.函数类型与变量 定义函数类型 描述: 我们可以使用type关键字来定义一个函数类型,具体格式如下:type calculation func(int, int) int
上面语句定义了一个calculation类型,它是一种函数类型,这种函数接收两个int类型的参数并且返回一个int类型的返回值。
简单来说,凡是满足这个条件的函数都是calculation类型的函数,例如下面的add和sub
都是calculation类型
的函数。
示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 type calculation func (int , int ) int func add (x, y int ) int { return x + y } func sub (x, y int ) int { return x - y } var c calculationc = add fmt.Println(c(1 ,2 ))
函数类型变量 描述: 我们可以声明函数类型的变量并且为该变量赋值:1 2 3 4 5 6 7 8 9 10 func main () { var c calculation c = add fmt.Printf("type of c:%T\n" , c) fmt.Println(c(1 , 2 )) f := sub fmt.Printf("type of f:%T\n" , f) fmt.Println(f(30 , 20 )) }
7.高阶函数 描述: 高阶函数分为函数作为参数
和函数作为返回值
两部分。
函数作为参数 函数可以作为参数示例:
1 2 3 4 5 6 7 8 9 10 11 12 func add (x, y int ) int { return x + y } func calc (x, y int , op func (int , int ) int ) int { return op(x, y) } func main () { ret2 := calc(10 , 20 , add) fmt.Println(ret2) }
函数作为返回值 函数也可以作为返回值示例(此种方式非常值得学习
):1 2 3 4 5 6 7 8 9 10 11 func do (s string ) (func (int , int ) int , error ) { switch s { case "+" : return add, nil case "-" : return sub, nil default : err := errors.New("无法识别的操作符" ) return nil , err } }
8.函数补充 递归函数 Q: 什么是递归(Recursion)函数?
答: 递归,就是在运行的过程中函数调用自身。 但是值得注意的是我们在使用递归时,开发者需要设置退出条件,否则递归将陷入无限循环中
(一定一定要有退出条件)。
语法格式:1 2 3 4 5 6 7 func recursion () { recursion() } func main () { recursion() }
Tips : 递归函数对于解决数学上的问题是非常有用的,就像计算阶乘,生成斐波那契数列等。
示例1.n的阶乘计算 1 2 3 4 5 6 7 8 9 func factorial(n uint64) (ret uint64) { if n <= 1 { return 1 } return n * factorial(n-1) } func demo1 () { fmt.Println("5 的阶乘 : " , factorial(5)) }
执行结果:
示例2.利用递归求斐波那契数列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 func Fibonacci (count uint64 ) (ret uint64 ) { if count == 0 { return 0 } if count == 1 || count == 2 { return 1 } ret = Fibonacci(count-1 ) + Fibonacci(count-2 ) return } func demo3 () { count := 10 fmt.Printf("%v 个斐波那契数列:" , count) for i := 1 ; i < count; i++ { fmt.Printf("%v " , Fibonacci(uint64 (i))) } } func fib () func () int { a, b := 0 , 1 return func () int { a, b = b, a+b return a } } func main () { f := fib() fmt.Println(f(), f(), f(), f(), f()) }
执行结果:1 10 个斐波那契数列:1 1 2 3 5 8 13 21 34
匿名函数 描述: 函数当然还可以作为返回值,但是在Go语言中函数内部不能再像之前那样定义函数了,只能定义匿名函数。
Q: 什么是匿名函数?
答: 匿名函数就是没有函数名的函数,在很多编程语言中都有这样的特性。 匿名函数多用于实现回调函数和闭包。
Tips : 匿名函数因为没有函数名,所以没办法像普通函数那样调用,所以匿名函数需要保存到某个变量
或者作为立即执行函数
:
1 2 3 4 5 6 7 8 9 10 11 12 func main () { add := func (x, y int ) { fmt.Println(x + y) } add(10 , 20 ) func (x, y int ) { fmt.Println(x + y) }(10 , 20 ) }
闭包 描述: 闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+外遍变量的引用
, 例如在第三方包里只能传递一个不带参数的函数,此时我们可以通过闭包的方式创建一个带参数处理的流程,并返回一个不带参数的函数。
Tips : 非常注意引用的外部外部变量在其生命周期内都是存在的(即下次调用还能使用该变量值
)。
闭包基础示例1:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func adder () func (int ) int { var x int return func (y int ) int { x += y return x } } func main () { var f = adder() fmt.Println(f(10 )) fmt.Println(f(20 )) fmt.Println(f(30 )) f1 := adder() fmt.Println(f1(40 )) fmt.Println(f1(50 )) }
闭包基础示例2:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport ( "fmt" "math" ) func f1 (f func () ) { fmt.Printf("# This is f1 func , Param is f func() : %T \n" , f) f() } func f2 (x, y int ) { fmt.Printf("# This is f2 func , Param is x,y: %v %v\n" , x, y) fmt.Printf("x ^ y = %v \n" , math.Pow(float64 (x), float64 (y))) } func f3 (f func (int , int ) , x , y int ) func () { tmp := func () { f(x, y) } return tmp } func main () { ret := f3(f2, 2 , 10 ) f1(ret) }
执行结果:
Tips : 变量f是一个函数并且它引用了其外部作用域中的x变量,此时f就是一个闭包。并且在f的生命周期内,变量x也一直有效。
闭包进阶示例1:相比较于上面这种方式该种是将x变量放入函数参数之中,在进行函数调用时赋值。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func adder2 (x int ) func (int ) int { return func (y int ) int { x += y return x } } func main () { var f = adder2(10 ) fmt.Println(f(10 )) fmt.Println(f(20 )) fmt.Println(f(30 )) f1 := adder2(20 ) fmt.Println(f1(40 )) fmt.Println(f1(50 )) }
闭包进阶示例2:判断文件名称是否以指定的后缀结尾,是则返回原文件名称,否则返回文件名称+指定后缀的文件
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func makeSuffixFunc (suffix string ) func (string ) string { return func (name string ) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func main () { jpgFunc := makeSuffixFunc(".jpg" ) txtFunc := makeSuffixFunc(".txt" ) fmt.Println(jpgFunc("test" )) fmt.Println(txtFunc("test" )) }
闭包进阶示例3:该示例中函数同时返回add,sub
两个函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func calc (base int ) (func (int ) int , func (int ) int ) { add := func (i int ) int { base += i return base } sub := func (i int ) int { base -= i return base } return add, sub } func main () { f1, f2 := calc(10 ) fmt.Println(f1(1 ), f2(2 )) fmt.Println(f1(3 ), f2(4 )) fmt.Println(f1(5 ), f2(6 )) }
Important : 闭包其实并不复杂,只要牢记闭包=函数+引用环境。
defer 语句 描述: Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行(压栈-后进先出)
,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。
(1) defer 执行时机 描述: 在Go语言的函数中return语句在底层并不是原子操作
,它分为给返回值赋值
和RET指令
两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前
。
具体如下图所示:
weiyigeek.top-defer执行时机
(2) 简单例子
示例1: defer 延迟特性演示1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { fmt.Println("start" ) defer fmt.Println(1 ) defer fmt.Println(2 ) defer fmt.Println(3 ) fmt.Println("end" ) } start end 3 2 1
示例2.探究程序执行开始时间以及最后函数返回前的时间1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func funcTime () int { fmt.Println("函数开始时间: " , time.Now().Local()) var x = 0 defer fmt.Println("init x = " , x) for i := 0 ; i <= 100 ; i++ { x += i } defer fmt.Println("函数返回前时间: " , time.Now().Local()) defer fmt.Println("ret x = " , x) return x } 函数开始时间: 2021 -08 -15 18 :28 :58.37787611 +0800 CST ret x = 5050 函数返回前时间: 2021 -08 -15 18 :28 :58.377991344 +0800 CST init x = 0
Tips : 由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间
等。
(3) 经典面试案例 示例1:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 package mainimport "fmt" func f1 () int { x := 5 defer func () { x++ }() return x } func f2 () (x int ) { defer func () { x++ }() return 5 } func f3 () (y int ) { x := 5 defer func () { x++ }() return x } func f4 () (x int ) { defer func (x int ) { x++ }(x) return 5 } func f5 () (x int ) { defer func (x int ) int { x++ return x }(x) return 5 } func f6 () (x int ) { defer func (x *int ) { *x++ }(&x) return 5 } func f7 () (x int ) { defer func (x *int ) int { (*x)++ return *x }(&x) return 5 } func main () { fmt.Println("f1() = " , f1()) fmt.Println("f2() = " , f2()) fmt.Println("f3() = " , f3()) fmt.Println("f4() = " , f4()) fmt.Println("f5() = " , f5()) fmt.Println("f6() = " , f6()) fmt.Println("f7() = " , f7()) } f1() = 5 f2() = 6 f3() = 5 f4() = 5 f5() = 5 f6() = 6 f7() = 6
示例2.问下面代码的输出结果是?(提示:defer注册要延迟执行的函数时该函数所有的参数都需要确定其值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func calc (index string , a, b int ) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main () { x := 1 y := 2 defer calc("AA" , x, calc("A" , x, y)) x = 10 defer calc("BB" , x, calc("B" , x, y)) y = 20 } {"A" , 1 , 2 , 3 } {"B" , 10 , 2 , 12 } {"BB" , 10 , 12 , 22 } {"AA" , 1 , 3 , 4 }
Tips : 当遇到defer语句时其中的函数中调用的变量值是外部变量时,是离该defer语句最近的外部变量其赋予的值(存在于一个变量多次赋值的场景
)。
函数总结示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 package mainimport ( "errors" "fmt" "strings" "time" ) func f1 () { fmt.Println("Hello World, Let's Go" ) } func f2 (name string ) { fmt.Println("Hello" , name) } func f3 (i int , j int ) int { sum := i + j return sum } func f4 (x, y int ) (sum, sub int ) { sum = x + y sub = x - y return } func f5 (title string , value ...int ) string { return fmt.Sprintf("Title : %v , Value : %v \n" , title, value) } const PATH = "/home/weiyigeek" var author = "WeiyiGeek" func f6 () { fmt.Println("author:" , author, ",Home PATH:" , PATH) } func f7 (x, y int ) { localAuthor := "WeiyiGeek" fmt.Println("localAuthor = " , localAuthor, ",x = " , x, ",y = " , y) if x > 0 { z := 1024 fmt.Println(z) } for i := 0 ; i < 10 ; i++ { fmt.Print(i, " " ) } fmt.Println() } type calc func (int , int ) int func sum (x, y int ) int { return x + y } func sub (x, y int ) int { return x - y } func f8 () { var c calc c = sum fmt.Printf("type of c:%T , c(1,2) : %v \n" , c, c(1 , 2 )) d := sub fmt.Printf("type of d:%T , d(1,2) : %v \n" , d, d(1 , 2 )) } func mul (x, y int ) int { return x * y } func div (x, y int ) int { return x / y } func calculation (x, y int , op func (int , int ) int ) int { return op(x, y) } func ops (s string ) (func (int , int ) int , error ) { switch s { case "+" : return sum, nil case "-" : return sub, nil case "*" : return mul, nil case "/" : return div, nil default : err := errors.New("无法识别的操作符" ) return nil , err } } func f9 () { fmt.Printf("Type : %T , calculation (10 , 20, mul) = %v \n" , calculation(10 , 20 , mul), calculation(10 , 20 , mul)) value, _ := ops("/" ) fmt.Printf("Type : %T , ops('/') -> div(100,10) = %v \n\n" , value(100 , 10 ), value(100 , 10 )) } func f10 () { muls := func (x, y int ) int { fmt.Println("匿名函数1 之 x , y =" , x, y) return x * y } ret := muls(3 , 2 ) fmt.Println("匿名函数1 返回结果: " , ret) func (x, y int ) { fmt.Println("匿名函数2 之 x , y =" , x*y) }(3 , 2 ) } func adder1 () func (int ) int { var x int return func (y int ) int { x += y return x } } func adder2 (x int ) func (int ) int { return func (y int ) int { x += y return x } } func makeSuffixFunc (suffix string ) func (string ) string { return func (name string ) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func f11 () { var f = adder1() fmt.Printf("\n闭包 adder1: %v\n" , f(10 )) fmt.Println("闭包 adder1:" , f(20 )) fmt.Println("闭包 adder1:" , f(30 )) g := adder2(10 ) fmt.Printf("闭包 adder2: %v\n" , g(10 )) fmt.Println("闭包 adder2:" , g(20 )) fmt.Println("闭包 adder2:" , g(30 )) testJPG := makeSuffixFunc("jpg" ) fmt.Printf("闭包 makeSuffixFunc : file test = %v , file test.jpg = %v \n\n" , testJPG("test" ), testJPG("test.jpg" )) } func funcTime () int { fmt.Println("函数开始时间: " , time.Now().Local()) var x = 0 defer fmt.Println("init x = " , x) for i := 0 ; i <= 100 ; i++ { x += i } defer fmt.Println("函数返回前时间: " , time.Now().Local()) defer fmt.Println("ret x = " , x) return x } func f12 () { ret := funcTime() fmt.Println("defer 示例1: 1+2+3+....+99+100 =" , ret) } func main () { f1() f2("WeiyiGeek" ) fmt.Println(f3(1 , 1 )) x, y := f4(1 , 3 ) fmt.Printf("x = %d ,y = %d \n" , x, y) fmt.Println(f5("我是一串数字:" , 1 , 2 , 3 , 4 )) f6() f7(1 , 2 ) f8() f9() f10() f11() f12() }
执行结果:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Hello World, Let''s Go Hello WeiyiGeek 2 x = 4 ,y = -2 Title : 我是一串数字: , Value : [1 2 3 4] author: WeiyiGeek ,Home PATH: /home/weiyigeek localAuthor = WeiyiGeek ,x = 1 ,y = 2 1024 0 1 2 3 4 5 6 7 8 9 type of c:main.calc , c(1,2) : 3 type of d:func(int, int) int , d(1,2) : -1 Type : int , calculation (10 , 20, mul) = 200 Type : int , ops(' /') -> div(100,10) = 10 匿名函数1 之 x , y = 3 2 匿名函数1 返回结果: 6 匿名函数2 之 x , y = 6 闭包 adder1: 10 闭包 adder1: 30 闭包 adder1: 60 闭包 adder2: 20 闭包 adder2: 40 闭包 adder2: 70 闭包 makeSuffixFunc : file test = testjpg , file test.jpg = test.jpg 函数开始时间: 2021-08-15 19:35:19.159014152 +0800 CST ret x = 5050 函数返回前时间: 2021-08-15 19:35:19.159208306 +0800 CST init x = 0 defer 示例1: 1+2+3+....+99+100 = 5050