Go语言基本语法| 青训营笔记

44 阅读10分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

路漫漫其修远兮~

我们一起从 0 开始学习 Go 语言~

不出意外今天的内容包括两部分

  • Go 基本语法
  • Go 的一些标准库

Go 基本语法

首先,每一个 .go 文件,第一行都有 package main

这句话表示这个文件属于 main 包的一部分,而 main 包是程序的入口包

//导入标准库中的一些包
import (
   "fmt" //往屏幕输入输出字符串、格式化字符串
   "math"
)

(1)声明一个变量

定义一个变量,一般用var 变量名 变量类型 指定变量类型

var b, c int = 1, 2

或者自动推断类型:

var a = "initial"

不用 var 的声明一个变量

f := float32(e)

常量的声明

const s string = "constant"

常量可以没有确定的类型,根据上下文确定类型

const h = 500000000
const i = 3e20 / h

(2)for 循环

golang 中没有 whiledo…while 循环只有for循环

我们看一下 for 循环的用法

for j := 7; j < 9; j++ {
   fmt.Println(j)
}

可以用break结束循环

for {
   fmt.Println("loop")
   break
}

continue继续循环

for n := 0; n < 5; n++ {
   if n%2 == 0 {
      continue
   }
   fmt.Println(n)
}

for 后面不跟小括号,跟大括号,且大括号后面要换行

(3)if 语句

用法:if 布尔条件语句 {}

示例:

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

if 后面也可以接赋值语句,需要接 ;后加布尔条件语句及{}

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")
}

(4)switch 语句`

golang 中的 switch 语句更加强大,可以用接字符串、结构体

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")
}

switch甚至可以取代任意的 if …… else语句 例如:

t := time.Now()
switch {
case t.Hour() < 12:
   fmt.Println("It's before noon")
default:
   fmt.Println("It's after noon")
}

switch后不加任何变量,而在case中写任意的分支,比多个if else更加清晰

(5)数组 array

golang中的数组是长度固定的元素序列 声明一个数组 var 数组名 [数组大小]数组元素类型

var a [5]int
b := [5]int{1, 2, 3, 4, 5}//声明一个数组,且赋值
fmt.Println(b)

存取和读取数组中特定索引下的值

a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))

声明一个二维数组,并赋值

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)

(6)切片 slice

因为数组长度是固定,因此我们在 go 语言中更常用的是切片 slice

切片 slice是一个可变长度的数组,支持任意时刻更改其大小,以及更多的操作

创建切片,make([]元素类型,切片长度)

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

append 向切片中追加元素

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

拷贝切片,即将一个切片的值复制到另一个切片上

s := []string{"a", "b", "c", "d", "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]

取出slice特定索引位置的值

s := []string{"a", "b", "c", "d", "e", "f"}
fmt.Println(s) // [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]

但是 slice 不支持负数索引,因此需要用 len 先取出切片的长度,再做一些运算

(7)map

map 类似于其他程序语言中的哈希或者字典,但是是非常常用的数据结构

创建一个空 mapmake(map[key的类型]value的类型)

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,不存在的key值,返回默认的值 0

读取map中的元素时,用一个ok (布尔值),检测这个key是否存在

不存在 ok 返回 false,数字返回 0,字符串 返回 空

var m = map[string]int{"one": 1, "two": 2}
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false

删除key-value

m := map[string]int{"one": 1, "two": 2}
delete(m, "one")
fmt.Println(m)           // map[two:2]

map是完全无序的,因此遍历的时候不会按照插入顺序输出

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

(8)range

对于一个slicemap可以用range快速遍历,使得代码更加简洁

对于数组/切片会返回两个值,第一个是索引,第二个是对应索引的值,如果不需要索引的话,可以用下划线 _代替 去做for循环

nums := []int{2, 3, 4}//nums是slice
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

map 进行 range 的话,第一个值是 key,第二个是 value

m := map[string]string{"a": "A", "b": "B"} //m的类型是map
for k, v := range m {
   fmt.Println(k, v) // b 8; a A
}

只对 map range Key

for k := range m {
   fmt.Println("key", k) // key a; key b
}

只对map range value

for _, v := range m {
   fmt.Println("value", v) //value A; value B
}

(9)定义一个函数

定义一个函数时:

func 函数名(接入的变量名 变量类型) 返回的变量类型{}

可以看到变量类型是后置的

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
}

(10)指针 point

感觉每次涉及到指针的知识点,都比较难以理解

指针:对传入的参数进行修改

n 写成一个指针类型:(n *变量类型)

之后,对指针变量进行运算的时候,都要在前面加 *

而调用的时候需要在变量名前加一个&才能编译通过

func add2ptr(n *int) {
   *n += 2 //运算时也需要加一个星号
}

看一下用了指针后的不同:

// 无效,是copy类型的+2
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
}

(11)结构体 struct

结构体:带类型的字段的集合

type user struct {
   name     string
   password string
}

.变量名进行读取和写入

var d user
d.name = "wang"
d.password = "1024"

如果没有对结构体初始化字段的话,会将字段值初始化成一个空值,数字为 0,字符串为空字符串

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
}

// 有指针和非指针两种用法
// 用指针的话可以进行结构体的修改,也能避免大结构体copy的开销
func checkPassword2(u *user, password string) bool {
   return u.password == password
}

(12)结构体方法

结构体方法:类成员函数

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")) // true
}

Go 的一些标准库

(1)错误 error

错误的处理需要引入 errors

import (
   "errors"
)

错误的处理(函数的声明中,变量类型在变量名后面)

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)
   }
}

(2)字符串 string

导入strings

import (
   "strings"
)

字符串的拼接

g := a + "foo"

判断字符串是否包含另一个字符串

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

字符串计数

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

查找某个字符串的位置

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

连接多个字符串

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

重复多个字符串

a := "hello"
fmt.Println(strings.Repeat(a, 2))             // hellohello

其他用法

a := "hello"
fmt.Println(strings.HasPrefix(a, "he"))  // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
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)fmt

fmt对输出格式进行控制,经常用到 导入 fmt

import (
   "fmt"
)

换行输出

fmt.Println(s, n)
fmt.Println(p) 

按特定格式输出

s := "hello"
n := 123
p := point{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}

(16)json

导入 json

import (
   "encoding/json"
)

具体用法:


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

(17)time

导入 time

import (
   "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获取时间戳
}

(18)strconv

字符串和数字之间的转换 导入 strconv

import (
   "strconv"
)

具体用法:

func main() {
   f, _ := strconv.ParseFloat("1.234", 64)
   fmt.Println(f) // 1.234
   //字符串,进制,返回64进制的整数
   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
}

(19)env

进程在执行时的命令化参数

导入标准库

import (
   "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
}

把学习过程记录下来,以后也方便自己查阅,今天的学习就先到这里~