Function of Golang pulse

Functions are organized, reusable blocks of code used to perform specified tasks.

Go language supports functions, anonymous functions and closures, and functions belong to "first-class citizens" in go language.

Function definition

func keyword is used for functions defined in Go language. The specific format is as follows:

func Function name(parameter)(Return value){
    Function body
}

Of which:

  • Function declaration: keyword func
  • Function name: composed of letters, numbers and underscores. However, the first letter of a function name cannot be a number. Function names cannot be duplicated within the same package.
  • Function parameters: parameters are composed of parameter variables and types of parameter variables. Parameter variables can be omitted. There can be one parameter, multiple parameters, or none; Multiple parameters are used to separate; When there are multiple parameters, the parameter variables are either all written or all omitted; If multiple adjacent parameters have the same type, you can only keep the declaration of the last parameter of the same type.
  • Function return value: the return value consists of the return value variable and its variable type. The return value variable can be omitted. There can be one return value, multiple return values, or none; Multiple return values must be wrapped in () and separated by; When there are multiple return values, the return value variables are either written or omitted.
  • Function body: the logic that implements the specified function.

Define a function that sums two numbers:

func add(x int, y int) int {
	return x + y
}

The parameters and return values of the function are optional. Implement a function that requires neither parameters nor return values:

func printf() {
	fmt.Println("printf function")
}

Function call

After the function is defined, the function can be called by the function name (). Call the two functions defined above:

func main() {
	printf()
	ret := add(10, 20)
	fmt.Println(ret)
}

Note that when you call a function with a return value, you can not receive its return value.

parameter

Parameter use

Formal parameter: when defining a function, it is used to receive external incoming data. It is called formal parameter, or formal parameter for short.

Actual parameter: when calling a function, the actual data passed to the formal parameter is called the actual parameter, which is called the actual parameter for short.

Function call:

A: Function names must match
B: Arguments and formal parameters must correspond to each other one by one: order, number and type

Type abbreviation

In the parameters of the function, if the types of adjacent variables are the same, the types can be omitted, for example:

func add(x, y int) int {
	return x + y
}

If the types of multiple adjacent parameters are the same, you can only keep the declaration of the last parameter of the same type. The add function has two parameters, and the types of both parameters are int. therefore, you can omit the type of x, because there is a type description after y, and the x parameter is also of this type.

Variable parameters

The number of parameters of the function is variable, such as the most common fmt.Println function. Variable parameters in Go language are identified by adding... After the parameter name. Variable arguments are slice types in the function body

Note: variable parameters are usually used as the last parameter of the function.

for instance:

func add(x ...int) int {
	fmt.Println(x) //x is a slice
	sum := 0
	for _, v := range x {
		sum = sum + v
	}
	return sum
}

Call the above function:

ret1 := add()
ret2 := add(10)
ret3 := add(10, 20)
ret4 := add(10, 20, 30)
fmt.Println(ret1, ret2, ret3, ret4) //0 10 30 60

When a fixed parameter is used with a variable parameter, the variable parameter should be placed after the fixed parameter. The example code is as follows:

func add(x int, y ...int) int {
	fmt.Println(x, y)
	sum := x
	for _, v := range y {
		sum = sum + v
	}
	return sum
}

Call the above function:

ret5 := add(100)
ret6 := add(100, 10)
ret7 := add(100, 10, 20)
ret8 := add(100, 10, 20, 30)
fmt.Println(ret5, ret6, ret7, ret8) //100 110 130 160

In essence, the variable parameters of a function are implemented by slicing.

Parameter transfer

The parameters of go language functions also exist value passing and reference passing

pass by value

func main(){
   /* Declare function variables */
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }

   /* Use function */
   fmt.Println(getSquareRoot(9))

}

Reference passing (involving pointer knowledge points)

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

