js worker's go study notes - go function

Go functions

function declaration

The function declaration includes the function name, the formal parameter list, the return value list (optional), and the function body.

func function name (formal parameter list) (return value list) { function body }

the return value of the function

Go supports multiple return values, while JavaScript only supports a single return value. If JS faces the situation of returning multiple return values, it can only choose to return a structure

go can name the return value

    func functionName ()(a,b int){
    
    
        a=2;
        b=3;
        return
    }

We named two variables a and b as the return value in the first line. When we use the named return value, we can not write it after the return, of course, it is also possible to write it.

    func functionName()(a,b int){
    
    
        a=2
        return a,3
    }

This code has the same meaning as the previous paragraph

Variables declared in the function body can only be used in the function body. If the function ends, these variables will be released and invalidated

function variable

Functions exist as variables in go

    func fun1 (){
    
    
        fmt.Printf("hello world")
    }
    fun2 := fun1
    fun2()

The running result of hello world
fun2 is the same as fun1

anonymous function

Anonymous functions do not need to define a function name, only the function declaration and function body

func (形参列表)返回值{
    
    
    函数体
}

Anonymous functions can also be called on the fly

    func (a int{
    
    
        printf(a)
    }(100)

It is equivalent to calling directly when declaring, passing the value of 100 to the formal parameter a

anonymous function assignment

Because functions are a kind of variable in go, we declare a constant to represent anonymous functions

    a:=func (){
    
    }
    a()

a represents the value of this anonymous function

Anonymous function as callback function

Anonymous functions can be used as callback functions

    func visit (arr []int , f func(int){
    
    
        for a := range arr{
    
    
            f(a)
        }
    }
    func main(){
    
    
        visit([]int{
    
    1,2,3},func(v int){
    
    
            fmt.Printf(v)
        })
    }

Function implementation interface and structure

Closure

function + reference environment = closure

The application scenarios of closures are mainly that when functions are instantiated in different application scenarios, they will become different closures. A closure is a function that references free variables. Once a closure is formed, even if it leaves the reference environment, the free variables remain. Can be used freely without affecting continued use in closures

Functions do not have memory, only closures have them. They are static at compile time as functions, and dynamically at runtime as closures.

package main

import "fmt"

func bibao(i int) func() int {
    
    

	return func() int {
    
    
		i++
		fmt.Printf("i:%d\n", i)
		return i
	}
}

func main() {
    
    
	bibao1 := bibao(1)
	for i := 0; i < 10; i++ {
    
    
		bibao1()
	}
}

i:2
i:3
i:4
i:5
i:6
i:7
i:8
i:9
i:10
i:11

The closure will memorize the variable captured in the closure, and the variable will always exist with the life cycle of the closure. When the bibao function is instantiated as bibao1, the closure has been formed

If we change the function executed in for to bibao(1)(), the function is repeatedly instantiated to form multiple closures, and the value we get will always be 2

variable parameter

Variable parameters means that if you want to pass several parameters, you can pass several parameters to the function. Unrestricted input. When the function receives parameters, it is received in the way of array slices.

func functionName(形参名 ...int){
    
    
    for _,value = range 形参名{
    
    
        value // 参数
    }
    }
    functionName(123

This declaration method is a fixed-type parameter, ...type is strictly a syntactic sugar, and must be in the last parameter

Variable parameters of any type

Just replace ...type with ...interface{} when making a function declaration

    func functionName(args ...interface{
    
    }){
    
    
        for _,value = range args{
    
    
            switch value.(type){
    
     // 判断每个元素的类型
                case int :
                    fmt.Println("number");
                case string :
                     fmt.Println("string");
                     ...
                defalut:
                    fmt.Println("unkonw")

            }
        }
    }

Go language defer (deferred execution of statements)

The statement after defer will be delayed for execution, and the statement after defer will be executed before the return of the function. When the function runs to return, defer will be notified to start execution. At this time, if there are multiple defers, Will follow the stack method, starting from the statement after the last declared defer, complete the execution in reverse order, and then execute return

   func fun() string {
    
    
	fmt.Println("action")
	defer fmt.Println("defer1")
	defer fmt.Println("defer2")
	fmt.Println("end")
	return "next action"
}

func main() {
    
    
	fmt.Println(fun())
}

The output of the function is as follows

action
end
defer2
defer1
next action
We can see that defer runs after the function body and completes in reverse order, but before the function completes. The function completion can be the end of normal operation, return or downtime

Use defer to release resources on function exit

When we learn about locks, we will understand the advantages of defer

var (
    // 一个演示用的映射
    valueByKey      = make(map[string]int)
    // 保证使用映射时的并发安全的互斥锁
    valueByKeyGuard sync.Mutex
)

// 根据键读取值
func readValue(key string) int {
    
    
    // 对共享资源加锁
    valueByKeyGuard.Lock()
    // 取值
    v := valueByKey[key]
    // 对共享资源解锁
    valueByKeyGuard.Unlock()
    // 返回值
    return v
}
func main(){
    
    
    i := readValue("one")
    fmt.Println(i)
    a := readValue("two")
    fmt.Println(a)
}

Let's briefly introduce locks, and make it clear that the locks used here are to prevent you from repeating multi-threaded calls to change

0
0
If we don't call unLock, we will get an error message when we print the second time

Use defer to optimize the previous piece of code

    func readValue(key string) int {
    
    

    valueByKeyGuard.Lock()
   
    // defer后面的语句不会马上调用, 而是延迟到函数结束时调用
    defer valueByKeyGuard.Unlock()

    return valueByKey[key]
    }

recursive function

Go supports recursive calls, that is, calling the function itself within a function

The processing logic of the recursive function is exactly the same except for the size of the parameters passed in. The
recursive function must have a termination condition, otherwise the function will recurse infinitely until a memory overflow occurs.

Let's take factorial as an example (factorial is given a non-negative number, always multiply by -1 number until it reaches 0, the factorial of 0 is 1)

    func Factorial(n uint64)(res uint64){
    
    
        if n>0{
    
    
            res = n*Factorial(n-1)
            return
        }else{
    
    
            return 1
        }
    }
    func main(){
    
    
        fmt.Println(Factorial(3))
    }

6

It is also possible to implement mutual recursion between two functions, because of the characteristics of go, it does not care where the functions are declared and the order of declarations

    package main

import "fmt"

func main() {
    
    
	fmt.Println(singular(2))
}
func singular(value uint64) string {
    
    
	value--
	if value == 0 {
    
    
		return "singular"
	} else {
    
    
		return plural(value)
	}
}
func plural(value uint64) string {
    
    
	value--
	if value == 0 {
    
    
		return "plural"
	} else {
    
    
		return singular(value)
	}
}

This is a good example of iterating over each other between functions that determine whether elements are singular or plural

Handling runtime errors

This step is similar to throw Error() in js, but the upper-level logic does not need to give too many resources for function exceptions. In go, developers are more expected to take error handling as some necessary links in normal development.

We customize an error

errors is a package in the go language

    package main

import (
    "errors"
    "fmt"
)

// 定义除数为0的错误
var errDivisionByZero = errors.New("division by zero")

func div(dividend, divisor int) (int, error) {
    
    

    // 判断除数为0的情况并返回
    if divisor == 0 {
    
    
        return 0, errDivisionByZero
    }

    // 正常计算,返回空错误
    return dividend / divisor, nil
}

func main() {
    
    

    fmt.Println(div(1, 0))
}

0,division by zero

Customize error messages during parsing

In many cases, if we use errors.New() directly, it is a very unfree behavior, and the error information that can be carried is very limited, so we can define an error interface by ourselves with the help of a custom structure.

package main

import (
    "fmt"
)

// 声明一个解析错误
type ParseError struct {
    
    
    Filename string // 文件名
    Line     int    // 行号
}

// 实现error接口,返回错误描述
func (e *ParseError) Error() string {
    
    
    return fmt.Sprintf("%s:%d", e.Filename, e.Line)
}

// 创建一些解析错误
func newParseError(filename string, line int) error {
    
    
    return &ParseError{
    
    filename, line}
}

func main() {
    
    

    var e error
    // 创建一个错误实例,包含文件名和行号
    e = newParseError("main.go", 1)

    // 通过error接口查看错误描述
    fmt.Println(e.Error())

    // 根据错误接口具体的类型,获取详细错误信息
    switch detail := e.(type) {
    
    
    case *ParseError: // 这是一个解析错误
        fmt.Printf("Filename: %s Line: %d\n", detail.Filename, detail.Line)
    default: // 其他类型的错误
        fmt.Println("other error")
    }
}

When parsing errors, the file name and error line number are played

main.go 1
Filename:main.go Line:1

downtime

There are many errors in go that will be presented when the language is compiled, but many errors need to occur at runtime, such as array access out of bounds, null pointer references, etc., and then downtime will occur.

When a crash occurs, the program will stop running and then start running the defer statement defined before the crash

Panic can manually trigger downtime in the program to reduce losses

package main

func main() {
    
    
	panic("crash")
}

Terminal returns information:

panic: crash

goroutine 1 [running]:
main.main()
	/Users/a11111/Desktop/code/golang/study/day4/main.go:4 +0x30
exit status 2

We can clearly see the downtime information and the specific number of lines, and we can accurately locate the wrong location. The intent of the panic method is to make the go program crash directly.

crash recovery

Because there is no operation similar to try/catch in go, but sometimes we need to recover from downtime, so there is a method called recover that corresponds to panic.

recover can only be valid in the defer statement. If the function runs normally without triggering a crash event, recover will return nil. If it is caught in a goroutine, recover can capture the value of panic and resume normal work.

package main

import (
	"fmt"
)

func main() {
    
    
	savePanic(func() {
    
    
		fmt.Println("手动触发宕机")
		panic("发生了宕机")
	})
}
func savePanic(entry func()) {
    
    
	defer func() {
    
    
		err := recover()
		fmt.Println(err)
		fmt.Println("处理宕机")
	}()
	entry()
}

The relationship between panic and recover:

有panic没有recover 程序发生宕机
有panic有recover,程序不会宕机,执行defer语句之后,从宕机点退出,函数继续执行

In panic, you can further use panic to know that when the program completely crashes, the
error is the value, so we can directly use the command return value to set

Calculate the execution time of a function

The importance of the runtime of a function goes without saying

func Since(t Time)Duration
The Since() function is used in the time package to get the running time of the function
The return value of Since is the time from t to the present, which is equivalent to time.Now().Sub(t)

package main

import (
	"fmt"
	"time"
)

func main() {
    
    
	start := time.Now()
	for i := 0; i < 1000; i++ {
    
    

	}
	end := time.Since(start)
	fmt.Println("运行时间为", end)
}

The runtime is: 583ns

Above we mentioned that time.Now.Sub(t) also has the same effect

func main() {
    
    
	start := time.Now()
	for i := 0; i < 1000; i++ {
    
    

	}
	end := time.Now().Sub(start)
	fmt.Println("运行时间为", end)
}

It is not difficult to find that the results obtained are similar, and the different results may be due to the different operating states of the CPU at this time, which are within the normal range.

test test function

In order for us to minimize the consideration of bugs in the case of complex projects, there are mainly two methods, one is code review, and the other is relatively simple code testing

Use the test package in go to complete the test

  1. The test case file will not participate in the compilation of normal source code, and will not be included in the executable file;
  2. The filename of the test case must end with _test.go;
  3. Need to use import to import the test package;
  4. The name of the test function must start with Test or Benchmark, followed by a string of any letters, but the first letter must be capitalized, such as TestAbc(), a test case file can contain multiple test functions;
  5. Unit tests take (t *testing.T) as parameters, and performance tests take (t *testing.B) as parameters;
  6. The test case file is executed using the go test command. The main() function is not required as an entry in the source code. All functions starting with Test in the source code file ending with _test.go will be automatically executed.

The testing package has a total of three testing methods, namely stress (performance) testing, unit (functional) testing, and coverage testing.

Performance Testing

package main

import "testing"

func TestGetArea(t *testing.T) {
    
    
	area := getArea(10, 20)
	if area < 2000 {
    
    
		t.Error("测试失败")
	}
}
a11111@LyiZrideMacBook-Pro day4 % go test -v
=== RUN   TestGetArea
    main_test.go:8: 测试失败
--- FAIL: TestGetArea (0.00s)

pressure test

func BenchmarkGetArea(t *testing.B) {
    
    
    	for i := 0; i < t.N; i++ {
    
    
    		getArea(100, 120)
    	}
    }
a11111@LyiZrideMacBook-Pro day4 % go test -bench="."
goos: darwin
goarch: arm64
pkg: main.go
BenchmarkGetArea-8   	1000000000	         0.3193 ns/op
PASS
ok  	main.go	1.718s

Tested 1000000000 times, the total time is 0.3193 nanoseconds

coverage test

The coverage rate can test the total business code covered by the program, that is, how much the main.go code tested by main_test.go is preferably 100%.

main_test.go

package main

import "testing"

func TestGetArea(t *testing.T) {
    
    
	area := getArea(10, 20)
	if area < 2000 {
    
    
		t.Error("测试失败")
	}
}

func BenchmarkGetArea(t *testing.B) {
    
    
	for i := 0; i < t.N; i++ {
    
    
		getArea(100, 120)
	}
}

main.go code

package main

func getArea(height int, witdh int) int {
    
    
	return height * witdh
}

% go test -cover
--- FAIL: TestGetArea (0.00s)
    main_test.go:8: 测试失败
FAIL
coverage: 100.0% of statements
exit status 1
FAIL	main.go	1.304s

You can see that the coverage is 100%

Guess you like

Origin blog.csdn.net/weixin_44846765/article/details/125059621