Go基础-基础语法 | 青训营笔记

133 阅读12分钟

Go基础-基础语法 | 青训营笔记

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

1. 什么是go语言

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

2. 入门

2.1 开发环境

go语言下载,环境变量啥的,网上一大堆,这里就不献丑了。

2.2 Hello World

开始大家熟悉的Hello World环节,

// main包的一部分,程序的入口包(入口文件)
package main
// 导入fmt包,主要是用来往屏幕输入输出字符串、格式化字符串
import "fmt"

func main() {
    fmt.Println("Hello World")
}

2.2 变量

Go和Java、C一样都是强类型语言,每一个变量都有它们自己的变量类型。

但是和许多编程语言不同,它在声明变量时将变量的类型放在变量的名称之后。这样做的好处就是可以避免像C语言中那样含糊不清的声明形式,例如:int* a, b; 。其中只有 a 是指针而 b 不是。其中只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写。而在 Go 中,则可以和轻松地将它们都声明为指针类型:

var a, b *int

变量声明也有所不同:

  1. 显式声明:通过var关键字声明变量,也可以不加数据类型,系统会自行推导

    // var 是声明变量的关键字,name 是变量名,type 是变量的类型
    // var name type
    var key string
    var value int
    
    // 多个声明
    var (
    	firstName, lastName string
        age int
    )
    
  2. 隐式声明:简化显式声明,移除了var关键字

    // name 是变量名,value 是变量的数值
    // name := value
    num := 100
    name := "Tom"
    

    一般来说都是用隐式声明,但是隐式声明只能在函数体内使用,如果要在函数体外声明变量只能使用显式声明。

Go语言的基本类型有:

  • bool
  • string
  • int、int8、int16、int32、int64
  • uint、uint8、uint16、uint32、uint64、uintptr
  • byte // uint8 的别名
  • rune // int32 的别名 代表一个 Unicode 码
  • float32、float64
  • complex64、complex128

当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等。所有的内存在 Go 中都是经过初始化的。

package main

import (
    "fmt"
    "math"
)

func main() {
    // 显式声明
    var a = "initial"
    var b,c int = 1,2
    var d = true
    var e float64
    // 也可进行批量声明变量
    var (
        a = "initial"
        b,c int = 1,2
        d = true
        e float64
    )
    // 隐式声明
    f := float32(e)
    g := a + "foo"
    // Go中不允许变量不被使用,如果你定义了不使用会报错
    fmt.Println(a,b,c,d,e,f)// initial 1 2 true 0 0
    fmt.Printf(g)// initialfoo
    // Go中的常量就是把var改成constant,没有特定的类型,而是根据使用的上下文来自动确定类型
    const s string = "JMjiumu" 
    const h = 500000000
    const i = 3e20/h
    fmt.Println(s,h,i,math.Sin(h),math.Sin(i))
    // JMjiumu 500000000 6e+11 -0.28470407323754404 0.7591864109375384
}

2.3 if-else

和C、C++差不多,不过if后面不需要小括号,如果加了保存时会自动帮你删除,并且if后的语句块必须要加大括号,不加编译都会出错。

在判断语句前还可以添加一些简单的执行语句。

// 小括号
if (7%2 == 0) {...}❌哒咩哒咩
if 7%2 == 0 {...}✅
// 大括号
if 7%2 == 0 fmt.Println("hhh") ❌哒咩呦
if 7%2 == 0 {✅
    fmt.Println("hhh")
}
package main

import "fmt"

func main() {
    if 7%2 == 0 {
        fmt.Println("7 is even")
    } else {
        fmt.Println("7 is odd")
    }
    // 执行简单语句
    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")
    }
}

2.4 循环

Go语言中没有while循环,没有do-while循环,只有for循环,同样不用加小括号,其余和C、C++等基本相同,可以用break跳出,continue继续循环等等操作。

package main

import "fmt"

func main() {
    i := 1
    // 相当于while条件为true,死循环
    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
    }
}

2.5 switch

  1. switch没有break不会继续往后执行,而是找到目标后直接会进行退出
  2. switch后面可以使用任意的变量类型(字符串、结构体),甚至可以取代任何if-else语句,也可以不加如何变量,在case里写条件分支,这样比用多个if-else逻辑会更加清晰
package main

import (
    "fmt"
    "time"
)

