【字节跳动青训营】后端笔记整理-1 | Go语言入门指南:基础语法和常用特性解析(二)
+https://developer.aliyun.com/article/1521856?spm=a2c6h.13148508.setting.14.439a4f0ez37VAK
八、错误处理
在 Go 语言里,符合语言习惯的做法是使用一个单独的返回值来传递错误信息。
不同于 Java 使用的异常,Go语言的处理方式能够很清晰地知道是哪个函数返回了错误,并且能用简单的 if else 来处理错误。
在函数定义时,可以在函数的返回值类型列表后加一个 error, 代表这个函数可能会返回错误。那么在函数实现的时候, return 就需要同时 return 两个值:如果出现错误,那么 return nil 和一个 error;如果没有错误,那么返回原本的结果和 nil。
package main import ( "errors" "fmt" ) type user struct { name string password string } func findUser(users []user, name string) (v *user, err error) { for _, u := range users { if u.name == name { return &u, nil } } return nil, errors.New("not found") } func main() { 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 包里有很多常用的字符串工具函数,比如contains判断一个字符串里是否包含有另一个字符串 , count 字符串计数, index 查找某个字符串的位置。 join 连接多个字符串 repeat 重复多个字符串 replace 替换字符串。
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 }
字符串的格式化
Go 语言中的格式化占位符和 C 语言中的格式化占位符在某些方面是相似的,它们用于将变量的值以特定格式插入到字符串中。以下是一些关于 Go 和 C 中格式化占位符的比较:
相似
- %d: 用于格式化整数(十进制)。
- %f: 用于格式化浮点数。
- %s: 用于格式化字符串。
- %c: 用于格式化字符。
- %p: 用于格式化指针地址。
不同
- 在 Go 语言中,没有像 C 语言中的 %i 一样的格式化占位符。在 Go 中,可以使用 %d 来格式化整数(十进制)。
- Go 语言中的 %v 是一个通用的占位符,可以用于格式化任何类型的变量。它会根据变量的类型自动选择合适的格式。
- Go 语言中的 %T 用于格式化变量的类型。
- 在 C 语言中,一些特定于整数长度的占位符(如 %ld、%lld 等)用于格式化长整型。而在 Go 中,可以使用 %d 格式化不同长度的整数,Go 会自动处理。
在标准库的 fmt 包里面有很多的字符串格式相关的方法,可以很轻松地用 %v 来打印任意类型的变量,而不需要区分数字字符串。也可以用 %+v 打印详细结果,%#v 则更详细。
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 }
十、JSON处理
在 Go 编程语言中,JSON(JavaScript Object Notation)处理是一项常见的任务,用于在应用程序之间传递和存储结构化的数据。Go 提供了内置的标准库来处理 JSON 数据,使得编码(将数据转换为 JSON 格式)和解码(将 JSON 数据转换为数据结构)变得非常容易。
对于一个已有的结构体,我们可以什么都不做,只要保证每个字段的第一个字母是大写(也就是是公开字段),那么这个结构体就能用 json.Marshal 序列化,变成一个 JSON 的字符串。序列化之后的字符串也能够用 json.Unmarshal 反序列化到一个空的变量里。
以下是在 Go 中处理 JSON 数据的基本流程:
1. 编码( 序列化 ):将数据转换为 JSON 格式。
使用 encoding/json 包中的 Marshal 函数可以将 Go 数据结构编码为 JSON 格式的字节流。这通常用于在应用程序中将数据发送到其他系统或存储到文件中。
json.Marshal()返回的是一个字节切片 []byte和一个err,通过把字节切片转换成字符串,看到其中的内容。
package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Email string `json:"email"` } func main() { person := Person{Name: "Alice", Age: 30, Email: "alice@example.com"} jsonBytes, err := json.Marshal(person) if err != nil { fmt.Println("JSON encoding error:", err) return } fmt.Println(string(jsonBytes)) //将字节切片转换为字符串,查看内容 }
输出:
可以看到,这样转换过来的json格式可读性较差,包含很长的字符串,并且没有空白缩进。为了生成便于阅读的格式,另一个json.MarshalIndent
函数将产生整齐缩进的输出。该函数有两个额外的字符串参数用于表示每一行输出的前缀和每一个层级的缩进:
data, err := json.MarshalIndent(person, "", " ") if err != nil { log.Fatalf("JSON marshaling failed: %s", err) } fmt.Printf("%s\n", data)
当需要把多个结构体(即一个结构体切片中的各个元素)都转换成json格式时,这个函数就非常有用了。
2. 解码(反 序列化 ):将 JSON 数据转换为 Go 数据结构。
使用 encoding/json 包中的 Unmarshal 函数可以将 JSON 格式的字节流解码为相应的 Go 数据结构。这通常用于从外部系统获取 JSON 数据并在应用程序中进行处理。
package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Email string `json:"email"` } func main() { jsonStr := `{"name":"Bob","age":25,"email":"bob@example.com"}` var person Person err := json.Unmarshal([]byte(jsonStr), &person) if err != nil { fmt.Println("JSON decoding error:", err) return } fmt.Println("Name:", person.Name) fmt.Println("Age:", person.Age) fmt.Println("Email:", person.Email) }
结构体字段的标签(json:"field"
)可以用于指定 JSON 键与 Go 结构体字段之间的映射关系。这在编码和解码过程中非常有用,确保正确的字段匹配。
补充代码示例:
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"}} }
十一、时间处理
在Go语言里面最常用的就是 time.Now() 来获取当前时间。
也可以用 time.Date 构造一个带时区的时间。
t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()有很多方法来获取这个时间点的年 月 日 小时 分钟 秒。
用点 .Sub 对两个时间进行减法,得到一个时间段diff。时间段diff又可以得到它有多少小时,多少分钟、多少秒:diff.Minutes(), diff.Seconds()
在和某些系统交互的时候,我们经常会用到时间戳,可以用 .Unix 来获取时间戳。
t.Format() 可以将时间进行格式化。
time.Parse()用于将字符串解析为时间对象。它在处理时间格式的字符串时非常有用,可以将字符串转换为对应的 time.Time 类型。
package main import ( "fmt" "time" ) func main() { now := time.Now() fmt.Println(now) // 2023-08-10 20:49:44.8566865 +0800 CST m=+0.007017101 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) //t2和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 }
十二、数字解析
Go语言中,关于字符串和数字类型之间的转换都在 strconv 这个包下。这个包是 string convert 这两个单词的缩写,可以用其中的 ParseInt() 或者 ParseFloat() 来解析一个字符串,把字符串按照要求转换为相应的整数。
我们还可以用该包中的 Atoi() 把一个十进制字符串转成数字,Atoi 是ASCII to Integer的缩写。反过来,可以用 Itoa() 把数字转成字符串。如果输入的参数不合法,那么这些函数都会返回error。
Atoi()和ParseInt()这两者的基本功能是相同的,都是将字符串解析为整数,但是 ParseInt 提供了更多的选项来处理不同的解析需求。例如,ParseInt 允许指定解析的进制(比如二进制、十六进制等),也可以指定结果的位数(比如 32 位整数或 64 位整数)。
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 }
十三、进程信息
- 在Go里我们能够用 os.Args 来得到程序执行时的指定的命令行参数。os.Args 是一个字符串切片,它包含了传递给程序的所有命令行参数,包括程序名称本身。比如我们编译的一个二进制文件,在command命令后接a b c d 来启动,那么 os.Args 会是一个长度为 5 的 slice。第一个成员代表程序名称本身: /var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main,而命令行参数是a、b、c、d。
- os.Setenv用于读取环境变量的值。
- exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()则用于在命令行执行 grep 命令,查找文件 /etc/hosts 中包含 127.0.0.1 的行。exec.Command 创建了一个命令对象,.CombinedOutput() 方法运行该命令并捕获其标准输出和标准错误输出。如果命令执行出错,会抛出错误。
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 }
十四、Go语言特性
Go语言的特点有:
- 高性能、高并发。
- 语法简单、学习曲线平缓。
- 标准库丰富,工具链完善。很多情况下,不需要借助第三方库,就能完成大部分基础功能的开发。
- 静态链接。
- 快速编译,拥有静态语言中几乎最快的编译速度。
- 可跨平台。
- 垃圾回收。
Go语言的“语法简单”在很多地方都有体现,如以下HTTP服务器的Go语言代码案例:
Go语言代码实现HTTP服务器
package main import ( "net/http" ) func main() { http.Handle("/", http.FileServer(http.Dir("."))) //路由处理 http.ListenAndServe(":8080", nil) //服务器监听与启动 }
该代码只有10行,而核心的代码只有main函数中的两行。其中,http.Handle(“/”, http.FileServer(http.Dir(“.”))) 用于路由处理,使用 http.FileServer 函数创建一个路由处理器,并将其映射到根路径 (“/”)。http.FileServer 函数接受一个参数,即一个表示要提供的文件目录的 http.Dir 类型。
而 http.ListenAndServe(“:8080”, nil) 用于服务器的监听与启动,这行代码启动了一个 HTTP 服务器,监听本地主机的 8080 端口。http.ListenAndServe 函数接受两个参数,第一个参数是监听地址(在这里是 “:8080”),第二个参数是一个处理器(这里是 nil,表示使用默认的路由处理器)。
这样一来,这段代码就创建了一个简单的本地服务器,它会将访问根路径的请求映射到当前程序所在的目录,并在本地的 8080 端口上启动一个服务器,允许用户通过浏览器或其他 HTTP 客户端访问这些文件。
我们可以在Go语言的开发环境GoLand中运行该代码片段,并随便导入一个html文件来进行测试:
运行go程序,启动服务器,访问localhost:8080/login.html,会发现,确实可以访问到该静态页面:
Go语言的基本语法入门就到这里。更为细致的知识点和用法,可以从Go语言的官方文档寻求帮助。