作者:Chancel Yang, 创建:2023-07-18, 字数:10730, 已阅:350, 最后更新:2023-09-22
Go语言在网络编程、系统编程、并发编程和分布式编程等领域非常常见,一些常见的应用程序,如Docker、Kubernetes和Terraform等,都是使用Go语言开发的
Go是一种编译型语言,并且自带编译器,具有快速编译(包括交叉编译)和高效性能的特点
作为一门编程语言,Go具有以下特性:
Go语言简洁的语法、强大的并发支持、高效的内存分配、丰富的标准库以及完整的工具链,使其成为一门适合开发各种类型应用的强大编程语言
Go的开发环境部署较为简单,IDE方面首推Goland
/Visual Studio Code
本次搭建基于Ubuntu2204
,其他操作系统环境可参考步骤
创建golang的安装文件夹
mkdir -p $HOME/apps/golang && cd $HOME/apps/golang
wget https://go.dev/dl/go1.18.1.linux-amd64.tar.gz
tar -zxvf https://go.dev/dl/go1.18.1.linux-amd64.tar.gz
添加$HOME/apps/golang/go/bin
到PATH
中
sed -i '$a PATH=$PATH:$HOME/apps/golang/go/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 provides access to operations on modules.
在当下的go版本中,go mod
是默认开启的,可以使用go env
来检查,
# 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
此时项目结构如下
❯ tree
.
├── go.mod
├── go.sum
└── main.go
go.mod
是go modules的配置文件,go.sum
记载了依赖包的版本与校验信息
此时我们运行项目,访问http://localhost:8080/
即可以看到hello world
Visual Studio Code
对于Golang开发也提供强大的支持,以上述的hello-world
项目为例,在终端用vsc打开该项目
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}
}
资料引用