下面是本次课程的学习笔记,简单记录自己的学习思路和关注的知识点。
1 GO基础语法
1.1 什么是go
首先介绍了go作为一门编程语言,具有什么样的优点、特性
其中,丰富的标准库能够使开发者减少对第三方库的依赖;跨平台就不用交叉编译,更加方便;垃圾回收使得开发者不需要关注内存的使用和释放,更好地专注自己的功能开发。
1.2 go在业务中的优势
2 go开发环境
首先安装go,到官网下载安装包,然后根据提示安装即可。 我用的mac m1,之前就已经安装过了,这里就不重复安装了。
然后我选用vs code作为ide.
安装成功后,使用和python相似,使用go命令。下面是查看自己安装的go的版本的指令。
课程还提供了在线云IDE,gitpods ,可以直接用准备好的云环境,运行示例项目。
3 基础语法
因为我先学的是C和python,自我感觉,编程风格还是蛮像二者的结合的。下面的很多学习也会和这两个比较一下,提醒我自己。
3.1 go的helloworld程序
学习编程的第一步都是helloworld。新建一个main.go, 写入以下代码
package main
import ("fmt")
func main() {
fmt.Println("hello world")
}
可以看到程序有结构清晰的三个部分
- package: 声明程序属于哪个包, main包就是程序的入口文件
- import: 导入包,fmt是标准库,用于标准输入输出和格式化操作。
- func: 程序的函数体,这里调用了fmt的Println,顾名思义就是打印函数。
之后写代码的时候显然主要的也就是这三部分,声明当前程序的包, 引入需要使用的库,写函数。
然后运行这个程序,使用的命令是
go run mian.go
除了直接运行,还可以编译后运行
go build main.go
./main
3.2 变量类型
go是一门强类型语言,每一个变量都有变量类型。声明的时候,变量类型是在变量名之后的,和C相反。
使用变量时,变量的声明有两种方式。
- var 变量名 变量类型 = 值 (变量类型不是必须的,可以由编译器直接推导
- 变量名 := 变量类型 值
基础类型有字符串、整型、浮点型、布尔型。
var a string = "initial"
g := a + "foo"
go中字符串是内置类型,可以直接加号拼接。 字符串得用双引号括起来。
var b,c int = 1,2
const h = 50000
d := 1
var e float64
f := float32(e)
const 就是常量。 go中的常量没有确定的变量类型,可以根据使用的上下文自动确定类型。
浮点型有float64和float32
var g = true
3.3 控制流
所有的语言的控制流不外乎选择和循环。
3.3.1 if-else
package main
import "fmt"
func main(){
if 1 > 0 {
fmt.Println("1 is positive")
} else if 1 < 0{
fmt.Println("1 is negative")
} else {
fmt.Println("1 is 0")
}
}
if-else 条件不需要括号,必须要用{}
3.3.1 for循环
go中只有for循环 for不写任何条件就是死循环。 循环的结构和C是很相似的,也可以省略三个条件中任意条件,比如简化成类似while的写法。
i := 1
for i <= 3 {
i = i + 1
}
for n := 0; n < 5; n++ {
if n%2 == 0{
continue
}
}
3.3.2 switch
go中的switch不需要break,程序只会选择一个分支。 而且switch功能更加强大,支持任意的数据类型。
switch a {
case 1:
b := 1
case 2,3:
b := 3
}
还可以用来取代if-else,在switch后面不加任何变量
t := time.Now()
switch {
case t.Hour() < 12:
pass
default:
pass
}
当有很多个If-else分支的时候,可以更加清晰
3.3.3 数组
具有编号且长度固定的元素类。 go的数组相比C和python就有点奇形怪状了
var a [5]int
b := [5]int{1,2,3,4,5}
var d [2][3]int
len(a)
表示数组的[]放在元素类型之前,其中表示数组的长度。数组长度固定,因此不太常用。
3.3.4 切片
数组的长度是固定的,这点和C相似,但是go有类似python动态数组的切片。 切片是可以任意更改长度的数组,用法和数组都相同。 创建切片使用make,可以指定长度
s := make([]string,3)
s = append(s, "d")
append的用法和python中很不相同,首先不是一个类的方法,其次,append完后必须赋值给原来的数组。 因为append本质上是对数组扩容创建了一个新的切片,所以需要赋值回去。 切片具有拷贝操作,也有和python中的切片相同的操作,但是没有负数操作。
c := make([]string, len(s))
copy(c, s)
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"}
good = append(good, "bye")
fmt.Println(good) // [g o o d]
3.3.4 map
类似c中的哈希表,python中的字典。map是完全无序的,不会按插入顺序输出。 也是用make创建, 创建的时候,map[key]value,其中一个指定key的数据类型,一个指定value的数据类型。 访问的时候,使用方括号来指定key的值。 go中的map访问不存在的key的时候不会报错,会给出默认值。还可以添加一个变量,比如下面的ok, 表示是否存在这个key。
m := make(map[string]int)
m["one"] = 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
delete(m, "one")
删除用的是delete。
3.3.5 range
用于遍历,使用非常简单,配合for使用,在range 后加上遍历的对象,赋值给输出变量。 可以遍历数组
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
使用range的时候会返回两个值,一个是索引,一个是值。 还可以遍历map,返回的是key和value,因为map的无序性,每次的输出结果可能是不同的。
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
}
3.3.6 函数
使用关键字func定义函数,注意变量类型是后置的。
func add(a int, b int) int {
return a + b
}
函数支持返回多个值。一般比较常见的是,第一个值是真正的返回结果,第二个值是错误信息。比如下面的ok
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
3.3.7 指针
相比C和C++中的指针,支持的操作比较有限,最主要的用途是对参数进行修改 比如go中函数调用传递参数的时候,传入的是拷贝,而不是真正的地址。
func add2(n int) {
n += 2
}
这样写的话,调用函数不会修改原来的变量n,函数失效后,+2的n也就失效了,因为它只是一份拷贝。 所以需要指针, 传入参数时和C一样,需要加上取址符&,使用的时候需要加上*表示对指针取值。
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n)
fmt.Println(n) // 7
}
指针的声明, 在变量前加上*。 指针的赋值,就是将变量取址后赋值给指针
n := 5
var p *int
p = &n
C的指针那么难,go中的指针应该也有挺多值得研究的,之后在细看。
3.3.8 结构体
结构体是带类型的字段的集合。
type user struct {
name string
password string
}
初始化,没有被初始化的字段会赋值默认值。
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
访问字段,用.来访问字段
var d user
d.name = "wang"
d.password = "1024"
结构体也有指针,可以用指针传递结构体参数,避免拷贝的开销。
3.3.9 结构体方法
类似于类的成员函数。 先看不用结构体方法实现的函数
func checkPassword(u user, password string) bool {
return u.password == password
}
写成结构体方法的时候,不在class里面,而是指定对应的结构体,从参数挪到func后面函数名前,加上括号。 使用的时候和类调用成员函数一样,用.来访问方法。
func (u user) checkPassword(password string) bool {
return u.password == password
}
// 带指针才能对结构体进行修改
func (u *user) resetPassword(password string) {
u.password = password
}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
有带指针和不带指针两种写法,带指针才能修改结构体。
3.3.10 错误处理
使用一个单独的返回值来传递错误信息。
在Go语言中,error 是一个内置的接口类型,用于表示错误条件。
接口方法 error.New(),该方法返回一个字符串,描述了错误的具体内容。nil 值表示没有错误发生。
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")
}
可以用简单的if-else处理错误,需要判断error是否存在,没有error说明有返回值,否则存在空指针错误。
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
3.3.11 字符串操作
内置的strings类型,具有很多种方法。
- strings.Contains(str, substr) 判断str是否包含substr
- strings.Count(str,substr) 字符串计数
- strings.Index(str, substr) 查找某个字符串的位置
- strings.Join(str1, str2, concat) 连接字符串
- strings.Repeat(str1, num) 重复num次字符串str
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
}
3.3.12 字符串格式化
fmt标准库中具有很多种方法。
比如Println,可以打印多个变量并且换行 然后就是Printf, 和C很像, 不同的是,可以用%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} 打印出结构体的完整Go语法表示,包括类型和字段值
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
3.3.13 json处理
一个已有的结构体,只要每个字段的首字母是大写,就可以用json.Marshal进行序列化得到byte数组。
可以使用json标签指定在JSON中使用的字段名,比如下面的age。
type userInfo struct {
Name string
Age int `json:"age"` // json别名
Hobby []string
}
反序列化用json.Unmarshal
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"]}
// 使用`json.MarshalIndent`函数将结构体实例`a`编码为格式化的JSON字符串,其中`""`表示最外层不添加任何前缀,`\t`表示每个层级使用制表符进行缩进。
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"}}
3.3.14 时间处理
引入time模块。
- time.Now() 获取当前时间
- time.Date() 获取某个时区的时间
- time.Unix() 获取时间戳
now := time.Now()
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.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
3.3.15 数字字符串解析
有一个表示数字的字符串,可以直接解析成数字。需引入标准库 strconv
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)
n3 := strconv.Itoa(123)
fmt.Println(n3) // 123
3.3.16 进程信息
引入os 获取进程的命令行参数
// 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"))
通过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