Administrator
发布于 2023-03-07 / 26 阅读
0
0

Golang--Functions

Declare Function & Call Function

Declare Function

A function declaration has four parts:

  • the keyword func,
  • the name of the function,
  • the input parameters,
  • the return type
package main

import "fmt"

func main() {
	result := div(11, 2)
	fmt.Println(result)    // 5

}

func div(num int, denominator int) int {
	if denominator == 0 {
		return 0
	}
	return num / denominator
}

Variadic Input Parameters and Slices

Go supports variadic parameters. The variadic parameter must be the last (or only) parameter in the input parameter list. You indicate it with three dots (…) before the type.

The variable that’s created within the function is a slice of the specified type. You use it just like any other slice.

package main

import "fmt"

func main() {
	fmt.Println(addTo(3))
	fmt.Println(addTo(3, 2))
	fmt.Println(addTo(3, 2, 4, 7, 9))
	a := []int{1, 3, 5, 6}
	fmt.Println(addTo(3, a...))

}

func addTo(base int, vals ...int) []int {
	outs := make([]int, 0, len(vals))
	for _, v := range vals {
		outs = append(outs, base+v)
	}
	return outs
}

输出

[]
[5]
[5 7 10 12]
[4 6 8 9]

Since the variadic parameter is converted to a slice, you can supply a slice as the input. However, you must put three dots (…) after the variable or slice literal.

Multiple Return Values

package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	result, remainder, err := divide(5, 2)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fmt.Println(result, remainder)

}

func divide(num int, denominator int) (int, int, error) {
	if denominator == 0 {
		return 0, 0, errors.New("cannot divide by zero")
	}
	return num / denominator, num % denominator, nil
}

输出

2 1

Named Return Values

When you supply names to your return values, what you are doing is pre-declaring variables that you use within the function to hold the return values.

They are written as a comma-separated list within parentheses. You must surround named return values with parentheses, even if there is only a single return value.

Named return values are created initialized to their zero values. This means that we can return them
before any explicit use or assignment.

package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	x, y, err := divide(5, 2)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fmt.Println(x, y)

}

func divide(num int, denominator int) (result int, remainder int, err error) {
	if denominator == 0 {
		err = errors.New("cannot divide by zero")
		return result, remainder, err
	}
	result = num / denominator
	remainder = num % denominator
	return result, remainder, err
}

输出

2 1

function instance & function type

我们普通写的一个function,其实就类似于一个实例---zhangsan,而我们定义的function type 就类似于Person类型。

那么,我们如何从写法上,来区分定义的是function type  还是function instance呢?
如果带有函数体,就是一个function instance
如果不带有函数体的,就是一个function type
type opFuncType func(int,int) int   // 不带有函数体,这是一个function type

func add(a int, b int) int { //带有函数体,这是一个function instance
    return a + b  
}

The type of a function is built out of the keyword func and the types of the parameters and return values.

This combination is called the signature of the function.

Any function that has the exact same number and type of parameters meets the type signature.


这里针对function instance,我们可以将function instance看做一个value,即可以将function instance分配给一个变量,如下:

package main

import "fmt"

func add(i int, j int) int {
	return i + j
}

func main() {

	opFunc := add // 这里的opFunc是一个变量
	res := opFunc(5, 3)
	fmt.Println(res) // 8
}

如果上面的代码,再结合function type,那么修改如下:

package main

import "fmt"

func add(i int, j int) int {
	return i + j
}

type opFuncType func(int, int) int

func main() {

	var opFunc opFuncType = add // 这里的opFunc是一个变量
	res := opFunc(5, 3)
	fmt.Println(res) // 8
}


下面,再结合map,体会下function instance 和 function type的使用场景

func add(i int, j int) int {
	return i + j
}

func sub(i int, j int) int {
	return i - j
}

func mul(i int, j int) int {
	return i * j
}

func div(i int, j int) int {
	return i / j
}

var opMap = map[string]func(int, int) int{
	"+": add,
	"-": sub,
	"*": mul,
	"/": div,
}

上面的代码,也可以,使用function type来表示,如下:

type opFuncType func(int, int) int

var opMap = map[string]opFuncType{
	"+": add,
	"-": sub,
	"*": mul,
	"/": div,
}

我们怎么理解function instance

func add(i int, j int) int {
	return i + j
}

在Java中,有类和实例化对象的概念,比如Person类 和 zhangsan实例化对象。

从面向对象的角度看,其实这里的add function,就是一个实例化对象。只不过这个对象,没有属性,且只有1个add方法。

这一点非常重要,我们需要牢记在心,并且形成这个思维习惯。这也是,为什么这里说这个add是function instance,instance这个单词不能丢了。



假如,我们需要计算i + j 两个变量的和,用java怎么实现呢?

我们必须先定义一个类,然后基于这个类,创建一个实例化对象,最后才能用这个对象,调用方法,传入i 和 j作为方法参数,最后计算出 和。

