GO语言上手之基础语法 | 青训营笔记

99 阅读8分钟

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

1.GO语言的特点

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

2.基础语法

首先让我们看看go语言的hello world

package main

import (
   "fmt"
)

func main() {
   fmt.Println("hello world")
}

简单的描述一下go语言的基础语法

1变量

GO具有多种值类型,包括字符串、整型、浮点型、布尔型等

  • 其中字符串可以通过+连接 在go中变量需要显性声明
  • go会自动判断已有初始值的变量的类型 例如
package main

import (
   "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) // initial 1 2 true 0 0
   fmt.Println(g)                // initialfoo

  
}

声明后没有给出对应的初始值时,变量将会初始化为零值

其中

  • :=是声明初始化变量的简写

  • go语言中浮点型有两种精度 float32和float64

2.常量

const用于声明一个常量 具体类型根据上下文来确定

3.for循环

for是go中唯一的循环结构

其中不带条件的for循环将一直重复执行,直到循环内用了break或者return跳出循环。

也可以使用continue直接进入下一次循环

例:

package main

import "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
   }
}
4.if/else

例:

package main

import "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")
   }
}
5.switch分支结构

例:

package main

import (
   "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中在跑到相应的case后,无需break会跳出switch,不会跑完所有的switch。

go语言中的switch可以使用任意类型

6.数组

在go中,数组是一个具有编号且长度固定的元素序列

数组默认值是零值

可以用内置函数len返回数组的长度

例:

package main

import "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.Print
   
   ln("2d: ", twoD)
}
7.切片

slice是go中一个重要的数据类型,它提供了比数组更强大的序列交互方式

与数组不同,slice的类型仅由它所包含的元素类型决定,创建一个slice通常用它的内置函数make

除了基本操作外,slice还支持内建函数append,该函数会返回一个或多个新值的slice。注意由于append可能返回一个新的slice,我们需要接受其返回值

slice还可以copy

slice支持通过slice[low:high]语法进行“切片”操作

slice可以组成多维数据结构。内部的slice长度可以不一致,这一点和多维数组不同

注意:slice和数组是不同的类型,但它们通过fmt.Println打印的输出结果是类似的。

例:

package main

import "fmt"

func main() {

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

   s = append(s, "d")
   s = append(s, "e", "f")
   fmt.Println(s) // [a b c d e f]

   c := make([]string, len(s))
   copy(c, s)
   fmt.Println(c) // [a b c d e f]

   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"}
   fmt.Println(good) // [g o o d]
}
8.map

map是go内建的关联数据类型(在一些其他语言中也被称为哈希(hash) 或者 字典(dict)

例:

package main

import "fmt"

func main() {
   m := make(map[string]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

   r, ok := m["unknow"]
   fmt.Println(r, ok) // 0 false

   delete(m, "one")

   m2 := map[string]int{"one": 1, "two": 2}
   var m3 = map[string]int{"one": 1, "two": 2}
   fmt.Println(m2, m3)
}

使用name[key]来获取一个键的值

内建函数len可以返回map的键值对数量

内建函数delete可以从一个map中移除键值对

当一个map取值时,还可以选择是否接受第二个返回值,该值表明了map中是否存在这个键

9.Range遍历

Range用于迭代各种各样的数据类型

range在数组和slice中提供对每项的索引和值的访问,若不需要索引,可以使用空白标识符_将其忽略

例:

package main

import "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) // index: 0 num: 2
      }
   }
   fmt.Println(sum) // 9

   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
   }
}
10.函数

go的函数同c语言相似,但是go需要明确的return,也就是说,它不会自动return最后一个表达式的值

当多个连续的参数为同样类型时,可以仅声明最后一个参数的类型

通过函数名(参数列表)来调用函数

多返回值

go原生支持多返回值。这个特性在go语言中经常使用,例如用来同时返回一个函数的结果和错误信息

如果只需要返回值的一部分,可以使用空白符_

package main

import "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) // 3

   v, ok := exists(map[string]string{"a": "A"}, "a")
   fmt.Println(v, ok) // A True
}
11.指针

go支持指针,允许在程序中通过引用传递来传递值和数据结构

一般的函数无法改变变量的值,只是copy一个去使用,而指针会到相应的地址中修改对应的值

例:

package main

import "fmt"

func add2(n int) {
   n += 2
}

func add2ptr(n *int) {
   *n += 2
}

func main() {
   n := 5
   add2(n)
   fmt.Println(n) // 5
   add2ptr(&n)
   fmt.Println(n) // 7
}
12.结构体

go语言中在结构体上的使用同c语言类似

package main

import "fmt"

type user struct {
   name     string
   password string
}

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

   fmt.Println(a, b, c, d)                 // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
   fmt.Println(checkPassword(a, "haha"))   // false
   fmt.Println(checkPassword2(&a, "haha")) // false
}

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

func checkPassword2(u *user, password string) bool {
   return u.password == password
}
13.错误处理

go语言使用一个独立、明确的返回值来传递错误信息,这与其他语言不同,go的处理方式可以清楚的知道哪个函数返回了错误

错误通常是最后一个返回值并且是error类型,它是一个内接的接口。

errors.New使用给的的错误信息构造基本的error值

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)
   }
}
14.字符串操作

下面是对字符串进行操作的函数

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
}

字符串格式化

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
}
15.JSON操作

GO提供内建的JSON编码解码(序列化反序列化)支持,包括内建及自定义类型与JSON数据之间的转化。

只有可导出的字段才会被JSON编码/解码,必须以大写字母开头的字段才是可导出的

json.Marshal() //编码,根据内容生成json文本

json.MarshalIndent(a, "", "\t")//格式化编码,作用与json.Marshal(a)类似,只是可以附带格式

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)//编码,根据内容生成json文本,返回两个值,
   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")//格式化编码,作用与json.Marshal(a)类似,只是可以附带格式
   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"}}
}

注意切片是以二进制代码保存的,直接输出会导致输出结果为二进制数字,一般需要把其转换为字符串格式

这里的panic()函数 是用来终止程序执行的

16.时间处理
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(2022, 3, 27, 1, 25, 36, 0, time.UTC)//构造一个带时区的时间

t.Format("2006-01-02 15:04:05")//把时间格式化为字符串

time.Parse()//把日期字符格式化为时间

t2.Sub(t)//用sub对两个时间进行减法

now.Unix()//获取当前时间戳

17.数字解析
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语言中关于字符串和数字类型之间的转换都在strconv这个包下

从字符串中解析数字在很多程序中是一个基础常见的任务, 而在 Go 中,是这样处理的。

内建的 strconv 包提供了数字解析能力。

使用 ParseFloat,这里的 64 表示解析的数的位数。

在使用 ParseInt 解析整型数时, 例子中的参数 0 表示自动推断字符串所表示的数字的进制。 64 表示返回的整型数是以 64 位存储的。

ParseInt 会自动识别出字符串是十六进制数。

ParseUint 也是可用的。

Atoi 是一个基础的 10 进制整型数转换函数。

在输入错误时,解析函数会返回一个错误。

18.进程信息
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.Getenv("PATH")//获得当前环境变量值

os.Setenv()//设置环境变量