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(1,2,3)
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
- The test case file will not participate in the compilation of normal source code, and will not be included in the executable file;
- The filename of the test case must end with _test.go;
- Need to use import to import the test package;
- 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;
- Unit tests take (t *testing.T) as parameters, and performance tests take (t *testing.B) as parameters;
- 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%