Java Java 字节青训营后端笔记——Go基础语法 Alive~o.0 2024-08-01 2024-11-03 变量
FMT包。这个包主要是用来往屏幕输入输出字符串、格式化字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "fmt" "math" ) func main () { var a = "initial" var b, c int = 1 , 2 var d = true var e float64 f := float32 (e) g := a + "foo" fmt.Println(a, b, c, d, e, f) fmt.Println(g) const s string = "constant" const h = 500000000 const i = 3e20 / h fmt.Println(s, h, i, math.Sin(h), math.Sin(i)) }
go语言是一门强类型语言,每一个变量都有它自己的变量类型。
字符串是内置类型,可以直接通过加号拼接,也能够直接用等于号去比较两个字符串。
大部分运算符的使用和优先级都和C或者C++类似。
变量的声明
一种是通过var name string="这种方式来声明变量,声明变量的时候,一般会自动去推导变量的类型。
另一种声明变量的方式是:使用变量冒号:=等于值。
常量
把var改成cost,go语言里面的常量没有确定的类型,会根据使用的上下文来自动确定类型。
if-else
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" func main () { if 7 %2 == 0 { fmt.Println("7 is even" ) } else { fmt.Println("7 is odd" ) } if 8 %4 == 0 { fmt.Println("8 is divisible by 4" ) } 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" ) } }
写法和C或者C++类似。
if后面没有括号。如果括号的话,那么在保存的时候你的编辑器会自动把你去掉。
if后面必须接大括号,不能像C或者C++一样,直接把f里面的语句同一行。
switch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "fmt" "time" ) func main () { 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" ) } t := time.Now() switch { case t.Hour() < 12 : fmt.Println("It's before noon" ) default : fmt.Println("It's after noon" ) } }
同样的在switch后面的变量名,并不要括号。在c++里面,switch case如果不不显示加break的话会然后会继续往下跑完所有的case,在go语言里面的话是不需要加break的。
相比C或者C++,go语言里面的switch功能更强大。可以使用任意的变量类型 ,甚至可以用来取代任意的if else语句。可以在switch后面不加任何的变量,然后在case里面写条件分支。这样代码比用多个if else代码逻辑会更为清晰。
循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport "fmt" func main () { i := 1 for { fmt.Println("loop" ) break } for j := 7 ; j < 9 ; j++ { fmt.Println(j) } for n := 0 ; n < 5 ; n++ { if n%2 == 0 { continue } fmt.Println(n) } for i <= 3 { fmt.Println(i) i = i + 1 } }
go里面没有while循环、do while循环,只有唯一的一种for循环,可以用break或者continue来跳出或者继续循环。
数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func main () { var a [5 ]int a[4 ] = 100 fmt.Println("get:" , a[2 ]) fmt.Println("len:" , len (a)) b := [5 ]int {1 , 2 , 3 , 4 , 5 } fmt.Println(b) var twoD [2 ][3 ]int for i := 0 ; i < 2 ; i++ { for j := 0 ; j < 3 ; j++ { twoD[i][j] = i + j } } fmt.Println("2d: " , twoD) }
在真实业务代码里面,很少直接使用数组,用的更多的是切片。
slice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport "fmt" func main () { s := make ([]string , 3 ) s[0 ] = "a" s[1 ] = "b" s[2 ] = "c" fmt.Println("get:" , s[2 ]) fmt.Println("len:" , len (s)) s = append (s, "d" ) s = append (s, "e" , "f" ) fmt.Println(s) c := make ([]string , len (s)) copy (c, s) fmt.Println(c) fmt.Println(s[2 :5 ]) fmt.Println(s[:5 ]) fmt.Println(s[2 :]) good := []string {"g" , "o" , "o" , "d" } fmt.Println(good) }
切片不同于数组可以任意更改长度,可以用mak来创建一个切片,可以像数组一样去取值,使用append来追加元素。
注意append的用法,必须把append的结果赋值为原数组。
因为sice的原理实际上是它有一个它存储了一个长度和一个容量,加一个指向一个数组的指针,在你执行append
操作的时候,如果容量不够的话,会扩容并且返回新的sice。
slice此初始化的时候也可以指定长度。sice拥有像python一样的切片操作,不过不同于python,这里不支持负数索引。
map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func main () { m := make (map [string ]int ) m["one" ] = 1 m["two" ] = 2 fmt.Println(m) fmt.Println(len (m)) fmt.Println(m["one" ]) fmt.Println(m["unknow" ]) r, ok := m["unknow" ] fmt.Println(r, ok) delete (m, "one" ) m2 := map [string ]int {"one" : 1 , "two" : 2 } var m3 = map [string ]int {"one" : 1 , "two" : 2 } fmt.Println(m2, m3) }
用make来创建一个空map,需要两个类型,第一个是key类型,这里是string;另一个是value的类型,是int。
可以从里面去存储或者取出键值对。可以用delete从里面删除键值对。
golang的map是完全无序的,遍历的时候不会按照字母顺序,也不会按照插入顺序输出,而是随机顺序。
range
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func main () { nums := []int {2 , 3 , 4 } sum := 0 for i, num := range nums { sum += num if num == 2 { fmt.Println("index:" , i, "num:" , num) } } fmt.Println(sum) m := map [string ]string {"a" : "A" , "b" : "B" } for k, v := range m { fmt.Println(k, v) } for k := range m { fmt.Println("key" , k) } }
对于一个slice或者一个map的话,可以用range来快速遍历。
range遍历的时候,对于数组会返回两个值,第一个是索引,第二个是对应位置的值。如果不需要索引可以用下划线来忽略。
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "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) v, ok := exists(map [string ]string {"a" : "A" }, "a" ) fmt.Println(v, ok) }
实现两个变量相加的函数。变量类型是后置的。
Golg里面的函数原生支持返回多个值。在实际的业务逻辑代码里面几乎所有的函数都返回两个值,第一个是真正
的返回结果,第二个值是一个错误信息。
指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func add2 (n int ) { n += 2 } func add2ptr (n *int ) { *n += 2 } func main () { n := 5 add2(n) fmt.Println(n) add2ptr(&n) fmt.Println(n) }
指针的一个主要用途就是对于传入参数进行修改。
这个函数试图把一个变量+2。但是单纯像上面这种写法其实是无效的。为了类型匹配,调用的时候会加一个&符号,进行地址传递而非值传递。
结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "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" )) }
结构体是带类型的字段的集合。这里user结构包含了两个字段,name和password。可以用结构体的名称去初始化一个结构体变量,构造的时候需要传入每个字段的初始值。也可以用键值对的方式去指定初始值,这样可以只对一部分字段进行初始化。结构体也能支持指针。
可以为结构体去定义一些方法。会有一点类似其他语言里面的类成员函数。比如上面例子的checkPassword的实现,从一个普通函数,改成了结构体方法。这样用户可以像a.checkPassword(“Xx”)去调用。具体的代码修改,就是把第一个参数,加上括号,写到函数名称前面。
在实现结构体的方法的时候也有两种写法,一种是带指针,一种是不带指针。性质同地址传递和值传递。
错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport ( "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) if u, err := findUser([]user{{"wang" , "1024" }}, "li" ); err != nil { fmt.Println(err) return } else { fmt.Println(u.name) } }
符合语言习惯的做法就是使用一个单独的返回值来传递错误信息。能够很清晰地知道哪个函数返回了错误,并且能用简单的if-else来处理错误。
在函数里面,可以在那个函数的返回值类型里面,后面加一个err,就代表这个函数可能会返回错误。
在函数实现的时候,return需要同时return两个值,如果出现错误,可以return nil和一个error。没有就返回原本的结果和nil。
字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "fmt" "strings" ) func main () { a := "hello" fmt.Println(strings.Contains(a, "ll" )) fmt.Println(strings.Count(a, "l" )) fmt.Println(strings.HasPrefix(a, "he" )) fmt.Println(strings.HasSuffix(a, "llo" )) fmt.Println(strings.Index(a, "ll" )) fmt.Println(strings.Join([]string {"he" , "llo" }, "-" )) fmt.Println(strings.Repeat(a, 2 )) fmt.Println(strings.Replace(a, "e" , "E" , -1 )) fmt.Println(strings.Split("a-b-c" , "-" )) fmt.Println(strings.ToLower(a)) fmt.Println(strings.ToUpper(a)) fmt.Println(len (a)) b := "你好" fmt.Println(len (b)) }
contains判断一个字符串里面是否有包含另一个字符串,count字符串计数,index查找某个字符串的位置。join连接多个字符串repeat重复多个字符串replace替换字符串。
字符串格式化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" type point struct { x, y int } func main () { s := "hello" n := 123 p := point{1 , 2 } fmt.Println(s, n) fmt.Println(p) fmt.Printf("s=%v\n" , s) fmt.Printf("n=%v\n" , n) fmt.Printf("p=%v\n" , p) fmt.Printf("p=%+v\n" , p) fmt.Printf("p=%#v\n" , p) f := 3.141592653 fmt.Println(f) fmt.Printf("%.2f\n" , f) }
ptf这个类似于C语言里面的pitf函数。可以用%v来打印任意类型的变量,而不需要区分数字字符串。
也可以用%+V打印详细结果,%#V则更详细。
JSON
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package mainimport ( "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) fmt.Println(string (buf)) 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) }
对于一个已有的结构体只要保证每个字段的第一个字母是大写,也就是是公开字段。那么这个结构体就能用JSON.marshaler序列化,变成一个JSON的字符串。序列化之后的字符串也能够用JSON.unmarshaler去反序列化到一个空的变量里面。
这样默认序列化出来的字符串的话,它的风格是大写字母开头,而不是下划线。可以在后面用json tag等语法来去修改输出JSON结果里面的字段名。
时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "fmt" "time" ) func main () { now := time.Now() fmt.Println(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) fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) fmt.Println(t.Format("2006-01-02 15:04:05" )) diff := t2.Sub(t) fmt.Println(diff) fmt.Println(diff.Minutes(), diff.Seconds()) 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) fmt.Println(now.Unix()) }
最常用的就是time.now0来获取当前时间,也可以用time.date去构造一个带时区的时间,上面有很多方法来获取这个时间点的年月日小时分钟秒,也能用点Sub去对两个时间进行减法,得到一个时间段。时间段又可以去得到它有多少小时,多少分钟、多少秒。
在和某些系统交互的时候,我们经常会用到时间戳,可以用.UNX来获取时间戳。time.format time.parse
数字解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "fmt" "strconv" ) func main () { f, _ := strconv.ParseFloat("1.234" , 64 ) fmt.Println(f) n, _ := strconv.ParseInt("111" , 10 , 64 ) fmt.Println(n) n, _ = strconv.ParseInt("0x1000" , 0 , 64 ) fmt.Println(n) n2, _ := strconv.Atoi("123" ) fmt.Println(n2) n2, err := strconv.Atoi("AAA" ) fmt.Println(n2, err) }
字符串和数字类型之间的转换都在STR conv这个包下,是string convert这两个单词的缩写。
可以用parselnt
或者parseFloat
来解析一个字符串、parseint
参数。
可以用Atoi
把一个十进制字符串转成数字。可以用itoA
把数字转成字符串。如果输入不合法,那么这些函数都会返回error。
进程信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "fmt" "os" "os/exec" ) func main () { fmt.Println(os.Args) fmt.Println(os.Getenv("PATH" )) 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)) }
用os.rgv
来得到程序执行的时候的指定的命令行参数。
比如编译的一个二进制文件,command。后面接abcd
来启动,输出就是os.argv
会是一个长度为5的slice,第一个成员代表二进制自身的名字。
可以用so.getenv
来读取环境变量exec
。