//A simple function that implements the operation of parameter + 1
func add1(a *int) int { // Please note that,
    *a = *a+1 // Modified the value of a
    return *a // Return new value
} 
func main() {
    x := 3
    fmt.Println("x = ", x) // Should output "x = 3"
    x1 := add1(&x) // Call ADD1 (& x) to pass the address of X
    fmt.Println("x+1 = ", x1) // Should output "x+1 = 4"
    fmt.Println("x = ", x) // Should output "x = 4"
}
  • Passing pointers enables multiple functions to operate on the same object.
  • The pointer is relatively lightweight (8 bytes). It only passes the memory address. You can use the pointer to pass large structures. If the parameter value is passed, a relatively large amount of system overhead (memory and time) will be spent on each copy. Therefore, when passing large structures, using pointers is a wise choice.
  • The implementation mechanisms of slice and map in Go language are similar to pointers, so they can be passed directly instead of passing the pointer after taking the address. (Note: if the function needs to change the length of slice, it still needs to take the address and pass the pointer)

Return value

In the Go language, the return value is output through the return keyword.

Multiple return values

Functions in Go language support multiple return values. If a function has multiple return values, all return values must be wrapped with ():

func swap(x, y string) (string, string) {
   return y, x
}

Return value naming

When defining a function, you can name the return value, directly use these variables in the function body, and finally return through the return keyword:

func SumAndProduct(a, b int) (add int, multiplied int) {
    add = a + b
    multiplied = a * b
    return
}

Return value supplement

When the return value type of a function is slice, nil can be regarded as a valid slice. There is no need to display and return a slice with length of 0.

func someFunc(x string) []int {
	if x == "" {
		return nil // There is no need to return [] int{}
	}
	...
}

Blank identifier

_ Is a blank identifier in Go. It can replace any value of any type.

For example, the result returned by the rectProps function is area and perimeter. If you want only area but not perimeter, you can use a blank identifier:

func rectProps(length, width float64) (float64, float64) {  
    var area = length * width
    var perimeter = (length + width) * 2
    return area, perimeter
}
func main() {  
    area, _ := rectProps(10.8, 5.6) // perimeter is discarded
    fmt.Printf("Area %f ", area)
}

Function advanced order

Variable scope

Scope: the scope within which variables can be used.

global variable

A global variable is a variable defined outside a function and is valid throughout the program's running cycle. All functions can be used and share this data.

//Define global variable num
var num int64 = 10

func testGlobalVar() {
	fmt.Printf("num=%d\n", num) //The global variable num can be accessed in the function
}
func main() {
	testGlobalVar() //num=10
}

local variable

Local variables are divided into two types: where variables are defined, they can only be used in which range. Beyond this range, variables are destroyed:

func testLocalVar() {
	//Define a function local variable x, which takes effect only within the function
	var x int64 = 100
	fmt.Printf("x=%d\n", x)
}

func main() {
	testLocalVar()
	fmt.Println(x) // The variable x cannot be used at this time
}

If the local variable and the global variable have the same name, the local variable is accessed first.

package main

import "fmt"

//Define global variable num
var num int64 = 10

func testNum() {
	num := 100
	fmt.Printf("num=%d\n", num) // Local variables are preferred in functions
}
func main() {
	testNum() // num=100
}

Variable defined by statement block: this method is usually used to define variables on if conditional judgment, for loop and switch statements.

func testLocalVar2(x, y int) {
	fmt.Println(x, y) //The parameters of the function also take effect only in this function
	if x > 0 {
		z := 100 //The variable z takes effect only in the if statement block
		fmt.Println(z)
	}
	//fmt.Println(z) / / variable Z cannot be used here
}

The variables defined in the for loop statement also take effect only in the for statement block:

func testLocalVar3() {
	for i := 0; i < 10; i++ {
		fmt.Println(i) //The variable i takes effect only in the current for statement block
	}
	//fmt.Println(i) / / variable i cannot be used here
}

Function types and variables

