函数
Golang函数特点
- 无需声明原型
- 支持多返回值
- 不定参数传参 也就是函数的参数个数不是固定的 但是后面的类型是固定的
- 支持命名返回参数
- 支持匿名函数和闭包
- 函数也是一种类型 可以赋值给一个变量
- 不支持 嵌套 一个包不能有2个名字一样的函数
- 不支持重载
- 不支持默认参数
函数声明
函数声明包含一个函数名,参数列表,返回值列表和函数体,如果函数没有返回值,则返回列表可以省略。函数从第一条语句开始执行,直到执行return语句或者执行函数的最后一条语句。
func test(str,x string,y int)(int,string){//相同的2个类型 可以省略type合并称一个//(int,string)返回参数}//函数是第一类对象 可以作为参数传递
- 参数 函数定义时指出,函数定义时有参数,该变量可被称为函数的形参,形参就像定义在函数的局部变量。但是当调用函数的时候,传递过来的变量就是函数的实参,函数可以通过2种方式传递参数
- 值传递:在 在调用函数的时候将实际参数复制一份传递到函数中,这样在函数中如果对参数修改,不会影响到实际参数
```go func swap(x ,y int)int{
}
> 2.引用传递: 引用传递是指在调用函数时把实际参数的地址传递到函数中,那么函数中对参数进行修改就是对实际参数修改,会影响到实际参数。```gofunc swap(x,y *int){//传入指针var temp int //创建一个临时变量temp =*x //x的地址的值赋值给temp临时变量*x=*y //y的地址的值给x*y=temp //temp的地址给到y}
在默认情况下Go语言使用的是值传递,也就是在调用过程中不会影响到实际参数
无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值得拷贝。引用传递是地址得拷贝,一般来说,地址拷贝更加高效。而值拷贝取决于对象得大小,对象越大 性能越低
map\slice\array\chan\指针\interface 默认都是以引用的方式传递
Golang可变参数本质上就是slice。只能有一个,且必须是最后一个。在参数赋值的时候不用一个个的赋值。可以直接传递一个数组或者切片,注意最后一个参数要加上args
...int
- 返回值 “_”标识符,用来忽略函数的某个返回值。Go的返回值可以被命名,并且像在函数开头声明的变量那样使用。返回值的名称应当具有一定的意义,可以作为文档使用。没有参数的return语句返回各个变量的当前值。这种用法被称作”裸返回”。直接返回语句仅应用在像下面这样的短函数中,在长的函数中它们会影响代码的可读性。
```go //直接返回sum func add(x, y int) (sum int) { sum = x + y return } func main() { sum := add(10, 11) fmt.Println(sum)//21 } //命名返回参数允许defer延迟调用通过闭包读取和修改 //直接返回sum
//直接返回sum func add(x, y int) (sum int) { defer func() { fmt.Println(“加100”) sum += 100 }() sum = x + y return } func main() { sum := add(10, 11) fmt.Println(sum) //加100 121 //显示return返回前,会先修改命名返回参数 }
<a name="0153bdbc"></a>#### 匿名函数> > 匿名函数是指不需要定义函数名的一种函数实现方式。在GO里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。匿名函数由一个不带函数声明和函数体组成。匿名函数的优越性可以直接使用函数内的变量不必声明。> ```gofunc main() {var sum = func() int {return 100 * 100> }fmt.Println(sum())//10000}//Golang匿名函数可赋值给变量,作为结构字段,或者在channel里传送func main() {//创建一个函数变量fun := func() {fmt.Println("111")}fun() //111//fun 数组 创建多个函数 函数的格式为接受一个参数返回一个int类型funs := [](func(x int) int){func(x int) int {return x + 1},func(x int) int {return x + 2},}fmt.Println(funs[0](100)) //101//作为结构体的一个fieldd := struct {fn func(x int) int}{ //创建一个函数变量fn: func(x int) int { //函数变量赋值return 1},}fmt.Println(d.fn(1)) //1//channelc := make(chan func() string, 2)c <- func() string {return "hello world"}fmt.Println((<-c)())//hello world}
闭包
闭包可以理解为一种保存函数状态的方法,当我们调用一个函数,或者执行操作,或者返回结果,函数运行结束之后,随即消亡,因为函数一般都是在堆上,当系统检测当前内存空间没有被引用就会回收
闭包的作用就是保存函数的运行状态 避免函数被回收。
- 访问所在的作用域
- 函数嵌套
- 在所在作用域外被调用
func bb() func(i int) int {var s int = 0fmt.Println("ccccc")fmt.Println("ccccc")b := func(i int) int {s = s + 1fmt.Printf("地址%#v", &s)return s}return b}func main() {a := bb()b := bb()fmt.Println(a(1))fmt.Println(a(1))fmt.Println(b(1))fmt.Println(b(1))}//ccccc//ccccc//ccccc//ccccc//地址(*int)(0xc00000a0a8)1//地址(*int)(0xc00000a0a8)2//地址(*int)(0xc00000a0c0)1//地址(*int)(0xc00000a0c0)2//可以看到a和b变量的调用 s被存在了不同的内存当中//当采用a调用的时候 s的变量被保存到了0xc00000a0a8当中//当采用b调用的时候 s变量被保存到了0xc00000a0c0 中 因为这是在调用bb()方法的时候 都申请了一块内存//把s定义成全局变量var s int = 0func bb() func(i int) int {b := func(i int) int {s = s + 1fmt.Printf("地址%#v", &s)return s}return b}func main() {a := bb()b := bb()fmt.Println(a(1))fmt.Println(a(1))fmt.Println(b(1))fmt.Println(b(1))}//ccccc//ccccc//ccccc//ccccc//地址(*int)(0x85dc68)1//地址(*int)(0x85dc68)2//地址(*int)(0x85dc68)3//地址(*int)(0x85dc68)4//可以看到当s提升为全局变量 申请的一块地址 用于a和b调用的时候 是共享的内存变量通过以上案例我们可以得出在a:=bb()的时候执行了bb函数,实际上这里的指向是直接指向的bb()函数的内部子函数。
当我们调用a()的时候 直接执行的也是内部的子函数。
通过局部变量和全局变量可以发现,申请的内存方式是不同的 ```
递归函数
递归就是在运行的过程中调用自己。一个函数调用自身叫做递归函数
构成递归函数得条件:
子问题必须与原始问题为同样的事,且更为简单
不能无限制调用必须有个出口,化简为分递归状态处理
func factorial(i int) int {if i <= 1 {return 1}return i * factorial(i-1)}func main() {fmt.Println(factorial(7))//5040}
延迟调用(defer)
```go defer特性: 1.关键字defer用于注册延迟调用
- 这些调用直到return前才会被执行。因此可以用来做资源清理 3.多个defer,按照先进后出的方式执行 4.defer语句中的变量,在defer声明时就决定了 defer用途: 1.关闭文件句柄 2.锁资源释放 3.数据库连接释放
func main() {
for i := 0; i < 10; i++ {//defer是先进后出的 defer 调用的函数参数的值 defer 被定义时就确定了.//defer fmt.Print(strconv.Itoa(i) + "\t") //9 8 7 6 5 4 3 2 1 0//defer 碰上闭包defer func() {fmt.Println(&i) //defer func内部所使用的变量的值需要在这个函数运行时才确定//也就是讲这在闭包用到的时候这个变量已经变成了10,所以输出全都是10}()}
}
<a name="2419eecb"></a>#### defer 与return>> ```go//defer 与returnfunc foo() (i int) {i = 0defer func() {fmt.Println(i)}()//在具名返回函数中,执行return 2的时候已经将i的值赋值为2了。所以输出的时候结果为2不是0return 2}func main() {foo()}
defer nil报错
```golang //defer nil函数报错
func test() { //声明的时候未被调用 var run func() = nil //被调用 报错 defer run() fmt.Println(“runs”) }
func main() { //捕捉异常 defer func() { if err := recover(); err != nil { fmt.Println(“错误日志”) fmt.Println(err) } }() //调用 runtime error: invalid memory address or nil pointer dereference //main从上到下开始执行。defer 延迟执行 //执行test方法。test方法调用的时候,出test的时候进行报错。 当test调用完成的时候 defer run才会被调用 test() }
<a name="840772d1"></a>#### 在错误的地方使用defer>> ```gofunc do() error {res, err := http.Get("http://www.google.com")defer res.Body.Close()if err != nil {return err}return nil}func main() {do()//panic: runtime error: invalid memory address or nil pointer dereference//因为google无法访问因为网络原因 defer 直接使用了res变量 res为nill未判断//可以在defer 的时候加上一层判断解决该错误if res!=nil{defer res.Body.Close()}}
不检查错误
```go //对于f.Close()可能会返回一个错误,但是这个错误会被我们忽略掉 func do() error { f, err := os.Open(“book.txt”) if err != nil { return err }
if f != nil {defer f.Close()}return nil
}
func main() { do() } // 改进 if f!=nill{ defer func(){ if err:=f.Close();err!=nill{ //code } } }
<a name="9094edd1"></a>#### 释放相同的资源>> ```gofunc do() error {f, err := os.Open("book.txt")if err != nil {return err}if f != nil {defer func() {if err := f.Close(); err != nil {fmt.Printf("defer close book.txt err %v\n", err)}}()}f, err = os.Open("another-book.txt")if err != nil {return err}if f != nil {defer func() {if err := f.Close(); err != nil {fmt.Printf("defer close another-book.txt err %v\n", err)}}()}return nil}func main() {do()//输出结果: defer close book.txt err close ./another-book.txt: file already closed//当延迟函数执行时候,只有最后一个变量会被用到,因此f会成为最后那个资源。而且2个资源都将这一个资源作为关闭对象}//解决方案//可以把f当作参数传递进去defer func(f io.Closer){}(f)
异常处理
Golang没有结构化异常,使用panic抛出异常,recover捕获错误。
异常的使用场景很简单:Go可以抛出一个panic异常,然后在defer中通过recover捕获这个异常。然后正常处理
panic:1.内置函数2.加入函数F中书写了panic语句 ,会终止其后要执行得代码,在panic所在函数F内如果存在要执行得defer函数列表。按照defer先进后出执行3.返回函数F得调用者G,在G中,调用函数F语句之后得代码不会执行,加入函数G在存在要执行得defer先进后厨执行4.直到goroutine整个退出,并报告错误recover:1.内置函数2.用来控制一个goroutine得panicking行为,捕获panic,从而影响应用得行为3.一般的调用建议1.在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行2.可以获取通过panic传递的error注:1.利用recover处理panic指令,defer必须放在panic之前定义,另外recover只有在defer调用的函数中才有效,否则当panic,receover无法捕捉到panic,无法防止panic扩散2.recover处理异常后,逻辑并不会恢复到panic那个点去,函数跑到defer之后的那个点3.多个defer会形成defer栈,后定义的defer语句最先被调用。延迟调用
```go //延迟调用中引发错误,可被后续异常调用捕获,但仅最后一个错误可被捕获 func test() { defer func() { fmt.Println(“最后调用”) fmt.Println(recover()) }()
defer func() {fmt.Println("第一次调用")panic("defer panic")}()panic("test panic")
}
func main() { test() } //捕获函数recover 只有在延迟调用内直接调用才会终止错误,否则总是返回nill。任何未捕获的错误都会沿调用堆栈向外传递 package main
import ( “fmt” )
func test() { defer func() { fmt.Println(recover()) //有效 }() defer recover() //无效! defer fmt.Println(recover()) //无效! defer func() { func() { println(“defer inner”) recover() //无效! }() }()
panic("test panic")
}
func main() { test() }
> Go实现类似try catch的异常处理> ```gofunc Try(fn func(), handler func(interface{})) {defer func() {if err := recover(); err != nil {handler(err)}}()fn()}func main() {Try(func() {fmt.Println("测试")}, func(err interface{}) {})}
