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

94 阅读9分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

1.Go语言特性

  • 高性能、高并发
  • 语法简单、学习曲线平缓
  • 丰富的标准库
  • 完善的工具链
  • 静态链接
  • 快速编译
  • 跨平台
  • 垃圾回收

2.基础语法

2.2 Hello World

 package main    //main包
 ​
 import(
     "fmt"       //导入标准库的format包
 )
 ​
 func main(){
     fmt.Println("hello world")
 }

2.3 变量

变量声明一般语法

 var 变量名字 (类型) (= 表达式)

其中“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。 数值类型变量对应的零值是0,布尔类型变量对应的零值是false,字符串类型对应的零值是空字符串

 var a string
 fmt.Print(a)    //""

也可以在一个声明语句中同时声明一组变量,或用一组初始化表达式声明并初始化一组变量。如果省略每个变量的类型,将可以声明多个类型不同的变量(类型由初始化表达式推导):

 var a,b,c int   //int,int,int
 var e,d,f = true,2.3,"four"     //bool,float,string

简短变量声明

以“名字 := 表达式”形式声明变量,变量的类型根据表达式来自动推导。

 a := 'aaa'
 b := a + "bbb"
 fmt.Print(b)    //"aaabbb"

因为简洁和灵活的特点,简短变量声明被广泛用于大部分的局部变量的声明和初始化。var形式的声明语句往往是用于需要显式指定变量类型的地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。

2.4 if else

 if 7%2 == 0 {
     fmt.Println("7 is even")
 } else {
     fmt.Println("7 is odd")
 }

与python类似,if的条件不用加(),且后面直接加{},不能把if和结果语句写在同一行

2.5 循环

Go中只有for循环,无其他循环

     i := 1
     for {                           //无限制条件——死循环
         fmt.Println("loop")
         break                       //break跳出
     }
     for j := 7; j < 9; j++ {        //限制条件
         fmt.Println(j)  
     }
     for n := 0; n < 5; n++ {
         if n%2 == 0 {
             continue                //continue跳过
         }
         fmt.Println(n)
     }
     for i <= 3 {
         fmt.Println(i)
         i = i + 1
     }
 ​
 /*
 loop
 7
 8
 1
 3
 1
 2
 3
 */

2.6 switch

与C++类似,不同的是,在搜索到对应的case后即使没有break,它也会直接跳出switch

     a := 2
     switch a {
     case 1:
         fmt.Println("one")
     case 2:
         fmt.Println("two")      //找到case后跳出switch
     case 3:
         fmt.Println("three")
     case 4:
         fmt.Println("four")
     default:
         fmt.Println("other")
     }
 ​
     fmt.Println("end")          //跳出switch执行此行
 ​
 /*
 two
 end
 */

此外,case可用其他类型变量以及条件语句

     a := 2
     switch {                    //switch不加变量
     case a < 5:                 //case后加条件语句
         fmt.Println("a<5")
     default:
         fmt.Println("a>=5")
     }
 ​
     fmt.Println("end")
 ​
 /*
 a<5
 end
 */

2.7 数组

 var a [5]int                    //定义数组
 a[4] = 100                      
 fmt.Println(a[4], 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)
 ​
 /*
 100 5
 [1 2 3 4 5]
 2d: [[0 1 2] [1 2 3]]
 */

由于数组长度固定,一般比较少用,而多用切片

2.8 切片

make(a,b) :用来创建切片,参数a指定类型,参数b指定长度

 s := make([]string, 3)      //创建切片
 s[0] = "a"
 s[1] = "b"
 s[2] = "c"
 fmt.Println("get:", s[2])   //get: c
 fmt.Println("len:",len(s))  //len: 3

append() :用来对切片进行添加操作,可一次添加多个元素

 s = append(s, "d")          //添加1个元素
 s = append(s, "e", "f")     //添加2个元素
 fmt.Println(s)              //[a b c d e f]

copy(a, b) :把切片b的内容复制到a

 c := make([]string, len(s))     //创建空切片c
 copy(c, s)                      //将s的内容复制到c中
 fmt.Println(c)                  //[a b c d e f]

s[a : b] :获取切片s中下标从a到b的内容

 fmt.Println(s[2:5])     //[c d e]
 fmt.Println(s[:5])      //[a b c d e f]
 fmt.Println(s[2:])      //[c d e f]

2.9 map

map有点类似于python的字典

make(map[a]b) :创建map,a表示key的类型,b表示value的类型

     m := make(map[string]int)       //创建map,key类型为string,value类型为int
     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(未定义的key,值默认为0)