Define function type

You can use the type keyword to define a function type. The specific format is as follows:

type calculation func(int, int) int

The above statement defines a calculation type, which is a function type. This function receives two parameters of type int and returns a return value of type int.

In short, all functions that meet this condition are of type calculation. For example, the following add and sub are of type calculation.

func add(x, y int) int {
	return x + y
}

func sub(x, y int) int {
	return x - y
}

Both add and sub can be assigned to variables of type calculation.

var c calculation
c = add

Function type variable

Declare a variable of function type and assign a value to it:

func main() {
	var c calculation               // Declare a variable of type calculation c
	c = add                         // Assign add to c
	fmt.Printf("type of c:%T\n", c) // type of c:main.calculation
	fmt.Println(c(1, 2))            // Call c like add

	f := add                        // Assign the function add to the variable f1
	fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int
	fmt.Println(f(10, 20))          // Call f as you call add
}

Higher order function

Higher order functions are divided into two parts: functions as parameters and functions as return values.

Function as parameter

Functions can be used as arguments:

func add(x, y int) int {
	return x + y
}
func calc(x, y int, op func(int, int) int) int {
	return op(x, y)
}
func main() {
	ret2 := calc(10, 20, add)
	fmt.Println(ret2) //30
}

Function as return value

Functions can also be used as return values:

func do(s string) (func(int, int) int, error) {
	switch s {
	case "+":
		return add, nil
	case "-":
		return sub, nil
	default:
		err := errors.New("Unrecognized operator")
		return nil, err
	}
}

Anonymous functions and closures

Anonymous function

Of course, functions can also be used as return values, but in Go language, functions can no longer be defined inside functions as before, only anonymous functions can be defined. Anonymous functions are functions without function names. The definition format of anonymous functions is as follows:

func(parameter)(Return value){
    Function body
}

Anonymous functions cannot be called like ordinary functions because they do not have a function name. Therefore, anonymous functions need to be saved to a variable or used as immediate execution functions:

func main() {
	// Save anonymous functions to variables
	add := func(x, y int) {
		fmt.Println(x + y)
	}
	add(10, 20) // Calling anonymous functions through variables

	//Self executing function: anonymous function definition plus () is executed directly
	func(x, y int) {
		fmt.Println(x + y)
	}(10, 20)
}

Anonymous functions are mostly used to implement callback functions and closures.

closure

A closure is an entity composed of a function and its associated reference environment. Simply put, closure = function + reference environment:

func adder() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = adder()
	fmt.Println(f(10)) //10
	fmt.Println(f(20)) //30
	fmt.Println(f(30)) //60

	f1 := adder()
	fmt.Println(f1(40)) //40
	fmt.Println(f1(50)) //90
}

When the variable f is a function and it references the X variable in its external scope, f is a closure. The variable x is also valid throughout the life cycle of F. Advanced closure example 1:

func adder2(x int) func(int) int {
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = adder2(10)
	fmt.Println(f(10)) //20
	fmt.Println(f(20)) //40
	fmt.Println(f(30)) //70

	f1 := adder2(20)
	fmt.Println(f1(40)) //60
	fmt.Println(f1(50)) //110
}

Advanced closure example 2:

func makeSuffixFunc(suffix string) func(string) string {
	return func(name string) string {
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}
		return name
	}
}

func main() {
	jpgFunc := makeSuffixFunc(".jpg")
	txtFunc := makeSuffixFunc(".txt")
	fmt.Println(jpgFunc("test")) //test.jpg
	fmt.Println(txtFunc("test")) //test.txt
}

Advanced closure example 3:

func calc(base int) (func(int) int, func(int) int) {
	add := func(i int) int {
		base += i
		return base
	}

	sub := func(i int) int {
		base -= i
		return base
	}
	return add, sub
}

