如何从一名 Javaer 转变为 Gopher
Golang 的安装
Windows
使用.msi 后缀的安装包进行安装,默认安装路径为 C:\Go。可以将 C:\Go\bin 路径添加到 Path 环境变量中
UNIX/Linux
下载二进制包:go.linux-amd64.tar.gz
将下载的二进制包解压至 /usr/local 目录。
1 tar -C /usr/local -xzf go.linux-amd64.tar.gz
编辑 ~/.bash_profile 或者 /etc/profile,并将以下命令添加该文件的末尾:
1 export PATH=$PATH :/usr/local/go/bin
或
MacOS
建议使用 homebrew 安装
Go 语言的 toolchain(工具链)包含一系列用于编译、构建、调试和测试 Go 代码的工具,主要包括以下内容:
编译和构建相关
1 2 3 4 go build: go run: go install: go clean:
包管理
测试和调试
1 2 3 go test : go benchmark: go tool pprof:
代码格式化和分析
1 2 3 go fmt : gofmt: go vet:
文档生成
交叉编译和工具
1 2 3 4 5 6 go env : go version: go list: go tool compile: go tool link : go tool objdump:
构建工具
Go 基础语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 package mainimport ( "fmt" "math" "sync" "time" ) const Pi = 3.14159 var globalVar = "我是全局变量" type Person struct { Name string Age int } func (p Person) SayHello() { fmt.Printf("你好,我是 %s,今年 %d 岁。\n" , p.Name, p.Age) } type Speaker interface { Speak() } func (p Person) Speak() { fmt.Println("speak" ) } func PrintValue [T any ](value T) { fmt.Println("泛型值:" , value) } func main () { var a int = 10 b := 20.5 c := "Hello, Go!" d := true fmt.Println("变量:" , a, b, c, d) arr := [3 ]int {1 , 2 , 3 } fmt.Println("数组:" , arr) slice := []int {4 , 5 , 6 } slice = append (slice, 7 ) fmt.Println("切片:" , slice) m := map [string ]int {"Alice" : 25 , "Bob" : 30 } fmt.Println("映射:" , m) if a > 5 { fmt.Println("a 大于 5" ) } else { fmt.Println("a 小于等于 5" ) } for i := 0 ; i < 3 ; i++ { fmt.Println("循环 i:" , i) } person := Person{Name: "张三" , Age: 28 } person.SayHello() ptr := &a fmt.Println("指针值:" , *ptr) var sp Speaker = person sp.Speak() func (msg string ) { fmt.Println("匿名函数:" , msg) }("Go 语言" ) defer fmt.Println("程序即将结束!" ) result, err := divide(10 , 0 ) if err != nil { fmt.Println("错误:" , err) } else { fmt.Println("除法结果:" , result) } PrintValue("泛型字符串" ) PrintValue(123 ) PrintValue(3.14 ) } func divide (a, b float64 ) (float64 , error ) { if b == 0 { return 0 , fmt.Errorf("除数不能为 0" ) } return a / b, nil }
defer 的特性:
关键字 defer 用于注册延迟调用
这些调用直到 return 前才被执行。因此,可以用来做资源清理
多个 defer 语句,按先进后出的方式执行
defer 语句中的变量,在 defer 声明时就决定了
defer 的用途:
关闭文件句柄
锁资源释放
数据库连接释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" type Test struct { name string } func (t *Test) Close() { fmt.Println(t.name, " closed" ) } func main () { ts := []Test{{"a" }, {"b" }, {"c" }} for _, t := range ts { defer t.Close() } }
Error 与 Panic
Go 语言的多值返回特性,使得它在返回常规的值时,还能轻松地返回详细的错误描述。使用这个特性来提供详细的错误信息是一种良好的风格。例如 os 库中:
1 func Create (name string ) (file *File, err Error)
在调用这个函数的时候可以通过返回值来捕获异常
1 file, err := os.Create("file.txt" )
Panic 会产生一个运行时错误并终止程序,该函数接受一个任意类型的实参(一般为字符串),并在程序终止时打印。
应用程序中应避免使用 panic。若问题可以被屏蔽或解决,最好就是让程序继续运行而不是终止整个程序。
interface
Go 语言提供了一种数据类型:接口。它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
鸭子类型
鸭子类型在程序设计中是动态类型的一种风格。在这种风格中,一个物件有效的语义,不是由继承自特定的类或实现特定的接口,而是由「当前方法和属性的集合」决定。“鸭子测试”可以这样表述:
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
在鸭子类型中,关注点在于物件的行为,能做什么;而不是关注物件所属的类型。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为「鸭子」的物件,并调用它的「走」和「叫」方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的物件,并调用它的「走」和「叫」方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的「走」和「叫」方法的物件都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 type Mover interface { move() } type dog struct { name string } type car struct { brand string } func (d dog) move() { fmt.Printf("%s会跑\n" , d.name) } func (c car) move() { fmt.Printf("%s速度70迈\n" , c.brand) } func main () { var x Mover var a = dog{name: "Doge" } var b = car{brand: "Porsche" } x = a x.move() x = b x.move() }
gorountine
在以前版本的 Java/C++ 中,要实现并发编程的时候,我们通常需要自己维护一个线程池,并且需要自己去包装一个又一个的任务,同时需要自己去调度线程执行任务并维护上下文切换,这通常会耗费大量的时间。那么能不能只需要定义很多个任务,让系统去帮我们分配到 CPU 上实现并发执行呢?
Go 语言中的 goroutine 就是这样一种机制, goroutine 的概念类似于线程,但 goroutine 是由 Go 的运行时(runtime)调度和管理的。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。
匿名函数
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" "math" ) func main () { getSqrt := func (a float64 ) float64 { return math.Sqrt(a) } fmt.Println(getSqrt(4 )) }
启动 goroutine 的方式非常简单,只需要在调用的函数(普通函数和匿名函数)前面加上一个 go 关键字。
注意:当 main 函数返回的时候,goroutine 就结束了,所有在 main 函数中启动的 goroutine 会一同结束
1 2 3 4 5 6 7 8 func hello () { fmt.Println("Hello Goroutine!" ) } func main () { go hello() fmt.Println("main goroutine done!" ) }
匿名函数的写法:
1 2 3 4 5 6 func main () { go func () { fmt.Println("Hello Goroutine!" ) }() time.Sleep(time.Second) }
常用标准库
fmt
fmt 包实现了类似 C 语言 printf 和 scanf 的格式化 I/O。主要分为向外输出内容和获取输入内容两大部分。
time
时间和日期是我们编程中经常会用到的,本文主要介绍了 Go 语言内置的 time 包的基本用法。
strconv
strconv 包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数: Atoi()、Itoa()、parse 系列、format 系列、append 系列。
Gin 基本用法
介绍
Gin 是一个 Golang 的 Web 框架,封装比较优雅,API 友好,源码注释比较明确,具有快速灵活,容错方便等特点
对于 Golang 而言,web 框架的依赖要远比 Python,Java 之类的要小。自身的 net/http 足够简单,性能也非常不错
安装
在 go 项目中(与 go.mod 同一目录下)执行:
1 go get -u github.com/gin-gonic/gin
然后在代码中导入:
1 import "github.com/gin-gonic/gin"
示例:如何启动一个 Web 服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "net/http" "github.com/gin-gonic/gin" ) func main () { r := gin.Default() r.GET("/" , func (c *gin.Context) { c.String(http.StatusOK, "hello World!" ) }) r.Run(":8000" ) }
如何添加一个拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func Authorize () gin.HandlerFunc { return func (c *gin.Context) { username := c.Query("username" ) token := c.Query("token" ) if { c.Next() } else { c.Abort() c.JSON(http.StatusUnauthorized, gin.H{"message" :"访问未授权" }) } } } func ServiceWithoutAuth (c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message" :"这是一个不用经过认证就能访问的接口" }) } func ServiceWithAuth (c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message" :"这是一个需要经过认证才能访问的接口" }) } func main () { router := gin.Default() router.GET("/service_without_auth" , ServiceWithoutAuth) router.Use(Authorize()) router.GET("/service_with_auth" , ServiceWithAuth) router.Run(":8080" ) }