美文网首页
Golang学习笔记-函数

Golang学习笔记-函数

作者: 土豆吞噬者 | 来源:发表于2019-09-27 19:01 被阅读0次

    Go函数定义格式如下:

    func function_name( [parameter list] ) [return_types] {
       函数体
    }
    

    例如:

    func add(n1, n2 int) int{
        return n1 + n2
    }
    

    Go可以很灵活的创建函数,并作为另外一个函数的实参:

    type getTowNumberResultFunc func(int,int) int
    
    func add(n1, n2 int) int{
        return n1+ n2
    }
    
    func printResult(n1, n2 int,f getTowNumberResultFunc){
        println(f(n1, n2))
    }
    
    func main() {
        printResult(10,20,add);
        myGetReslut := func(n1, n2 int) int {
            return n1 * n2
        }
        printResult(10,20,myGetReslut)
    
    }
    
    

    Go支持匿名函数,可作为闭包,匿名函数是一个"内联"语句或表达式,匿名函数的优点在于可以直接使用函数内的变量。

    type getTowNumberResultFunc func(int,int) int
    
    func getAddFunc() func(int,int) int{
        initValue:=20
        return func(n1, n2 int) int{
            return initValue+ n1 + n2
        }
    }
    
    func printResult(n1, n2 int,f getTowNumberResultFunc){
        println(f(n1, n2))
    }
    
    func main() {
        printResult(10,20,getAddFunc());
    
    }
    

    参数

    Go函数中只有值传递(比较特殊的是,Go语言闭包函数对外部变量是以引用的方式使用),参数传递时会复制对象,要么是值的拷贝,要么是指针的拷贝(slice,map,channel,函数,指针),如果参数类型是指针,则函数可以通过该参数影响原有对象。

    当参数为interface类型时,如果实参的实现有指针接受者方法时,参数只能用指针不能用值。

    type HeightSetter interface {
        setHeight1(height int)
        setHeight2(height int)
    }
    
    type Person struct{
        height int
    }
    
    
    func (person *Person) setHeight1(height int){
        person.height=height
    }
    
    func (person Person) setHeight2(height int){
        person.height=height
    }
    
    func zeroHeight(heightSetter HeightSetter){
        heightSetter.setHeight1(0)
        heightSetter.setHeight2(0)
    }
    
    func main() {
        person:=Person{100}
        //zeroHeight(person) Person does not implement HeightSetter (setHeight1 method has pointer receiver)
        zeroHeight(&person)//正确
    }
    
    

    可变参数

    Go函数还支持可变数量的参数,可变数量的参数必须是最后出现的参数,可变数量的参数其实是一个切片类型的参数。

    package main
    
    import "fmt"
    
    func printStrings(tag int,strs ...string){
        for _,str:=range  strs{
            fmt.Println(str)
        }
    }
    
    func printValues(tag int,values ...interface{}){
        fmt.Println(values...)
    }
    
    func main() {
        printStrings(1,"hello","world")
        printValues(1,"hello","world")
    }
    
    hello
    world
    hello world
    

    返回值

    Go函数可以返回多个值,这个时候返回值类型要用括号括起来,例如:

    func div(n1, n2 int) (int,int){
        return n1 / n2, n1 % n2
    }
    

    Go函数可以给返回值命名,这个时候即使只有一个返回值也要用括号括起来,例如:

    func add(n1, n2 int) (result int){
        result= n1 + n2
        return
    }
    
    func div(n1, n2 int) (result int,remainder int){
        result= n1 / n2
        remainder= n1 % n2
        return
    }
    

    defer

    关键字defer后面的语句会在函数退出之前调用,如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用。

    在defer表达式中可以通过修改命名返回值改变最终返回值,而以局部变量作为返回值时则不会影响,因为函数会先将局部变量赋值给返回值,然后调用defer表达式,这个时候在defer表达式修改局部变量也改变不了返回值。

    func testFunc1(n int) int{
        result:=n
        defer func() {
            result+=1
        }()
        return result
    }
    
    func testFunc2(n int) (result int){
        result=n
        defer func() {
            result+=1
        }()
        return
    }
    
    func main() {
        println(testFunc1(1))//1
        println(testFunc2(1))//2
    }
    

    recover用于捕捉panic抛出的异常信息,recover必须在defer函数中运行,defer函数必须定义在panic之前,直接defer调用recover无效。

    func main() {
        defer recover()
        panic(1)
    }
    

    下面代码中recover运行在普通函数中,所以也是无效的。

    func main() {
        defer func() {
            func() { recover() }()
        }()
        panic(1)
    }
    

    方法

    Go没有面向对象,但却可以给指定类型添加方法,格式如下:

    func (variable_name variable_data_type) function_name() [return_type]{
       /* 函数体*/
    }
    

    Go中的方法就是一个包含了接受者的函数,接受者可以是类型的一个值或是一个指针,类似于C++中的this。

    在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。

    如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。

    type Person struct{
        height int
    }
    
    func (person Person) printHeight(){
        println(person.height)
    }
    
    
    func (person *Person) setHeight1(height int){
        person.height=height
    }
    
    func (person Person) setHeight2(height int){
        person.height=height
    }
    
    func main() {
        person:=Person{100}
        person.setHeight1(200)
        person.printHeight()//200
        person.setHeight2(300)
        person.printHeight()//200
    }
    
    

    利用方法值和方法表达式可以将方法转换为普通函数。

    package main
    
    type Person struct{
        height int
    }
    
    func (person Person) printHeight(){
        println(person.height)
    }
    
    
    func main() {
        person:=Person{100}
    
        //方法表达式
        normalFunc1:=(*Person).printHeight//func printHeight(person *Person)
        normalFunc1(&person)
    
        //方法表达式
        normalFunc2:=(Person).printHeight//func printHeight(person Person)
        normalFunc2(person)
    
        //方法值
        normalFunc3:=person.printHeight
        normalFunc3()
    }
    

    Go语言函数的递归调用深度逻辑上没有限制,函数调用的栈是不会出现溢出错误的,因为Go语言运行时会根据需要动态地调整函数栈的大小。每个goroutine刚启动时只会分配很小的栈(4或8KB,具体依赖实现),根据需要动态调整栈的大小,栈最大可以达到GB级。

    在Go1.4以前,Go的动态栈采用的是分段式的动态栈,通俗地说就是采用一个链表来实现动态栈,每个链表的节点内存位置不会发生变化。但是链表实现的动态栈对某些导致跨越链表不同节点的热点调用的性能影响较大,因为相邻的链表节点它们在内存位置一般不是相邻的,这会增加CPU高速缓存命中失败的几率。

    为了解决热点调用的CPU缓存命中率问题,Go1.4之后改用连续的动态栈实现,也就是采用一个类似动态数组的结构来表示栈。不过连续动态栈也带来了新的问题:当连续栈动态增长时,需要将之前的数据移动到新的内存空间,这会导致之前栈中全部变量的地址发生变化。

    虽然Go语言运行时会自动更新引用了地址变化的栈变量的指针,但最重要的一点是要明白Go语言中指针不再是固定不变的了(因此不能随意将指针保持到数值变量中,Go语言的地址也不能随意保存到不在GC控制的环境中,因此使用CGO时不能在C语言中长期持有Go语言对象的地址)。

    相关文章

      网友评论

          本文标题:Golang学习笔记-函数

          本文链接:https://www.haomeiwen.com/subject/zdlfyctx.html