前言:本教程写在Ubuntu24.04虚拟机,Go1.23.2上。
Go安装
- 命令行下载
$ wget https://golang.google.cn/dl/go1.23.2.linux-amd64.tar.gz
$ tar -zxvf go1.23.2.linux-amd64.tar.gz
$ sudo mv go /usr/local/
进入/usr/local/go文件夹
$ sudo chmod -R 0777 go
修改~/.bashrc文件
$ export PATH=$PATH:/usr/local/go/bin
$ go version
- 应用中心直接搜索Go进行下载(本人就是这样,因为目前不太熟悉Linux的操作😭)
Hello World编写
文件夹中新建一个main.go文件,写入
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
执行go run main.go或 go run .,将会输出
$ go run .
Hello World!
以下是对该文件的解读:
- package main:声明了 main.go 所在的包,Go 语言中使用包来组织代码。一般一个文件夹即一个包,包内可以暴露类型或方法供其他包使用。
- import “fmt”:fmt 是 Go 语言的一个标准库/包,用来处理标准输入输出。
- func main:main 函数是整个程序的入口,main 函数所在的包名也必须为 main。
- fmt.Println(“Hello World!”):调用 fmt 包的 Println 方法,打印出 “Hello World!”
- go run main.go,其实是 2 步:编译和执行,
go build main.go:编译成二进制可执行程序,./main:执行该程序
{% note success %}
如果报错误:go: cannot find main module; see ‘go help modules’,则是Go强制启用了 Go Modules 机制,即环境变量中设置了 GO111MODULE=on,需要先初始化模块 go mod init hello,hello为你的包名
{% endnote %}
变量及内置数据类型
var a int // 如果没有赋值,默认为0
var a int = 1 // 声明时赋值
var a = 1 // 声明时赋值
a := 1
msg := "Hello World!"
空值:nil
整型类型: int(取决于操作系统), int8, int16, int32, int64, uint8, uint16, … 浮点数类型:float32, float64 字节类型:byte (等价于uint8) 字符串类型:string 布尔值类型:boolean,(true 或 false)
var a int8 = 10
var c1 byte = 'a'
var b float32 = 12.2
var msg = "Hello World"
ok := false
str1 := "Golang"
str2 := "Go语言"
fmt.Println(reflect.TypeOf(str2[2]).Kind()) // uint8
fmt.Println(str1[2], string(str1[2])) // 108 l
fmt.Printf("%d %c\n", str2[2], str2[2]) // 232 è
fmt.Println("len(str2):", len(str2)) // len(str2): 8
- 因为字符串是以 byte 数组的形式存储的,所以,str2[2] 的值并不等于"语"。str2 的长度 len(str2) 也不是 4,而是 8( Go 占 2 byte,语言占 6 byte)
- 字符串使用
UTF8编码,打印时需要用 string 进行类型转换,否则打印的是编码值 - 应将string转存为rune数组,转换成
[]rune类型后,字符串中的每个字符,无论占多少个字节都用 int32 来表示,因而可以正确处理中文:
str2 := "Go语言"
runeArr := []rune(str2)
fmt.Println(reflect.TypeOf(runeArr[2]).Kind()) // int32
fmt.Println(runeArr[2], string(runeArr[2])) // 35821 语
fmt.Println("len(runeArr):", len(runeArr)) // len(runeArr): 4
数组的长度不能改变,如果想拼接2个数组,或是获取子数组,需要使用切片。切片是数组的抽象。 切片使用数组作为底层结构。切片包含三个组件:容量,长度和指向底层数组的指针,切片可以随时进行扩展
slice1 := make([]float32, 0) // 长度为0的切片
slice2 := make([]float32, 3, 5) // [0 0 0] 长度为3容量为5的切片
fmt.Println(len(slice2), cap(slice2)) // 3 5
// 添加元素,切片容量可以根据需要自动扩展
slice2 = append(slice2, 1, 2, 3, 4) // [0, 0, 0, 1, 2, 3, 4]
fmt.Println(len(slice2), cap(slice2)) // 7 12
// 子切片 [start, end)
sub1 := slice2[3:] // [1 2 3 4]
sub2 := slice2[:3] // [0 0 0]
sub3 := slice2[1:4] // [0 0 1]
// 合并切片
combined := append(sub1, sub2...) // [1, 2, 3, 4, 0, 0, 0]
- 声明切片时可以为切片设置容量大小,为切片预分配空间。在实际使用的过程中,如果容量不够,切片容量会自动扩展。
- sub2... 是切片解构的写法,将切片解构为 N 个独立的元素.
- go的map格式为
map[keyType]ValueType,可以通过make进行构建 - go存在指针,用于函数传参对象的修改
流程控制
- if,switch,for都没有
(),不存在while, switch不需要break,默认不继续向下执行,如需要,其他语言的break位置改为fallthrough- Go 语言中没有枚举(enum)的概念,一般可以用常量const的方式来模拟枚举。
- 对数组(arr)、切片(slice)、字典(map) 使用 for range 遍历:
for key,value:= range map{}
函数
- 函数格式(返回值放后面):
func functionname(num int ,num2 int)(int, int){TODO} - 也可以给返回值命名,相当于提前命名,返回语句可简化为 return
- 有错误返回,判断是否为nil,不是则输出错误,可以通过
errorw.New返回自定义的错误:
func hello(name string) error {
if len(name) == 0 {
return errors.New("error: name is null")
}
fmt.Println("Hello,", name)
return nil
}
- go中的defer 和 recover类似
try ...catch,使用 defer 定义异常处理的函数,如果触发了 panic,控制权就交给了 defer,在 defer 的处理逻辑中,可使用 recover,使程序执行其他操作
结构体,方法,接口
- go中的结构体(struct)类似其他语言的类,但是没有继承的概念,只能组合,结构体中方法的声明格式为
func (this *structName) functionName()(returnName){TODO} - 接口不能被实例化,一个类型可以实现多个接口。格式:
type person interface{} - 并不需要显式地声明实现了哪一个接口,只需要直接实现该接口对应的方法即可。
- 实例化结构体后可强制类型转换为接口类型,需要方法均实现,可以用以下方法判断:
var _ Interface = (*Struct)(nil)
将空值 nil 转换为 *Struct 类型,再转换为 Interface 接口,如果转换失败,说明 Struct 并没有实现 Interface 接口的所有方法。
- 接口也可以强制类型转换为实例
并发
- sync
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func download(url string) {
fmt.Println("start download", url)
time.Sleep(time.Second)//执行任务
wg.Done()//计数-1
}
func main() {
for i := 0; i < 20; i++ {
wg.Add(1)//计数+1
go download("www.baidu.com")//启动新的协程
}
wg.Wait()//等待所有协程执行结束
fmt.Println("done!")
}
- channel
var ch = make(chan string, 10) // 创建大小为 10 的缓冲信道
func download(url string) {
fmt.Println("start to download", url)
time.Sleep(time.Second)
ch <- url // 将 url 发送给信道
}
func main() {
for i := 0; i < 3; i++ {
go download("a.com/" + string(i+'0'))
}
for i := 0; i < 3; i++ {
msg := <-ch // 等待信道返回消息。
fmt.Println("finish", msg)
}
fmt.Println("Done!")
}
单元测试
- go的测试文件以
_test.go结尾,测试函数以Test开头,go test命令会自动执行以_test.go结尾的文件中的测试函数 - go的测试文件中可以包含多个测试函数,测试函数之间相互独立,不会互相影响 格式如下:
package main
import "testing"
func TestAdd(t *testing.T) {
if ans := add(1, 2); ans != 3 {
t.Error("add(1, 2) should be equal to 3")
}
}
包和模块
- 一个文件夹可以作为 package,同一个 package 内部变量、类型、方法等定义可以相互看到。
- 一个文件夹下的多个之间有依赖的文件,需要在
go run后加上两个文件的名称,或是直接在该文件夹下go run . - Go 语言也有 Public 和 Private 的概念,粒度是包。如果类型/接口/方法/函数/字段的首字母大写,则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见。
- 环境变量 GO111MODULE 的值默认为 AUTO,强制使用 Go Modules 进行依赖管理的话,可以将 GO111MODULE 设置为 ON。
- 初始化module:
go mod init module_name,当import第三方包时,会自动触发第三方包的下载,具体的版本信息也记录在了go.mod中.