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

67 阅读7分钟

仪式感, 先来个hello world

package main

import (
	"fmt"
)

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

在命令行输入 go run main.go。

变量

var a, b int = 1;
c := 1;

go语言是强类型的语言,var a, b int = 0 同时给a b 赋值为1, 不需要单独赋值。

在go语言里面变量的声明有两种方式,一种是通过 var name [type],这种方式来声明变量,不写type会自动推导。另一种是使用 :=(python里也有,叫海象运算符 0-0)。

for 循环

go的for循环没有while,只有for,不需要 java、c++的括号(),和python的类似,但不需要冒号:, 还支持 fori 的风格。

  1. 死循环
for {
    fmt.Println("0.0")
    break
}
  1. fori
for i := 0; j < 10; i++ {
    
}
  1. range
for i, num := range nums {
    sum += num
    if num == 2 {
            fmt.Println("index:", i, "num:", num) // index: 0 num: 2
    }
}

go的range有点类似于python的enumerate,只是类似。

:= 前有两个值,range会返回索引(键)和值。只有一个会优先返回值。

if

if也是不需要括号,但是必须有大括号,支持在if中赋值。

if condition {
        
} else {
        
}

在if中赋值

if num := 9; num < 0 {
        
} else if num < 10 {
        
} else {
        
}

switch

go的switch感觉很不一样。

switch a {
    case 1:
        fmt.Println("one")
    case 2:
        fmt.Println("two")
    case 4, 5:

    default:

    }

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

首先switch后变量不需要括号(这个没有那么不一样)。

go的每个分支不需要break,即满足某个分支后并不会向下执行,而是结束整个switch。(这太不一样了)。

其次,switch后的变量可以不加,会直接判断每个分支。这样的代码比if-else更有明了。

数组

定义数组:

var a [n]type

数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。

而且,[1]int 和 [2]int 是不同的类型。

但是数组的长度固定,所以一般不同,而是用切片。

切片

切片是引用类型,它是对地址长度容量的封装。

[ ] 内不指定长度。

var name []type 

s := make([]string, 3)

切片可以任意更改长度,有更多丰富的操作。可以使用 append 来追加元素。

slice 的原理实际上是它有一个它存储了一个长度和一个容量,加一个指向一个数组的指针,在执行 append 操作的时候,如果容量不够的话,会扩容并目返回新的 sIice。

slice 初始化的时候也可以指定长度slice 。

slice拥有像 python 一样的切片操作,但不只是负数的索引。

map

即哈希表、数组。

m := make(map[type1]type2)

操作方法和python比较像,不需要像java那种调用get函数,直接写在 [ ]内就可。

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

函数

func 方法名(参数列表) (返回参数) {
    函数体
}

可变参数使用,例如 x ...int,此时,x是切片。

返回参数可以仅指定类型,也可以定义变量名+类型。指定变量名+类型,并在函数体中直接使用这些变量,最后通过return返回。

func f(x, y int) (a, b int) {
	a = ...
	b = ...
	return
}

指针

go语言指针一个主要用途就是对于传入参数进行修改。

func add(n int) {
    n += 2
}
func addptr(n *int) {
    *n += 2
}
func main() {
    n := 5
    add(n)
    fmt.Println(n) // 5
    addptr(&n)
    fmt.Println(n) // 7
}

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

结构体

type user struct {
    name     string
    password string
}

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

结构体方法

在 golang 里面可以为结构体去定义一些方法。会有一点类似其他语言里面的类成员函数。可以像 a.checkPassword(“xx”) 这样去调用。在实现结构体的方法的时候也有两种写法,一种是带指针,一种是不带指针。这个它们的区别的话是说如果你带指针的话,那你那么你就可以对这个结构体去做修改。如果你不带指针的话,那你实际上操作的是一个拷贝,你就无法对结构体进行修改。

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
}

错误处理

(蛮多人吐槽go的错误处理的 [doge])

错误处理 在 go 语言里面符合语言习惯的做法就是使用一个单独的返回值来传递错误信息。 go语言的处理方式,能够很清晰地知道哪个函数返回了错误,并且能用简单的 if else 来处理错误。在函数里面,我们可以在那个函数的返回值类型里面,后面加一个 error, 就代表这个函数可能会返回错误。 那么在函数实现的时候, return 需要同时 return 两个值,要么就是如果出现错误的话,那么可以 return nil 和一个 error。如果没有的话,那么返回原本的结果和 nil。

a, err := fun()
if err != nil {
    fmt.Println(err)
    return
}
fmt.Println(a)

json

go语言 里面的 JSON 操作非常简单,对于一个已有的结构体,我们可以什么都不做,只要保证每个字段的第一个字母是大写,也就是是公开字段。那么这个结构体就能用 JSON.marshaler 去序列化,变成一个 JSON 的字符串。 序列化之后的字符串也能够用 JSON.unmarshaler 去反序列化到一个空的变量里面。 这样默认序列化出来的字符串的话,它的风格是大写字母开头,而不是下划线。我们可以在后面用 json tag 等语法来去修改输出 JSON 结果里面的字段名。

type userInfo struct {
    Name  string
    Age   int `json:"age"`
    Hobby []string
}

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

时间处理

与其他语言不同的是,在进行格式转换时,go不是输入 YYYY、mm、dd,而是使用固定的一个时间2006-01-02 15:04:05

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