函数:
package 包的引入
- 包的本质就是不同的目录,每个文件必须属于一个包,也就是说 go 是以包来单位来进行组织和管理的。
- 建议是将包名和目录名保持一致,而文件名则是自定义,但是每个文件都要写明自己属于哪个包。
- 在一个包中引用其他包时,引用的路径是GO中的PATH路径下src 为相对路径的
- 一个包引入其他包时是名字是 包名.变量或函数 (). 首字母的大小写,决定了是私有还是公有的变量或函数。
- package 包名 ;用来打包 ; 而import "包名" 用来引用包
- 包可以取别名 ,但是一旦指定别名 就只能使用别名了
import时需要指明包的路径对应的目录,只是该目录才是包名,并非完整的路径是包名。
- 同一个包下不能有相同名称的函数和变量名,要保持全局唯一
- 如果你要编译成一个可执行文件,则该包名必须是main ,但是如果想定义为一个库,则名字是任意的。
函数的特点
- 基本数据类型都是在栈区,而引用类型一般是放在堆区,但是编译器存在一个逃逸分析,因此这不是绝对的。
- 在执行main 函数时,在栈区开辟一块空间属于main ,而当main 函数调用其他函数时,会在堆区在单独开辟一个test 函数区,
- 此两个区是逻辑隔离的。test 区中函数中的变量不会影响到 main的变量。当test 函数执行完成后,编译器会回收该区域的空间。
- return 是可以返回多个函数值的,这一点其他函数的没有的。当有多个返回值时,为了保证先后的顺序,可以类似形参的方式来定义返回值
func sum(a int, b int) (sum1 int, sub int) {
sum1 = a + b
sub = a - b
return
}
- 函数名调用函数名后面的小括号不能省略,哪怕是没有形参也不能省略
函数的递归
斐波拉契数 1 1 2 3 5 8
func f1(n int) int {
switch {
case n == 1 || n == 2:
return 1
case n < 1:
return 0
default: // 1 从未知到已知,必须从大到小,从右到左,n = n-1 +n-2
return f1(n-1) + f1(n-2)
}
- 已知f(10)=1 求f(1) , 从未知向已知,从左到右 f(n) 和f(n+1)的关系 则 f(10)=1 f(9)= (f(10)+1)*2 -> f(n)=(f(n+1)+1)*2
---
init 函数
- 每个源文件都可以包含一个init 函数,init 函数在main 函数之前执行,但是在全局变量之后执行
- import 引入其他包之后的 执行顺序: 其他包的 全局变量---init 函数 ----main包 全局变量---init 函数---main函数 - 其他函数的调用过程中 加载其他私有或公有函数
匿名函数 :没有名字的函数
- 有时我们需要仅仅执行一次的函数可以用匿名函数来实现。//注意除了 全局匿名函数外,其他都是在main 函数中来定义和申明另一个函数
- 匿名函数有三种表现形式 : 方式1 可以在main函数中 定义同时使用,(10,20) 就是其使用部分。
package main
import "fmt"
func main() {
a := func(n1 int, n2 int) int {
return n1 + n2
}(10, 20) //(n1,n2) 是调用部分,调用够将其返回值赋给一个变量a
fmt.Println("a = ", a)
- 方式2 main 函数中将其赋个一个变量来多次使用。对比方式1 只是少了调用部分 。此时该变量b 就是一个函数类型
package main
import "fmt"
func main() {
a := func(n1 int, n2 int) int {
return n1 + n2
}(10, 20) //(n1,n2) 是调用部分,调用够将其返回值赋给一个变量a
b := func(n1 int, n2 int) int {
return n1 + n2
} //对比a 只是少了调用部分 ,但是此时b类似函数的名字。调用时需要b(10,20)来调用
fmt.Println("a = ", a, "b = ", b(10, 20))
}
- 无论是方式1 还是方式2 都是在函数main 中进行的,正常情况下不允许在一个函数中定义另一个函数,但是匿名函数是例外
- 因此如果想在一个函数中定义另一个函数则可以使用匿名函数。
- 全局匿名函数 本质上就是定义一个全局变量 来将匿名函数赋予该变量。全局变量要求是公有的因此首字母必须是大写。
- 因为 在函数外不允许使用 a := 10 的方式来定义变量,因此只是使用 var a = 10 的方式来定义。同理 全局匿名函数也是如此。
- 全局匿名函数相对第二种方式只是1 用了一个全局变量 来接收,2 是在函数外 定义和赋值给全局变量的。其他都一模一样。
闭包 :一个函数和其引用的环境变量合成的一个整体就是闭包。
package main
import "fmt"
func Add() func(int) int { //此时的返回值是一个函数。上面的函数类型的定义。
var n2 int = 10 //函数体部分 第一部分是变量的赋值
return func(n1 int) int { //第二部分是 返回了一个函数的整体,还是一个匿名函数。
n2 += n1
return n2
}
}
func main() {
f1 := Add() // 函数的调用,必将返回一个完整的函数体。而在函数体中引用了一个变量,该变量和返回的函数整体构成了一个闭包
fmt.Println(f1(1)) // f1(1)是 10+1=11
fmt.Println(f1(2)) // 11 +2 =13
}
一次调用反复使用时,当多次使用时,并不会进行变量的初始化,只是在调用时执行一次,因此其值是累加的。
- 可以理解为 闭包是一个类 ,返回的函数是一个类的方法,而变量的定义是一个类的字段
- string(93) 是将数字转换为对应的ASSIC码 符号 ,字符串的值是可以修改的
var a string = "hello" //字符串的值是可以修改的
a += "1"
fmt.Println(a)
- 函数做为返回值来使用时,典型特点是只需要传递一次,因此使用了匿名函数,返回其他函数时会语法错误。
package main
import (
"fmt"
"strings"
)
//此时的闭包是一个后缀变量 一个放回的函数整体。函数作为返回值时,只能使用匿名函数
func f1(suffix string) func(string) string { //一次传递多次调用,只需要传递一次 suffix ->.jpg
return func(s2 string) string { // 就可以实现多次来判断是否 包含这个。如果是函数的方式来使用,需要传递
//两个参数,后缀和文件名,每次调用都需要传递两个参数,但是闭包只需要传递一次后缀名。
if !strings.HasSuffix(s2, suffix) {
s2 += suffix
}
return s2
}
}
func main() {
a1 := f1(".jpg")
fmt.Println(a1("22jpg"))
}
- 闭包的好处是只需要传递一次 后缀的参数,如果要用函数调用的方式则每次都需要传递后缀的参数。闭包可以保留上次引入的某个值,一次传入多次使用
defer函数
- 在函数的使用过程中,需要创建资源(如创建mysql 的连接、文件的句柄、锁等),在函数执行完成之后,需要及时的释放资源,此时通过defer 机制来释放资源。
- defer 语句在函数中时,在执行到defer 语句时会被压入到栈中,【先进后出 】待函数调用完成后,在将栈中语句执行,先进后出
- defer 就是函数调用完成之后执行的延迟语句。而本质上新建立一个栈将defer 语句压入栈中。
- defer 语句在压入栈中是将其变量的值 也拷贝进入了栈中,也就是说后面的变量,对栈中的值是没有改变的
func sum(num1 int, num2 int) int {
defer fmt.Println("step 1 ", num1) //先压入栈 ,此时的变量值 num1 =10 也被压入了栈中
defer fmt.Println("step 2 ", num2) //先压入栈 ,此时的值 num2 =20 也被压入了栈中。
num1++
num2++
sum := num1 + num2
fmt.Println("step 3 ", sum)
return sum
}
func main() {
a1 := sum(10, 20) //函数的调用,先执行3 ,2 ,1, 4
fmt.Println("step 4 ", a1) //last
}
- defer 的最佳实践就是 可以在资源打开的同时,defer 资源关闭 ,这样在函数调用完成后将从栈中自动关闭资源,而不用去关心什么时候回收资源
- 伪代码如下
值传递和引用类型传递
- 函数中的形参中的变量在传递的过程中 有两种 分别是值传递和引用类型传递
- 值传递的本质是 值的copy ,而引用类型的本质是地址的copy .当数据量比较大时,明显指针的效率要高
- 值传递的数据类型有 基本数据类型 数组 结构体三种 简称 基组结 ,而引用类型有 指针 -- 切片--映射 --管道--接口
函数的作用域
- 在函数的内定义的变量 无论首字母是否大写,都不能被其他函数所引用。只有在 函数外定义的变量称为全局变量,首字母小写可以在整个包中引用,首字母大写则可以跨包使用。
- if 和for 中定义的变量的作用域仅仅是在 if for 中才有效。他的作用域是代码块。