6. Functions in Go language

Function is the core design in Go. It is declared through the keyword func. Its format is as follows:

func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
    
    
    // 这里是处理逻辑代码
    // 返回多个值
    return value1, value2
}

From the above code we can see

  • The keyword func is used to declare a function funcName
  • Functions can have one or more parameters, each parameter is followed by a type, separated by ,
  • Functions can return multiple values
  • The above return value declares two variables, output1 and output2. If you don’t want to declare them, you can just use the two types.
  • If there is only one return value and no return value variable is declared, then you can omit the parentheses surrounding the return value
  • If there is no return value, then the final return information is simply omitted.
  • If there is a return value, a return statement must be added outside the function

Let's look at an example of a practical application function (used to calculate the Max value)

package main
import "fmt"
//  返回 a 、 b 中最大值 .
func max(a, b int) int {
    
    
    if a > b {
    
    
   		return a
    }
    return b
}
func main() {
    
    
    x := 3
    y := 4
    z := 5
    max_xy := max(x, y) // 调用函数 max(x, y)
    max_xz := max(x, z) // 调用函数 max(x, z)
    fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
    fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
    fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) //  也可在这直接调用它
}

In the above, we can see that the max function has two parameters, their types are int, then the type of the first variable can be omitted (i.e. a, b
int, not a int, b int), the default is to leave it The nearest type, the same applies to more than 2 variables or return values ​​of the same type. At the same time, we notice that its return value is a type, which is omitted.

Multiple return values

One of the more advanced features of Go language than C is that functions can return multiple values.

Let’s go directly to the code to see examples

package main
import "fmt"
// 返回 A+B  和 A*B
func SumAndProduct(A, B int) (int, int) {
    
    
	return A+B, A*B
}
func main() {
    
    
    x := 3
    y := 4
    xPLUSy, xTIMESy := SumAndProduct(x, y)
    fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
    fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}

In the above example, we can see that two parameters are returned directly. Of course, we can also name the variables of the return parameters. In this example, only two types are used. We can also change it to the following definition, and then return without The variable name is because it is initialized directly in the function. But if your function is exported (the first letter is capitalized), the official recommendation is: it is best to name the return value, because not naming the return value will make the code more concise, but it will cause the generated document to be less readable.

func SumAndProduct(A, B int) (add int, Multiplied int) {
    
    
    add = A+B
    Multiplied = A*B
    return
}

Variable parameters

Go functions support variable parameters. Functions that accept variadic parameters have an indefinite number of parameters. In order to do this, you first need to define a function that accepts variadic arguments:

func myfunc(arg ...int) {
    
    }

arg ...int tells Go that this function accepts an indefinite number of arguments. Note that the types of these parameters are all int. In the function body, the variable arg is a slice of int :

for _, n := range arg {
    
    
	fmt.Printf("And the number is: %d\n", n)
}

Passing by value and pointer

When we pass a parameter value to the called function, we are actually passing a copy of the value. When the parameter value is modified in the called function, the corresponding actual parameters in the calling function will not change at all, because Numerical changes only affect copy.

To verify what we said above, let’s look at an example

package main
import "fmt"
// 简单的一个函数,实现了参数 +1 的操作
func add1(a int) int {
    
    
    a = a+1 //  我们改变了 a 的值
    return a // 返回一个新值
}
func main() {
    
    
    x := 3
    fmt.Println("x = ", x) //  应该输出 "x = 3"
    x1 := add1(x) // 调用 add1(x)
    fmt.Println("x+1 = ", x1) //  应该输出 "x+1 = 4"
    fmt.Println("x = ", x) //  应该输出 "x = 3"
}

see it? Although we called the add1 function and performed the a = a+1 operation in add1, the value of the x variable in the above example did not change.

The reason is simple: because when we call add1, the parameter received by add1 is actually a copy of x, not x itself.

Then you may ask, what should I do if I really need to pass the x itself?

* This involves so-called pointers. We know that variables are stored at a certain address in memory, and modifying a variable actually modifies the memory at the variable address. Only the add1 function knows the address of the x variable and can modify the value of the x variable. Therefore, we need to pass the address &x of x into the function, and change the type of the function parameter from int to int, that is, to a pointer type, in order to modify the value of the x variable in the function. At this time, the parameters are still passed by copy, but the copy is a pointer.

Please see the example below

package main
import "fmt"
// 简单的一个函数,实现了参数 +1 的操作
func add1(a *int) int {
    
     //  请注意,
    *a = *a+1 //  修改了 a 的值
    return *a //  返回新值
}
func main() {
    
    
    x := 3
    fmt.Println("x = ", x) //  应该输出 "x = 3"
    x1 := add1(&x) //  调用 add1(&x)  传 x 的地址
    fmt.Println("x+1 = ", x1) //  应该输出 "x+1 = 4"
    fmt.Println("x = ", x) //  应该输出 "x = 4"
}

In this way, we achieve the purpose of modifying x. So what are the benefits of passing pointers?

  • Passing pointers allows multiple functions to operate on the same object.
  • Passing pointers is relatively lightweight (8 bytes) and only passes the memory address. We can use pointers to pass large structures. If passed by parameter value, relatively more system overhead (memory and time) will be spent on each copy. So when you want to pass a large structure, using pointers is a wise choice.
  • The implementation mechanisms of the three types of string, slice, and map in Go language are similar to pointers, so they can be passed directly instead of taking the address and then passing the pointer. (Note: If the function needs to change the length of the slice, it still needs to take the address and pass the pointer)