delete() :删除map的某个键值对

 delete(m, "one")        //删除m中key为“one”的键值对
 fmt.Println(m)          //map[two:2]

当某键值对的value为0时,可用两个变量判断其到底是真的值为0还是不存在此键值对

     m["zero"] = 0
     a, b := m["zero"]       //a返回value,b返回true/false来判断是否存在
     fmt.Println(a, b)       //0 true
     c, d := m["unknow"]
     fmt.Println(c, d)       //0 false

2.10 range

对于数组,可用range返回下标和对应的元素

     nums := []int{2,3,4}
     for i, num := range nums {      //i返回下标,num返回元素
         fmt.Println(i,num)
     }
 ​
 /*
 0 2
 1 3
 2 4
 */

对于map,range返回key和value

     m := map[string]string{"a":"A","b":"B"}
     for k,v := range m {
         fmt.Println(k,v)
     }
 ​
 /*
 a A
 b B
 */

2.11 函数

GO中函数定义时,无论是参数还是返回值,其变量类型都后置

 func add(a int, b int) int {    //参数a、b为int型,int后置;返回值也为int型,也后置
     return a + b
 }
 ​
 func add2(a, b int) int {       //同类型的参数可一起定义
     return a + b
 }
 ​
 //参数为map型变量和string型变量;返回值为string和bool
 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
 }

2.12 指针

与C语言相比,GO中指针用法有限,主要用来在函数中修改参数的值

 func add(n int) {
     n += 2
 }
 ​
 func add2ptr(n *int) {      //参数为指针,在类型前加*
     *n += 2
 }
 ​
 func main(){
     n := 5
     add(n)
     fmt.Println(n)      //5
     add2ptr(&n)         //调用指针函数时变量前加“&”
     fmt.Println(n)      //7
 }

调用非指针函数返回原本的变量时不会改变变量的值,调用指针函数则会改变

2.13 结构体

类似java、python的类,定义如下:

 type user struct {
     name        string
     password    string
 }

初始化时,若有未赋值的,则为0

 a := user{name:"wang", password:"1024"}     //普通赋值
 b := user{"wang","1024"}                    //省略变量名
 c := user{name:"wang"}                      //省略部分变量
 c.password = "1024"                         //利用定义好的结构体赋值
 ​
 var d user                                  //先定义结构体后赋值
 d.name = "wang"
 d.password = "1024"

结构体也可作函数的参数,也分指针和非指针,指针结构体函数可修改原结构体的元素,也可在某些情况下减少大结构体的开销

 func checkPassword(u user, password string) bool {
     return u.password == password
 }
 ​
 func checkPassword2(u *user, password string) bool {
     return u.password == password
 }

调用:

 fmt.Println(checkPassword(u, "haha"))       //false
 fmt.Println(checkPassword2(&u, "1024"))     //true

结构体方法

类似java、python的类内部子函数,与上面结构体作参数的函数不同的是结构体声明变量提前。同样分指针和非指针,指针可对结构体内部信息作修改。

 func (u user) checkPassword(password string) bool {
     return u.password == password
 }
 ​
 //利用指针对user内部的password进行修改
 func (u *user) resetPassword(password string) {
     u.password = password
 }

调用:

 u.resetPassword("2048")
 fmt.Println(u.checkPassword("2048"))    //true

2.14 错误处理

调用errors模块进行错误处理

 import (
     "errors"
     "fmt"
 )

对于可能出现错误的函数,其返回值加上error;返回error时,若程序未出现错误,返回error为 "nil" (空);若出现错误,将错误信息赋值给error

 func findUser(users []user, name string) (v *user, err error) {
     for _, u := range users {
         if u.name == name {
             return &u, nil      //没有错误,err赋值为nil
         }
     }
     return nil, errors.New("not found")     //有错误则返回错误
 }

调用:

     u, err := findUser([]user{{"wang", "1024"}}, "wang")
     if err != nil {         //若错误为空,输出错误终止程序
         fmt.Println(err)
         return
     }                       //错误不为空才能继续执行以下程序
     fmt.Println(u.name)     //wang   本次程序无错,故直接输出name

有错误的调用:

     if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
         fmt.Println(err)    //not found  本次有错,输出错误
         return
     } else {
         fmt.Println(u.name)
     }

2.15 字符串操作

Contains(a, b) :字符串a中是否包含b

 a := "hello"
 fmt.Println(strings.Contains(a, "ll"))      //true

Count(a, b) :字符串a中b的数目

 fmt.Println(strings.Count(a, "l"))      //2

HasPrefix(a, b) :字符串a是否以b为前缀

