Go 语言基础(6) | 青训营笔记

113 阅读7分钟

Go 的字符串格式化

来个小例子:

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}

	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
}

之前已经见过 fmt.Println(),这一个例子里主要讲的是 fmt.Printf()。这个函数和 Cprintf() 类似,不同的是这里只需要 %v 就可以打印很多种类的变量了,不用像 C 语言那样区分 %d%s 之类的格式说明。%+v 能得到更详细的结果,比如说结构体字段的名字,%#v 是比 %+v 还要详细的结果,连结构体类型都打印出来了。(据说井号是四个加号拼成的,那应该有个草字头在中间表示比一个加号详细比一个井号省略才对。。。纯属吐槽,以前有同学把 C# 叫“塞加加加加”)打印浮点数和之前的格式类似,%.2f 就是保留两位小数。

运行结果如下:

hello 123
{1 2}
s=hello
n=123
p={1 2}
p={x:1 y:2}
p=main.point{x:1, y:2}
3.141592653
3.14

Go 的 JSON 操作

关于 JSON 是什么看这里。省流版:一种 web 开发常见的数据格式,后端和前端之间的纽带。

来个小例子:

package main

import (
	"encoding/json"
	"fmt"
)

type userInfo struct {
	Name  string
	Age   int `json:"age"`
	Hobby []string
}

func main() {
	a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
	buf, err := json.Marshal(a)
	if err != nil {
		panic(err)
	}
	fmt.Println(buf)         // [123 34 78 97...]
	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
	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"}}
}

结构体序列化的操作要保证结构体的每个字段第一个字母是大写,然后用 json.Marshal() 就可以序列化。序列化的结果是一个字节数组,可以当“字符串”,要直接打印出来的话全都是十六进制编码,而转换成字符串以后才是人看的明白的字符串。

反序列化的操作就是 json.Unmarshal(),因为默认情况下转换的时候每个字段都是大写字母开头,需要小写字母开头的字段的话,在结构体定义的时候就要标明这个字段在序列化的时候要叫什么。

没介绍到的 json.MarshalIndent() 是带缩进的序列化,JSON 可以写的非常紧凑,也可以带上缩进,这样人看起来更清晰。例子里的 buf, err = json.MarshalIndent(a, "", "\t") 有三个输入,第一个是即将被序列化的结构体,第二个是前缀,第三个是缩进。打印的时候(已经转换成字符串格式了)每个 JSON 元素都单独新开一行,类似“前缀缩进 JSON 元素”这样。

例子运行结果如下:

[123 34 78 97 109 101 34 58 34 119 97 110 103 34 44 34 97 103 101 34 58 49 56 44 34 72 111 98 98 121 34 58 91 34 71 111 108 97 110 103 34 44 34 84 121 112 101 83 99 114 105 112 116 34 93 125]
{"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
{
        "Name": "wang",
        "age": 18,
        "Hobby": [
                "Golang",
                "TypeScript"
        ]
}
main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}

可以看到如果是一个非常大的结构体还是层级化的 json.MarshalIndent() 看起来更清晰,但是交给电脑去处理数据还是用 json.Marshal(),这样搞更快一点。

Go 的时间处理

来个小例子:

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
	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
	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
	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
}

最常用的是 time.Now() 获取当前时间,然后也可以用 time.Date() 来构造时间,当一个时间点对象被构造出来以后就可以获取它的年份月份日期时分秒等信息了。两个时间点之间的时间段用 Sub() 方法来计算。如果需要按照一定的日期格式来输出日期,那么需要给出日期格式,这里和其他的语言不太一样不是 YYYY-MM-DD HH:MM:SS 这种格式而是 "2006-01-02 15:04:05",这点比较诡异,其实这个日期没什么特别的含义,就是给每个时间单位上都给一个不同的数,这样不容易混淆哪一位是哪一位。一月二号下午三点四分五秒零六年西七区。我有充分的理由怀疑 Go 是一个国际化团队,有人摸鱼不开会或者不及时提交代码然后拿自己看错了时间当借口,有些地方是日/月/年,有些地方是月/日/年,有些地方是年/月/日,还有更多的格式,反正就是摸鱼了,时间就是永远看不明白。这种每个时间单位上都有一个不同的数字的做法可以不用翻译就明白哪一位是哪一位。(但是该摸鱼还是摸鱼)

image.png

然后有些系统是需要 Unix 时间戳的,用 Unix() 来将年月日时分秒这种时间转换成时间戳。

例子运行结果如下:

2023-05-17 23:36:02.8272705 +0800 HKT m=+0.000046801
2022-03-27 01:25:36 +0000 UTC
2022 March 27 1 25
2022-03-27 01:25:36
1h5m0s
65 3900
true
1684337762

Go 的数字解析

来个小例子:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	f, _ := strconv.ParseFloat("1.234", 64)
	fmt.Println(f) // 1.234

	n, _ := strconv.ParseInt("111", 10, 64)
	fmt.Println(n) // 111

	n, _ = strconv.ParseInt("0x1000", 0, 64)
	fmt.Println(n) // 4096

	n2, _ := strconv.Atoi("123")
	fmt.Println(n2) // 123

	n2, err := strconv.Atoi("AAA")
	fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}

实际上就是字符串和数字之间的转换,用到了 strconv 这个包。ParseInt() 里面第一个输入是要转换的数字字符串,第二个输入是这个数的进制(0 表示自动推测),最后一个表示数字精度。或者不用 ParseIntParseFloat 直接 Atoi()(反过来数字到字符串是 Itoa())。如果数字字符串不符合要求,可以在调用的时候把错误信息接收上打印出来看。

例子运行结果如下:

1.234
111
4096
123
0 strconv.Atoi: parsing "AAA": invalid syntax

Go 的进程信息

来个小例子:

package main

import (
	"fmt"
	"os"
	"os/exec"
)

func main() {
	// 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
}

os.Args 获取的是执行这个文件需要的参数,os.Getenv() 获取环境变量,os.Setenv() 写入环境变量,exec.Command() 是开子进程然后执行,可以获取输入输出。

例子运行结果如下:

[/tmp/go-build103122862/b001/exe/main a b c d]
一大堆环境变量,我这里不贴了,有凑字数的嫌疑
<nil>
127.0.0.1       localhost
127.0.0.1       kubernetes.docker.internal