这是我参与「第三届青训营 -后端场」笔记创作活动的的第 1 篇笔记。
特点
- 高性能、高并发
- 语法简单、学习曲线平缓
- 丰富的标准库
- 完善的工具链
- 静态编译
- 快速编译
- 跨平台
- 垃圾回收
开发环境
本地
-
安装 Go
-
如果国内访问速度特别慢,配置这个
-
配置集成环境
- VS Code + Go 插件
- GoLand
基础语法
Go 语言每一行末尾不需要添加分号 ; ,但添加也不会报错。
Hello World
package main // 指定程序的包
// 导入外部的包
import (
"fmt"
)
// main 函数
func main() {
fmt.Println("Hello World")
}
在 Go 中,只有 main 包下的程序才可以执行。
使用 go run main.go 运行程序,使用 go build main.go 把程序编译成二进制文件,再使用 ./main 执行程序。
变量
Go 是强类型的语言,变量需要指定变量类型。常见的类型包括字符串、整数、浮点型、布尔型。
字符串是内置类型,可以通过加号 + 拼接,也可以用等号 == 比较两个字符串。
运算符的优先级与 C/C++ 类似。
变量的声明
变量的声明有两种方式。
一种是通过 var 关键字声明。var variable[, variable1, ...] [type] [= value [, value2, ...]]
var a = "initial"
var b,c int = 1, 2
var d = true
var e float64
通过这种方式声明,一般会自动推导类型,但也可以显式的指定 var b, c int = 1, 2。
另一种是通过 := 的方式命名。variable := value
f := float32(e)
常量的声明
与变量的 var 声明类似,只是把 var 改为 const。const variable [type] = value
注意,常量没有确定的类型,是根据使用的上下文自动确定的。
const s string = "constant"
const h = 5
const i = 3e20 / h
条件控制
if else
Go 的 if 语句与 C/C++ 类似,但有一些区别。
- if 的条件不需要添加括号,即使加了括号,在编译时也会去掉
- if 的条件后面要跟大括号,不能换行,也不能省略,即使只有一条语句
- if 语句不能写成一行,即左大括号输入后就需要换行,即使只有一条语句
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
switch
Go 的 switch 与 C/C++ 类似,但也有区别。
- switch 后面的变量名不需要括号
- 每个 case 默认不需要添加 break
- 变量可以使用任意的变量类型
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
switch 可以取代 if 语句,只需要在 switch 后不写变量名即可。
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
循环
Go 只有 for 循环,没有 while 和 do while 。
i := 1
for i <= 3 {
fmt.Println(i)
i = i + 1
}
for 循环如果不写条件,就是死循环。
for {
fmt.Println("loop")
break
}
for 循环可以使用类似 C/C++ 的三段式结构,这三段任何一段都可以省略。
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for 循环支持使用 continue 跳过循环,使用 break 跳出循环。
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
数组
数组是长度固定的,元素类型一样的数据结构类型,其中每个元素都具有编号。
Go 可以使用 var 来定义数组。var name [size]type
var a [5]int
可以在定义数组的时候初始化数组。
b := [5]int{1, 2, 3, 4, 5}
eg:定义一个二维数组并遍历
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
切片
切片与数组类似,但是切片是可变长的。
-
使用
make创建空的切片value := make([]type, len)s := make([]string, 3) s[0] = "a" s[1] = "b" s[2] = "c" fmt.Println("get:", s[2]) // c fmt.Println("len:", len(s)) // 3 -
使用
append追加元素,需要注意的是,append 后需要把结果赋值给原变量。s = append(s, "d") s = append(s, "e", "f") fmt.Println(s) // [a b c d e f] -
使用
copy(a, b)在两个切片拷贝数据c := make([]string, len(s)) copy(c, s) fmt.Println(c) // [a b c d e f] -
切片支持像 Python 那样的切片操作
fmt.Println(s[2:5]) // [c d e] fmt.Println(s[:5]) // [a b c d e] fmt.Println(s[2:]) // [c d e f] -
创建切片时赋初值
good := []string{"g", "o", "o", "d"} fmt.Println(good) // [g o o d]
map
map 是一种 Key-Value 的数据类型。map 中的元素是无序的。
-
使用
make创建空 mapvalue := make(map[key]value)m := make(map[string]int) -
访问 map 的元素
访问 map 中的 key 时,与访问数组的元素类似,中括号
[ ]中写 Key。m["one"] = 1 m["two"] = 2 fmt.Println(m) // map[one:1 two:2] fmt.Println(len(m)) // 2 fmt.Println(m["one"]) // 1 fmt.Println(m["unknow"]) // 0访问 map 中的元素时,实际是返回两个值,一个是 key 的值(key 不存在返回 0);另一个是布尔值,如果 key 存在,返回 true,否则返回 false。
r, ok := m["unknow"] fmt.Println(r, ok) // 0 false -
使用
delete删除 map 中的元素delete(map, key)delete(m, "one") -
创建有初值的 map
m2 := map[string]int{"one": 1, "two": 2} var m3 = map[string]int{"one": 1, "two": 2} fmt.Println(m2, m3)
range
可以使用 range 来遍历切片或 map
对于切片,range 会返回两个值,第一个是切片的下标,另一个是对于下标的值。
nums := []int{2, 3, 4}
sum := 0
//如果不需要下标,可以把 i 换成 _
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
对于 map,range 会返回两个值,第一个是 key, 第二个是 value。
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}
函数
在 Go 中,函数的返回类型是后置的,支持返回多个值。这在实际应用中,一般第一个是返回函数的值,第二个是返回错误信息。
函数名首字母大写表示这个函数是可导出的,即在其他程序中,只需要导入包后,即可通过 package.func 来调用这个函数。
package main
import "fmt"
//返回单个值的语法
func add(a int, b int) int {
return a + b
}
func add2(a, b int) int {
return a + b
}
//返回多个值的语法
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func main() {
res := add(1, 2)
fmt.Println(res) // 3
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok) // A True
}
指针
指针在 Go 中,一般用于对传入的值进行修改, * 用来声明一个指针。
在使用指针时, * 用来取消指针的引用, & 用来获取变量的地址。
package main
import "fmt"
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n)
fmt.Println(n) // 7
}
结构体
结构体是带类型字段的集合,是自定义数据类型。使用 type 定义结构体。
type user struct {
name string
password string
}
使用结构体的名称初始化一个变量。
//使用名字指定初始化的值,允许只初始化一部分,没有初始化的赋初始值
a := user{name: "wang", password: "1024"}
c := user{name: "wang"}
//如果没有使用名字指定,需要按照变量定义的类型和顺序传参
b := user{"wang", "1024"}
也可以使用 var 初始化一个空的结构体对象。
var d user
使用 variable.field 访问结构体的内容。
d.name = "wang"
d.password = "1024"
结构体类型也可以当作函数的参数,同时支持指针。
package main
import "fmt"
type user struct {
name string
password string
}
func main() {
fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false
}
func checkPassword(u user, password string) bool {
return u.password == password
}
func checkPassword2(u *user, password string) bool {
return u.password == password
}
结构体方法
这与 Java 等其他面向对象语言的对象方法类似。
注意,结构体方法并不是在定义结构体时写在里面,而是在 func 关键字后添加 (name struct)。
package main
import "fmt"
type user struct {
name string
password string
}
func (u user) checkPassword(password string) bool {
return u.password == password
}
//带指针可以对结构体进行修改
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
错误处理
Go 可以使用 if-else 去处理错误。
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
// 在函数返回类型中加一个 error 类型的参数,表示这个函数会返回错误
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil //如果正常,就返回值和 nil
}
}
return nil, errors.New("not found") // 如果不正常,就返回 nil 和错误
}
func main() {
// 如果不需要处理错误,可以把 err 换成 _
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name) // wang
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
字符串操作
在 strings 包中有很多处理字符串的函数。
package main
import (
"fmt"
"strings"
)
func main() {
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "ll")) // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6
}
字符串格式化
在 fmt 包中,有很多处理字符串格式化的函数。
package main
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
//不需要像 C 那样区分类型,直接用 %v,可以使用 %+v 显示详细的结果, %#v 更加详细
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
}
JSON 处理
在 Go 中,结构体中变量首字母大写为可导出的变量,类似于 Java 中的 public 变量。
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"` // json 的 tag,Age 在序列化时会转成 tag 的值
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
//使用 Marshal 函数需要保证结构体的每个变量都是共有的,即大写字母开头
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97...]
//输出 json 对象时需要使用 string(),否则会输出一些十六进制的值
//这也推出 json 对象实质是一个字符数组
//默认序列化的结果中的 key 是大写的,可以在结构体的字段后面加个 json 的 tag
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
var b userInfo
//使用 Unmarshal 进行反序列化,把一个 json 字符串转成 对象
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
时间处理
处理时间相关的函数在 time 包中。
package main
import (
"fmt"
"time"
)
func main() {
//获取当前时间
now := time.Now()
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
//time.Date(year, month, day, hour, minute, second, millisecond, zone) 构造一个时间对象
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 2022-03-27 01:25:36 +0000 UTC
//获取单个值
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
//输出格式化的时间,2006-01-02 15:04:05 是固定的
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2022-03-27 01:25:36
//获取两个时间段的差值
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
//差值的是多少分钟,多少秒
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
//把字符串解析成时间,2006-01-02 15:04:05 是固定的
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // true
//获取时间戳
fmt.Println(now.Unix()) // 1648738080
}
数字解析
有关数字解析的函数都在 strconv 中。
package main
import (
"fmt"
"strconv"
)
func main() {
//(str, 精度)
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
//(str, str进制, 精度)
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
// 0 表示自动推断 str 的进制类型
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
//把一个十进制字符串转为数字
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
//把数字转为字符串
n3 := strconv.Itoa(123)
fmt.Println(n3) // "123"
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
进程信息
通过 os 包获取进程信息。
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
// os.Args 获取进程执行时的命令行参数
// go run example/20-env/main.go a b c d
fmt.Println(os.Args) // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
//获取 或 写入环境变量
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB"))
//启动子进程并获取输入输出
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
}