Go 语言是一门高性能高并发,语法简单,具有完善工具链,可静态链接与快速编译,可跨平台以及支持垃圾回收的编程语言,在处理后端业务上具有显著优势。本文旨在记载 go 语言的入门使用,为以后复习提供参考。
选用编辑器
· VScode 主流全能编辑器,配置了 go 插件即可。
· GoLand 专门用于进行 go 语言开发的编辑器,支持代码补齐,语法修改,代码提示,自动引包等优势,用起来相当丝滑。
关于使用
· go 语言依靠 Module 进行项目管理,要合理的运行管理一个 go 项目需要在 main 所在根目录下设置 go.mod 文件,编译运行前记得执行命令 go mod tidy 以完善 go.mod 和生成 go.sum 的信息。
· 经过一番折腾,目前得出结论是如果要引用自定义包,那么自定义包放置位置需要在 GOPATH\src 对应目录下。或许有不用把项目放到 GOPATH/src 也能使用相对路径实现引包的方法,但是目前没有实践出来。
关于语法
输出打印
放在 fmt 包中,可用 fmt.Println(a ... any) 的方式打印。
如果不想处理其中错误信息,可以使用 fmt.Printf(string, a ... any) 可以打印一个字符串。
变量初始化
有两种方式:
- 采用
var关键字,对给定变量初始化,初始化方式可以是直接赋值,也可以是指定初始化类型。 - 采用
target := (Type) value的形式同时完成类型和值的初始化。
常量初始化采用关键字 const, 形如 const h string = "hello" 必须完成值的初始化。
For 循环
有几种使用方式:
-
什么都不加,直接开始,但是需要设置出口
break, eg:for { // Do something. break } -
类似 C++ 的写法,但是不用括号,eg:
for i := 1; i < 10; i++ { // Do something. } -
单纯给一个判断,此时用法像是其它语言的 where, eg:
for i <= 3 { // Do something. i = i + 1 }
If-else 条件判断
使用方法和其他语言没什么不同,就是正常 if-else 逻辑
if JUDGE1 {
// Do something.
} else if JUDGE2 {
// Do something.
} else {
// Do something.
}
但是有些不同点在于,条件判断可以同时完成某些初始化,eg:
if num := 9; num < 0 {
// Do something.
} else {
// Do something.
}
Switch-case
总体和 C++ 使用方式相近,不同点在于:
- 每一个 case 后面不用加 break 也可以实现结束这一个 case 后自动结束。
- case 的 key 不一定是常量,可以是一个运算量,也可以是多个值。
示例:
switch a {
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
case 3, 4:
fmt.Println("3,4")
default:
fmt.Println("other")
}
switch {
case t.Hour() < 12:
fmt.Println("less")
default:
fmt.Println("more")
}
数组操作
数组的定义十分简单,和普通变量定义没什么区别,只需要在定义时加上数组关键字,如
var a [5]int
b := [5]int{1, 2, 3, 4, 5}
· 同时其也支持二维数组,如 var twoD [2][3]int
· 如果不定长度,可以先初始化再使用 make 函数进行内存分配,如
var a []Type
a = make([]Type, Size)
· 同时该数组也支持 C++ 中对 vector 容器的一些抽象接口实现,如 append 等。
· 关于数组遍历:
-
可以采用普通的 for 循环,即
for i := 0, i < len(arr), i++ { // ... } -
可以使用 range 遍历,如
for i, element := range Arr { // Do something. }同时注意:
range不止可以遍历数组,还可以遍历一般的迭代器,实现类似 C++ 的迭代器遍历功能,对 map 的 key-value 对也能遍历。
· 关于输出:函数 fmt.Println() 可以接受多个参数,已经对数组接口做了抽象,直接输出即可,这点比 C++ 抽象更强。
· 关于切片:类似 Python 中的切片思路,Go 对数组也实现了切片操作,方式和 Python 切片方式差不多, Arr[2:5] 表示切片提取其中的 2,3,4 号元素,即取首不取尾。而 [:3], [2:] 之类的切片分别表示从其实开始切片到给定位置,以及从给定起点切片到最后。
Map
map 是一类存储 key-value 键值对的容器,可以通过 Map[key] 的形式获取对应的值.
· 初始化方式有:
- 使用 make 函数初始化,如
m: make(map[keyType]valueType). - 使用 var 关键字,如
var m = map[string]int{"one":1, "two":2}. - 使用
:=方式,如m := map[string]int{"one":1, "two":2}.
· 索引返回值还有一个是否获取的标志,即 value, ok := map[key] 其中 ok 在搜索到键值的时候为 true, 搜不到时为 false.
· 打印:直接使用 fmt.Println() 即可。
· 删除键值对:使用 delete 函数,如 delete(Mao, key).
· 遍历:使用 range 遍历,如
for k, v := range m {
// Do something. k --> key, v --> value
}
for k : range {
// Do something. k --> key
}
函数
Go 的函数定义也很简洁,返回值放在声明后面,形如
func f(a int, b string) (int, error) {
// Do something.
}
func g(x, y float64) (z float64) {
// Do something.
}
注意,此处的形参为拷贝传入,在函数中对形参的修改并不能改变原来的实参,如果需要改变原来实参,需要采用指针传入,如
func add(n *int) { // 如果 n 只是传入 int, 则原来传入值不改变
*n += 2
}
func main() {
n := 5
add(&n) // n 需要取地址传入,给函数指针
// Do something.
}
结构体
Go 提供了定义结构体的方式,可以实现不同类型的抽象封装,构造定义是 type 关键字,如
type user struct {
name string
password string
}
初始化时可以按照顺序直接初始化每个元素,也可以用关键字初始化,如
a := user{"Harry", "12345"}
var b = user{name: "Harry", password: "12345"}
· 索引元素使用 . 关键字,可以实现查找和对元素的修改。
· 打印:使用 fmt.Println() 可以直接打印
· 关于初始化时的 tag 标签:当这个结构体要转化为一些特定输出(比如 gorm, json 等)的时候,其结构体的 key 和输出对应的 key 可能不一样,也可能不是所有结构都需要给定值写入等,此时可以采用 tag 标签让结构体的元素在转化时有标准转化形式,如
type user struct {
// 要求结构体 key 首字母大写才被 json 写入,为了转为小写用 tag 修改
Name string `json:"name"`
// 如果想要把 password 设置为可以不写入时初始化,加上 omiempty 的 tag 标识
Password string `json:"password, omiempty"`
}
当然,此处写入时会涉及到一些零值不被读取的问题,要解决这个问题需要把结构体元素类型定义为指针类型,如 size *int 等。
· 结构体方法:形如 C++ 中的 class 概念,go 通过结构体方法实现了对结构体内部函数的统一调度,如
// 不可修改原结构体,只是使用元素
func (u user) checkPassword(password string) bool {
return u.password == password
}
// 传入指针,可以对原结构体进行修改
func (u *user) resetPassword(password string) {
u.password = password
}
调用方式:a.resetPassword("2048"). 类似 C++ 中的用法。
报错 error
go 提供了一个统一的报错形式 error 结构,可以处理统一的报错,一般使用放在函数返回的第二个参数,如
u, err := findUser(Some target)
if err != nil {
// Handle error
}
String
字符串也是一个 go 封装好的结构体,使用方式和 C++ 差不多,有一些细微的使用接口差别,可读性很强,支持搜索,计数,大小写转换,拼接,替换,分片等一系列操作。
时间相关 time
go 也对调用时间封装了一个很好的包 time,支持很多功能:
- 获取当前时间:
time.Now() // 20xx-xx-xx xx:xx:xx.xxxxxx +0800 CST m=+0.000087933 时间格式. - 通过给定时间拼装成一个时间:
time.Date(2023, 8, 20, 1, 25, 36, 0, time.UTC). - 获取具体的年月日等:
t.Year(), t.Month(), etc. - 计算两时间插值:
t2.Sub(t1). - 转化时间为整数:
time.Now().Unix()转化为从1970年某个点开始到现在时刻的秒数。 - ... ...
类型转化
类型转化有主要有直接调用 Type(target) 的函数做强制转化或者使用 strconv 包进行转化。
· 对于一些简单的转化,比如 int 转为 int64 这种类型,可以直接转化,如
var a int = 1
var b int64
b = int64(a)
· 对于一些复杂的转化,需要使用 strconv 的函数(返回值中除了转换值还有个 error 表示是否转换成功),如
// string --> float64
strconv.ParseFloat("1.234", 64) // 1.234
// string --> int64
strconv.ParseInt("111", 10, 64) // 111, 第二个参数表示10进制,第三个参数表示 int64
strconv.ParseInt("0x1000", 0, 64) // 4096, 第二个参数为0则采用默认转换,0x 表示16进制
// string --> int
strconv.Atoi("123") // 123
总结
总的来说 go 语法对有 C++ 或其他语言基础的人来说很友好,基本上看上两三天就会了,但是其中的工具包使用和语法结构比较复杂,需要进行比较多的训练。