func main() {
    a := 5
    switch a {
        case 1:
        fmt.Println("学Go的第1天...")
        case 5:
        fmt.Println("学Go的第5天...")
        case 10:
        fmt.Println("学Go的第10天...")
        case 15, 20:
        fmt.Println("...")
        default:
        fmt.Println("学个锤子,不学了🥳")
    }

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

2.6 数组

数组和其他语言大体相同,但是在真实业务代码里面,我们很少直接使用数组,因为它长度是固定的,我们用的更多的是切片

package main

import "fmt"

func main() {
    var a [5]int
    a[4] = 100
    fmt.Println(a[2], a[4], len(a))// 0 100 5

    b := [5]int{1, 2, 3, 4, 5}
    fmt.Println(b)// [1 2 3 4 5]

    var twoD [2][3]int
    for i := 0; i < 2; i++ {
        for j := 0; j < 3; j++ {
            twoD[i][j] = i + j
        }
    }
    fmt.Println(twoD)// [[0 1 2] [1 2 3]]有一说一,没有逗号看得有点不习惯😂
}

2.7 切片

切片不同于数组可以任意更改长度,也有更多丰富的操作,有点像Python的切片。

slice的实际上是一个数组存储了一个长度和一个容量,加一个指向一个数组的指针,Go 支持切片运算符 s[i:p],其中:

  • s 表示数组。
  • i 表示指向要添加到新切片的基础数组(或另一个切片)的第一个元素的指针。 变量 i 对应于数组 array[i] 中索引位置 i 处的元素。 请记住,此元素不一定是基础数组的第一个元素 array[0]
  • p 表示创建新切片时要使用的基础数组中的元素数目。 变量 p 对应于可用于新切片的基础数组中的最后一个元素。 可在位置 array[i+1] 找到基础数组中位置 p 处的元素。 请注意,此元素不一定是基础数组的最后一个元素 array[len(array)-1]

切片容量是p - i,但是切片长度是len(s) - i,不要忘记i是指针,p只是显示的末尾而已,实际上的末尾是数组的最后一个位置。

切片可以通过内置函数make()来创建,也可以像数组一样创建:

// name为变量名,type为数据类型,len为切片长度,cap为切片容量
name = make([]type, len, cap)
var name []int = arr[2:3]
name := []int{1, 2, 3}

切片也可以通过内置函数append()来动态地增加元素

slice := []int{1, 2, 3}
slice = append(slice, 4, 5)
// slice[1 2 3 4 5]
package main

import "fmt"

func main() {
    // 使用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
    // 添加元素
    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]
    // 将s从2切到5
    fmt.Println(s[2:5]) // [c d e]
    // :前没有数值默认从0开始
    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]
}

2.8 map

map(映射)在其他编程语言里面,它可能可以叫做哈希或者字典,map是实际使用过程中最频繁用到的数据结构。

同样是使用make进行创建:

// 第一个类型为key的类型,第二个为value的类型
temp := make(map[string]int)

我们可以从里面去存储或者取出键值对。可以用delete 从里面删除键值对:

temp := make(map[string]int)
// 添加,直接添加
temp["九牧", 20]
temp["Tom", 666]
// 删除,使用delete函数
delete(temp, "Tom")

golang的map是完全无序的,遍历的时候不会按照字母顺序,也不会按照插入顺序输出,而是随机顺序。

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

2.9 range

对于一个slice或者一个map的话,可以用range来快速追历,这样代码能够更加简洁。range遍历的时候,对于数组会返回两个值。第一个是索引,第二个是对应位置的值。如果我们不需要索引的话,我们可以用下划线来忽略。

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

2.10 函数

这个是Golang里面一个简单的实现两个变量相加的函数。Golang和其他很多语言不一样的是,变星类型是后置的。

Golang里面的函数原生支持返回多个值。在实际的业务逻辑代码里面几乎所有的函数都返回两个值,第一个是真正的返回结果,第二个值是一个错误信息。

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
}

2.11 指针

go里面也支持指针。当然,相比C和C++里面的指针,支持的操作很有限。指针的一个主要用途就是对于传入参数进行修改。

这个函数试图把一个变量+2,但是单纯像上面这种写法其实是无救的,因为传入函数的参数实际上是一个拷贝,那也说这个+2,是对那个拷贝进行了+2,并不起作用。如果我们需要起作用的话,那么我们需要把那个类型写成指针类型,那么为了类型匹配,调用的时候会加一个&符号。

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
}

2.12 结构体

结构体的话是带类型的字段的集合,比加这里user结构包含了两个字段,,name和password,我们可以用结构体的名称去初始化一个结构体变量,构造的时候需要传入每个字段的初始值。也可以用这种键值对的方式去指定初始值,这种可以只对一部份字段进行初始化。同样的结构体我们也能支持指针,这样能够实现对于结构体的修改,也可以在某些情况下避免一些大结构体的拷贝开销。

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
}

2.13 结构体方法

在Golang里面可以为结构体去定义一些方法。会有一点类似其他语言里面的类成员函数。

package main

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

2.14 错误处理

2.15 字符串操作

其实下面都看得懂的,和其他语言都很相似

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
}

2.16 字符串格式化

%v来打印任意类型的变量,可以用%+v打印详细结果,%#v则更详细

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
}

2.17 JSON处理

可以使用json.Marshal函数将字段进行序列化,序列化之后的字符串也能够用JSON.unmarshaler去反序列化到一个空的变量里面

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

2.18 时间处理

Go里基本上都是使用time来处理时间有关操作,有非常多有关时间的方法,得到的时间都是时间戳,也就是以纳秒为单位的。

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
}

2.19 数字解析

有关字符串与数字之间转换就要用到strconv包了,它提供了许多函数来实现字符串和整数、浮点数、布尔值之间的转换

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
}

2.20 进程信息

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
}