HasSuffix(a, b) :字符串a是否以b为后缀

 fmt.Println(strings.HasPrefix(a, "he"))     //true
 fmt.Println(strings.HasSuffix(a, "llo"))    //true

Index(a, b) :字符串a中b开始出现的下标

 fmt.Println(strings.Index(a, "ll"))     //2

Join([]a, b) :数组a内的元素以b相连接

 fmt.Println(strings.Join([]string{"he", "llo"}, "-"))   //he-llo

Repeat(a, n) :将字符串a复制n个

 fmt.Println(strings.Repeat(a, 2))   //hellohello

Replace(a, b, c, n) :将字符串a中前n个不重叠的b都换成c,若n<0,则全部替换

 fmt.Println(strings.Replace(a, "e", "E", -1))   //hEllo

Split(a, b) :将字符串a按b分隔开

 fmt.Println(strings.Split("a-b-c", "-"))        //[a b c]

ToLower(a) :字符串a全部变成小写

ToUpper(a) :字符串a全部变成大写

 fmt.Println(strings.ToLower(a))     //hello
 fmt.Println(strings.ToUpper(a))     //HELLO

len(a) :返回字符串a的长度,a中的中文元素,其长度不为1

 fmt.Println(len(a))         //5
 fmt.Println(len("你好"))      //6

2.16 字符串格式化

通常使用fmt.Printf() 对字符串进行格式化, %v是万能输出,各种类型的变量都可通过它输出,%+v则是比%v更加详细的输出,%#v又是比%+v更详细的输出。 %.nf保留小数后n位

 type point struct {
     x, y int
 }
 ​
 ....
 ​
 s := "hello"
 n := 123
 p := point{1, 2}
     
 fmt.Printf("s=%v\n", s)     //s=hello
 fmt.Printf("s=%v\n", n)     //s=123
 fmt.Printf("s=%v\n", p)     //s={1 2}
 fmt.Printf("s=%+v\n", p)    //s={x:1 y:2}
 fmt.Printf("s=%#v\n", p)    //s=main.point{x:1, y:2}
 ​
 f := 3.1415926
 fmt.Printf("%.2f\n", f)     //3.14

2.17 JSON处理

导入json模块

 import (
     "encoding/json"
     "fmt"
 )

结构体的变量名首字母大写即可使用json

 type userInfo struct {
     Name string
     Age  int `json:"age"`   //可以在不想大写输出变量名的变量后加上这个
     Hobby []string
 }

json.Marshal(a)可对a进行序列化,将结构体内容返回成字符串,但输出时要加string()修饰,否则会打印出16进制编码

 a := userInfo{"wang", 18, []string{"Golang","Python"}}
 buf, err := json.Marshal(a)
 if err != nil {
     panic(err)
 }
 fmt.Println(string(buf))    //{"Name":"wang","age":18,"Hobby":["Golang","Python"]}

序列化后的字符串可以通过json.Unmarshal() 反序列化到空的变量中

     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", "Python"}}

2.18 时间处理

导入time模块

 import (
     "time"
     "fmt"
 )

获取当前时间:time.Now()

     now := time.Now()
     fmt.Println(now)    //2022-05-07 16:09:15.0401635 +0800 CST m=+0.003224801

构造带时区的时间:time.Date()

 t := time.Date(2022, 3, 27, 1, 25, 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

格式化时间:Format(s) ,不用像其他语言那样YYYY-mm-dd,选择一个想要的格式的时间字段s即可

 fmt.Println(t.Format("2006-01-02 15:04:05"))    //2022-03-27 01:25:36

另一种格式化时间:time.parse(a, b) :把时间b转换成时间a的格式

 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

计算时间差:Sub()

 t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
 t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
 diff := t2.Sub(t)
 fmt.Println(diff)       //1h5m0s
 //也可以将diff转换成时、分、秒
 fmt.Println(diff.Minutes(), diff.Seconds())     //65 3900

时间戳:Unix()

 fmt.Println(now.Unix())     //1651911542

2.19 数字解析

导入strconv模块

 import (
     "strconv"
     "fmt"
 )

strconv.ParseFloat(s, m) :将字符串s转换成m精度的数字

strconv.ParseFloat(s, n, m) :将代表n进制数字的字符串s转换成m精度的数字,若n=0则自动推测进制

     f, _ := strconv.ParseFloat("1.234", 64)
     fmt.Println(f)      //1.234
 ​
     n, _ := strconv.ParseInt("111", 10, 64)
     fmt.Println(n)      //111
 ​
     n1, _ := strconv.ParseInt("0x1000", 0, 64)
     fmt.Println(n1)     //4096

strconv.Atoi(s) :自动转化s为数字,若转化不了则报错

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

\