作者:Chancel Yang, 创建:2023-07-18, 字数:10486, 已阅:928, 最后更新:2024-06-14
Go(通常称为 Golang)是一种开源编程语言,由 Google 开发,设计目标是简单、高效和可并发,由 Robert Griesemer、Rob Pike 和 Ken Thompson 于 2007 年开始开发,并于 2009 年正式发布。
Go常用于网络、系统、并发、分布式编程,采用Go的大型项目包括了 Docker、Kubernetes、Prometheus、Terraform 等,Go语言的主要特性是简洁、并发支持、静态类型、垃圾回收、工具链。
以下基于Debian12
+ VSCode
创建golang的安装文件夹
mkdir -p $HOME/apps/golang && cd $HOME/apps/golang
wget https://go.dev/dl/go1.18.1.linux-amd64.tar.gz
tar -zxvf go1.18.1.linux-amd64.tar.gz
添加$HOME/apps/golang/go/bin
到PATH
中
echo 'export PATH=$PATH:$HOME/apps/golang/go/bin' >> ~/.zshrc
echo 'export GOPATH=$HOME/apps/golang' >> ~/.zshrc
echo 'export PATH=$PATH:$GOPATH/bin' >> ~/.zshrc
确认安装成功
go version
# 输出 go version go1.18.1 linux/amd64
创建第一个项目 hello-world
mkdir -p $HOME/codes/golang/hello-world && cd $HOME/codes/golang/hello-world
在 hello-world 项目中创建一个 main.go 的文件,内容如下:
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
运行 main.go
go run main.go
输出: hello world
沿用 hello-world 项目,修改 main.go 引入 web 框架 GIN ,输出一个 HTML 页面的 hello world
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(200, "hello world")
})
r.Run()
}
要使用第三方依赖包,需要借助 go mod
go 在1.11版本之后由 go mod 作为包的默认管理工具,它提供了对于包的依赖管理、部署构建
go mod
是默认开启的,如下:
# GO111MODULE有3个值,默认是auto,on/off指示开关
❯ go env | grep MODULE
GO111MODULE=""
如无特殊项目要求,保持默认(auto)即可,此时go会自动判断项目类型并查找依赖
go mod
常用语法如下
go mod <command> [arguments]
The commands are:
download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module in current directory
tidy add missing and remove unused modules
vendor make vendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed
使用 go mod
初始化包依赖管理,并下载 main.go
中使用到的web框架
go mod init hello-world
go mod tidy
此时项目结构如下
.
├── go.mod
├── go.sum
└── main.go
go.mod
是go modules的配置文件,go.sum
记载了依赖包的版本与校验信息
此时我们运行项目,访问 http://localhost:8080/
即可以看到hello world
Visual Studio Code
对于Golang开发也提供了支持,以上述的 hello-world 项目为例,在终端用 vscode
打开该项目
code $HOME/codes/golang/hello-world
在扩展插件中搜索go并安装,安装完成后按 ctrl+shift+p
,输入 go install ,选择 go: Install/Update tools ,勾选所有项并点击 ok ,如下:
随后创建 .vscode/launch.json ,内容如下
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"console": "internalConsole"
}
]
}
按F5可启动单步调试,过程如下所示
至此,开发环境搭建完成
Go对大小写敏感,命名规范原则上遵循对外可见则大写开头(UserModel),对外不可见则是小写开头(userModel)
命名推荐规范如下
类型 | 规范 | 例子 |
---|---|---|
包名 | 小写 | net/http |
文件名 | 下划线区分单词 | web_api.go |
接口/结构体/函数 | 驼峰命名 | WebApi |
GO的基础类型包含bool
、staring
、int/int8/int16/int32(rune)/int64
、uint/uint8(byte)/uint16/uint32/uint64
、float32/float64
以及complex64/complex128
float32 精确到小数点后7位,float64 精确到小数点后64位,由于精确度的问题,使用 == 和 != 比较float类型时应注意
变量声明代码如下:
// 单变量声明
var userName string = "chancel"
// 多个变量声明
var (
userName string = "chancel"
userAge int = 10
userHistory = []float32 {0.01,0.2}
getName func() string
userFamily struct {
fatherName string
}
)
Go语言的变量类型转换比较特殊,大多数时候都借助strconv
,如下
package main
import (
"fmt"
"strconv"
)
func main() {
// string to int
strVar := "100"
inVar, err := strconv.Atoi(strVar)
if err != nil {
fmt.Println(err)
}
fmt.Println(inVar) // 100
// int to string
s1 := strconv.FormatInt(int64(inVar), 10)
fmt.Println(s1) // 100
}
Go语言有匿名变量,通常采用关键字 _
,该变量不占用内存空间不分配内存
package main
import (
"fmt"
)
func getData() (int, int) {
return 1, 2
}
func main() {
var a, _ = getData()
fmt.Println("a value is ", a)
}
指针运算符是Go语言被归入C语言家族的重要原因,指针是指向变量值内存地址的变量
一个内存地址用一个操作系统原生字(native word)来存储。 一个原生字在32位操作系统上占4个字节,在64位操作系统上占8个字节
在Go里,一个指针的形式为*T,类型T被成为指针类型的base type,指针通常缩写为Ptr
指针的声明如下,通常采用前面一种(无名指针类型)
*int
type Ptr *int
对于其他编程语言来说,指针是比较晦涩难懂的,举例说明
package main
import "fmt"
func double(x *int) {
*x += *x
x = nil
}
func main() {
var a = 3
double(&a)
fmt.Println(a) // 6
p := &a
double(p)
fmt.Println(a, p == nil) // 12 false
}
Go语言也对指针做了很多限制,举例说明
package main
import "fmt"
func main() {
a := int64(5)
p := &a
// 下面这两行编译不通过。
/*
p++
p = (&a) + 8
*/
*p++
fmt.Println(*p, a) // 6 6
fmt.Println(p == &a) // true
*&a++
*&*&a++
**&p++
*&*p++
fmt.Println(*p, a) // 10 10
}
在Go中,数组必须在声明时固定长度且无法动态添加元素的
如下声明一个没有初始值数组 array_1
和拥有初始值的数组 array_2
package main
import "fmt"
func main() {
var array_1 [5]int
fmt.Println(array_1) // [0,0,0,0,0]
var array_2 = [5]int{1, 2, 3, 4, 5}
fmt.Println(array_2) // [1,2,3,4,5]
}
固定数组的使用场景较少,实际开发更需要可变数组
可变数组的用途非常广泛,在Go中可以借助 Slice(切片) 来实现可变数组
切片的常见使用如下
package main
import (
"fmt"
"sort"
)
func main() {
var array_1 [5]int
fmt.Println(array_1)
var array_2 = [5]int{3, 5, 7, 8, 1}
fmt.Println(array_2)
var slice_1 = array_2[:]
fmt.Println(slice_1[1:4]) // [5,7,8]
fmt.Println("slice length:", len(slice_1), "cap:", cap(slice_1), "ptr:", &slice_1[0]) // slice length: 5 cap: 5 ptr: 0xc0001aa060
slice_1 = append(slice_1, 4, 10, 2)
fmt.Println("slice length:", len(slice_1), "cap:", cap(slice_1), "ptr:", &slice_1[0]) // slice length: 8 cap: 10 ptr: 0xc0001ba000
slice_1 = append(slice_1, 9, 6)
fmt.Println("slice length:", len(slice_1), "cap:", cap(slice_1), "ptr:", &slice_1[0]) // lice length: 10 cap: 10 ptr: 0xc0001ba000
// 对元素进行排序
sort.Ints(slice_1)
fmt.Println("slice content:", slice_1) // slice content: [1 2 3 4 5 6 7 8 9 10]
}
可以看出每当cap变化的时候,切片的内存地址就会发生改变,说明了cap与length的区别在于cap是空间长度,如添加元素时cap长度不足,则Go会采用以下公式来进行扩容
var newSclice = make(int[],len(oldSlice),2*len(oldSlice)+1)
Go语言的字典是哈希结构的,key的类型必须是可直接比较的类型(如Slice/Func/map等无法作为key值),而Vlaue的类型则没有任何限制
所以借助value类型为slice的字典可实现Python语言的dict类型的效果
常见使用如下
package main
import "fmt"
func main() {
map_1 := map[string]string{"name": "zhangsan"}
fmt.Println(map_1) // map[name:zhangsan]
// 遍历
for k, v := range map_1 {
fmt.Println(k, v)
}
// 插值
map_1["age"] = "18"
// 查找
value, exist := map_1["name"]
if exist {
println(value) // zhangsan
}
//删除
delete(map_1, "age")
fmt.Println(map_1) // map[name:zhangsan]
}
Go的接口是支持面向对象编程(OOP)的重要标志,与其他语言的接口实现相比,go里面的类型不需要声明实现接口,只要实现了接口的方法即实现了接口
通俗地讲,go的接口是方法/行为的集合,往往作为一种通用类型参数
定义一个求面积的矩形接口,再定义一个计算体积的方法Bulk,分别计算圆与长方形的体积代码如下
package main
import (
"fmt"
"math"
)
// 定义接口 shape
type Shape interface {
Area() float64 //求面积
}
// 定义了结构体 circle
type Circle struct {
radius float64
}
// 定义结构体 rectangle
type Rectangle struct {
length float64
width float64
}
// 结构体 circle 实现了 shape.area 方法
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
// 结构体 rectangle 实现了 shape.area 方法
func (r Rectangle) Area() float64 {
return r.length * r.width
}
// 体积计算
func Bulk(s Shape, h float64) float64 {
return s.Area() * h
}
func main() {
var c Circle
c.radius = 3
fmt.Println(c.Area()) // 28.274333882308138
b := Bulk(c, 5)
fmt.Println(b) // 141.3716694115407
r := Rectangle{5.0, 8}
fmt.Println(r.Area()) // 40
b = Bulk(r, 5)
fmt.Println(b) // 200
}
可以看到,结构体 Circle
与 Rectangle
并不需要声明实现了接口 Shape
,但在方法 Bulk
中可以将他们看成 Shape
接口类型参数
接口为Go带来了抽象数据的能力,从而实现OOP(Object Oriented Programming)
此外,类型不一定需要实现接口的所有方法才实现了接口,可以通过嵌入其他结构体来获得其实现方法,如下
package main
import (
"fmt"
)
type AirCondition interface {
heating()
refrigerating()
}
type Heater struct{}
func (h Heater) heating() {
fmt.Println("heating...")
}
type Haier struct {
Heater // 注意此处不是结构体变量,而是将结构体Heater直接嵌入到Haier中
}
func (h Haier) refrigerating() {
fmt.Println("refrigerating...")
}
func main() {
h := Haier{}
h.refrigerating() // heating...
h.heating() // refrigerating...
}
此外,在实际开发中常用到空接口类型如var x interface{}
,使用空接口类型作为形参可以实现任意传参
package main
import (
"fmt"
)
func println(x interface{}) {
fmt.Println(x)
}
func main() {
a := "hello,world"
b := 100
println(a) // hello,world
println(b) // 100
}
判断接口类型与值可以使用interface.(type)
来判断,如下
package main
import (
"fmt"
)
func main() {
var x interface{}
a := "hello,world"
x = a
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("error")
}
}
方法与函数是类似的,区别在于方法是一类带有接受者(也就是实例)的特殊函数
所以在Go语言里面,方法可以看成是带有类型的函数
a Go method is a function that acts onvariable of a certain type, called the receiver. So a method is a specialkindof function ——《The Way to Go》
defer是延迟调用,执行方式是先进后出,常用于释放资源如关闭文件写入,关闭数据库连接等
与Python中的with语法较为相似
代码例子如下
package main
import (
"fmt"
)
func main() {
for i := 0; i < 5; i++ {
defer fmt.Println(i) //4 3 2 1 0
}
}
在处理Json上,Go采用了标签标识的方法来解决json字段与结构体字段对应的问题
除了标识对应关系外,还可以添加例如omitempty来标识序列化时忽略0值或Nil值
代码如下
package main
import (
"encoding/json"
"fmt"
)
type Student struct {
Name string `json:"username"`
Age int `json:"age,string"`
Gender int `json:"gender"`
Score int `json:"score,omitempty"`
}
func main() {
var data string = `{ "username":"chancel", "age":"18", "gender":0, "Score":666 }`
var s = &Student{}
var err = json.Unmarshal([]byte(data), s)
fmt.Println(err) // <nil>
fmt.Println(s) // &{chancel 18 0 666}
s.Score = 0
var jsonData, _ = json.Marshal(s)
fmt.Println(string(jsonData)) // {"username":"chancel","age":"18","gender":0}
}
资料引用