从这个角度看,发现,Java很啰嗦。

go就发现了这个问题,他说,我能不能直接创建一个实例化对象,调用这个对象的方法,直接计算出和,不想先定义一个类。

正是基于这个想法,于是function出现了。上面的add function就是一个实例化对象,直接调用函数,就计算出和了,不需要定义一个类。

因此,function instance就是一个实例化对象。

anonymous functions

These inner functions are anonymous functions; they don’t have a name. You don’t have to assign them to a variable, either. You can write them in-line and call them immediately.
package main

import "fmt"

func main() {
	for i := 0; i < 5; i++ {
		func(j int) {
			fmt.Println("print", j, "from inside of anonymous function")
		}(i)

	}

}

输出

print 0 from inside of anonymous function
print 1 from inside of anonymous function
print 2 from inside of anonymous function
print 3 from inside of anonymous function
print 4 from inside of anonymous function

Closures

什么叫做closure?

就是,A函数在使用()调用时,声明一个B函数。此时,B函数就可以访问 和 修改 A函数中变量。

下面,结合具体的例子看

Passing Functions as Parameters

package main

import (
	"fmt"
	"sort"
)

type Person struct {
	firstName string
	lastName  string
	age       int
}

func main() {
	people := []Person{
		{"zhang", "san", 15},
		{"li", "si", 12},
		{"wang", "er", 20},
	}

	fmt.Println(people)
	sort.Slice(people, func(i int, j int) bool {
		return people[i].age < people[j].age
	})
	fmt.Println(people)
}

输出

[{zhang san 15} {li si 12} {wang er 20}]
[{li si 12} {zhang san 15} {wang er 20}]

上面的例子中,sort.Slice函数,使用()调用时,声明了一个新的函数func(i int, j int) bool,并且将这个新函数,作为参数,传入到sort.Slice函数中,因此,新函数中,就能够访问people这个定义在sort.Slice函数中的变量,refer to people so we can sort it by the age field. In computer science
terms, people is captured by the closure.

Returning Functions from Functions

在讨论这个话题之前,我们先思考一个问题:因为内部函数 和 外部函数,是2个函数,假设外部函数调用完成了,外部函数中的变量被销毁了。此时,才调用内部函数,那么内部函数去访问外部函数中的变量,怎么办呢?

先说结论:内部函数在创建时,就会将,之后要使用的外部函数中的变量,copy一份。

Data isolation is a property that is available in the closure function. The state of the closures become unique when created. This makes each one of them having its own state. Here is an example

package main
 
import (
    "fmt"
)
 
func f() func() int {
    i := 0
    return func() int {
        i+=10
        return i
    }
}
 
func main() {
 
    a := f()
    b := f()
     
    fmt.Println(a())        // 10
    fmt.Println(b())        // 10
     
    a()  // call a again
     
    // the difference suggests that they contains different state
    // a had an extra call that made it 30 while b is still 20
     
    fmt.Println(a())        // 30
    fmt.Println(b())        // 20
     
}

In the code above, a and b have isolated states. That becomes apparent when we invoke a function call that results in a mutation of the state. This is what makes closures very useful.

**the important: it can access the values from which it was created,and if created twice,the value is not shared,they are isolated **

defer—release resources & closure

normally, a function call runs immediately, but defer delays the invocation until the surrounding function exits.

The code within defer closures runs after the return statement.

there’s a way for a deferred function to examine or modify the return values of its
surrounding function. 

There is, and it’s the best reason to use named return values.

Let’s look at a way to handle database transaction cleanup using named return values and defer:
func DoSomeInserts(ctx context.Context, db *sql.DB, value1, value2 string) (err error) {	
	tx, err := db.BeginTx(ctx, nil)	
	if err != nil {		
		return err	
	}	
    defer func() {		
        if err == nil {			
            err = tx.Commit()		
        }		
        if err != nil {			
            tx.Rollback()		
        }	
    }()	
    _, err = tx.ExecContext(ctx, "INSERT INTO FOO (val) values $1", value1)
    if err != nil {		
        return err	
    }	// use tx to do more database inserts here	
    return nil
}

A common pattern in Go is for a function that allocates a resource to also return a closure that cleans up the resource
func getFile(name string) (*os.File, func(), error) {	
    file, err := os.Open(name)	
    if err != nil {		
        return nil, nil, err	
    }	
    return file, func() {		
        file.Close()	
    }, err
}



f, closer, err := getFile(os.Args[1])
if err != nil {	
    log.Fatal(err)
}
defer closer()

需要注意的是,即使function出现了panic,defer 仍然会被调用。类似于Java中异常的finally。

call by value

It means that when you supply a variable for a parameter to a function, Go always makes a copy of the value of the variable.



This program leads to the question: why do maps and slices behave differently than other types? It’s because maps and slices are both implemented with pointers. 

Every type in Go is a value type. It’s just that sometimes the value is a pointer.

评论