func main() {
	f1, f2 := calc(10)
	fmt.Println(f1(1), f2(2)) //11 9
	fmt.Println(f1(3), f2(4)) //12 8
	fmt.Println(f1(5), f2(6)) //13 7
}

Closures are not complicated. Just remember that closures = functions + references to outer variables.

defer statement

The defer statement in the Go language delays the statements that follow it. When the function to which defer belongs is about to return, the delayed statements are executed in the reverse order defined by defer, that is, the statements first deferred are executed last, and the statements last deferred are executed first.

func main() {
	fmt.Println("start")
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("end")
}

Output results:

start
end
3
2
1

Due to the delayed calling of defer statement, defer statement can easily deal with the problem of resource release. For example, resource cleaning, file closing, unlocking and recording time.

defer execution timing

In the function of Go language, the return statement is not an atomic operation at the bottom. It is divided into two steps: assigning a return value and RET instruction. The defer statement is executed after the return value assignment operation and before the RET instruction is executed. See the following figure for details:

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-4i604a1l-1634525193071)( https://qiniu.drunkery.cn//blogimage-20210930113047536.png )]

defer classic case

Read the following code and write the final print result.

func f1() int {
	x := 5
	defer func() {
		x++
	}()
	return x
}

func f2() (x int) {
	defer func() {
		x++
	}()
	return 5
}

func f3() (y int) {
	x := 5
	defer func() {
		x++
	}()
	return x
}
func f4() (x int) {
	defer func(x int) {
		x++
	}(x)
	return 5
}
func main() {
	fmt.Println(f1()) //5
	fmt.Println(f2()) //6
	fmt.Println(f3()) //5
	fmt.Println(f4()) //5
}

defer interview questions

func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}

func main() {
	x := 1
	y := 2
	defer calc("AA", x, calc("A", x, y))
	x = 10
	defer calc("BB", x, calc("B", x, y))
	y = 20
}

Q: what is the output of the above code? (Note: when defer registers a function to be delayed, all parameters of the function need to be determined)

defer note

When the statements in the peripheral function are executed normally, the peripheral function will really end the execution only when all the delay functions are executed.
When executing in a peripheral function return Statement, the peripheral function will not really return until all the delay functions are executed.
When the code in the peripheral function causes a run-time panic, the run-time panic will not be really extended to the calling function until all the delay functions are executed.

Introduction to built-in functions

Built in functionintroduce
closeIt is mainly used to close the channel
lenIt is used to find the length, such as string, array, slice, map and channel
newIt is used to allocate memory. It is mainly used to allocate value types, such as int and struct. The pointer is returned
makeIt is used to allocate memory. It is mainly used to allocate reference types, such as chan, map and slice
appendUsed to append elements to arrays and slice s
panic and recoverUsed for error handling

panic/recover

At present (Go1.12), there is no exception mechanism in the Go language, but the panic/recover mode is used to handle errors. Panic can be raised anywhere, but recover is only valid in the function called by defer. Let's start with an example:

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	panic("panic in B")
}

func funcC() {
	fmt.Println("func C")
}
func main() {
	funcA()
	funcB()
	funcC()
}

Output:

func A
panic: panic in B

goroutine 1 [running]:
main.funcB(...)
        .../code/func/main.go:12
main.main()
        .../code/func/main.go:20 +0x98

During program running, panic was thrown in funcB, resulting in program crash and abnormal exit. At this time, you can recover the program through recover and continue to execute it later.

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	defer func() {
		err := recover()
		//If a panic error occurs in the program, it can be recovered through recover
		if err != nil {
			fmt.Println("recover in B")
		}
	}()
	panic("panic in B")
}

func funcC() {
	fmt.Println("func C")
}
func main() {
	funcA()
	funcB()
	funcC()
}

be careful:

  1. recover() must be used with defer.
  2. The defer must be defined before the statement that may cause panic.

Tags: Go

Posted on Sun, 17 Oct 2021 22:11:14 -0400 by hoogeebear