defer

There is a good design in the Go language, that is, the defer statement. You can add multiple defer statements to the function. When the function is executed to the end, these defer statements will be executed in reverse order, and finally the function returns. Especially when you are performing some operations to open resources and you encounter an error and need to return early, you need to close the corresponding resources before returning, otherwise it will easily cause problems such as resource leakage.

As shown in the following code, we generally write and open a resource like this:

func ReadWrite() bool {
    
    
    file.Open("file")
    //  做一些工作
    if failureX {
    
    
        file.Close()
        return false
    }
    if failureY {
    
    
        file.Close()
        return false
    }
    file.Close()
    return true
}

We see that there is a lot of duplicate code above, and Go's defer effectively solves this problem. After using it, not only the amount of code is reduced a lot, but the program becomes more elegant. The function specified after defer will be called before the function exits.

func ReadWrite() bool {
    
    
    file.Open("file")
    defer file.Close()
    if failureX {
    
    
    	return false
    }
    if failureY {
    
    
    	return false
    }
    return true
}

If there are many calls to defer, then defer uses the last-in-first-out mode, so the following code will output 4 3 2 1 0

for i := 0; i < 5; i++ {
    
    
	defer fmt.Printf("%d ", i)
}

Functions as values, types

In Go, a function is also a type of variable. We can define it through type. Its type is a type that has the same parameters and the same return value.

type typeName func(input1 inputType1 [, input2 inputType2 [, ...]) (result1 resultType1 [, ...])

What are the benefits of functions as types? That is, this type of function can be passed as a value. Please see the following example.

package main
import "fmt"
type testInt func(int) bool //  声明了一个函数类型
func isOdd(integer int) bool {
    
    
    if integer%2 == 0 {
    
    
    	return false
	}
	return true
}
func isEven(integer int) bool {
    
    
    if integer%2 == 0 {
    
    
    	return true
    }
    return false
}
//  声明的函数类型在这个地方当做了一个参数
func filter(slice []int, f testInt) []int {
    
    
    var result []int
    for _, value := range slice {
    
    
        if f(value) {
    
    
        	result = append(result, value)
        }
	}
	return result
}
func main(){
    
    
    slice := []int {
    
    1, 2, 3, 4, 5, 7}
    fmt.Println("slice = ", slice)
    odd := filter(slice, isOdd) //  函数当做值来传递了
    fmt.Println("Odd elements of slice are: ", odd)
    even := filter(slice, isEven) //  函数当做值来传递了
    fmt.Println("Even elements of slice are: ", even)
}

Functions as values ​​and types are very useful when we write some general interfaces. From the above example we see that the type testInt is a function type, and the parameters and return values ​​of the two filter functions are the same as the testInt type, but we can Implementing many kinds of logic makes our program very flexible.

main function and init function

There are two reserved functions in Go: init function (can be applied to all packages) and main function (can only be applied to package main). These two functions cannot have any parameters or return values ​​when defined. Although you can write any number of init functions in a package, for both readability and future maintainability, we strongly recommend that users only write one init function for each file in a package.

Go programs automatically call init() and main(), so you don't need to call these two functions anywhere. The init function in each package is optional, but package main must contain a main function.

The initialization and execution of the program start from the main package. If the main package also imports other packages, they will be imported in sequence during compilation. Sometimes a package will be imported by multiple packages at the same time, so it will only be imported once (for example, many packages may use the fmt package, but it will only be imported once, because there is no need to import it multiple times). When a package is imported, if the package also imports other packages, the other packages will be imported first, and then the package-level constants and variables in these packages will be initialized, and then the init function (if any) will be executed. ),And so on. After all imported packages are loaded, the package-level constants and variables in the main package will be initialized, then the init function in the main package will be executed (if it exists), and finally the main function will be executed.

import

When we write Go code, we often use the import command to import package files. The methods we often see are as follows:

import(
	"fmt"
)

Then we can call it in our code in the following way

fmt.Println("hello world")

The above fmt is the standard library of Go language. In fact, you need to go to goroot to load the module. Of course, Go's import also supports the following two methods to load modules written by yourself:

  • Relative path
    import "./model" //The model directory in the same directory as the current file, but this method of import is not recommended.

  • Absolute path
    import "shorturl/model" //Load the gopath/src/shorturl/model module

The above shows some commonly used methods of import, but there are also some special imports that make it difficult for many novices to understand. Let’s explain one by one what is going on.

  • Click Operation.
    Sometimes we will see the following way to import packages:

    import(
    	. "fmt"
    )
    

    The meaning of this operation is that after the package is imported, when you call the function of this package, you can omit the prefixed package name, that is, the fmt.Println("hello world") you called earlier can be omitted and written as Println("hello world") world")

  • Alias ​​operation As the name
    suggests, we can name the package another name that is easy for us to remember.

    import(
    	f "fmt"
    )
    

    In the case of alias operation, the prefix becomes our prefix when calling the package function, that is, f.Println("hello world")

  • _Operation
    This operation is often an operator that is confusing to many people. Please see the following import.

    import (
    "database/sql"
    _ "github.com/ziutek/mymysql/godrv"
    )
    

    The _ operation actually imports the package, instead of directly using the functions in the package, it calls the init function in the package.

Guess you like

Origin blog.csdn.net/u012534326/article/details/120028914