go基础语法 | 青训营笔记

234 阅读5分钟

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

go语法

package

每个 Go 程序都是由包构成的。

程序从 main 包开始运行。

只有package main中的func main可以运行

一个目录下只能有一个package,有不同的package会报错

同一个目录下存在多个main方法会报错

import

package main
​
import (
  "fmt"
  
  // alias
  alias "math/rand"
  
  // load for side-effect
  _ "github.com/go-sql-driver/mysql"
)
​
func main() {
  fmt.Println("My favorite number is", alias.Intn(10))
}
​
  • package名可以和文件名或者目录名不一样

相同包内变量不用import

example

  1. 文件结构
> tree module 
module
├── another.go
└── main.go
  1. 文件内容
// file module/main.go
package main
​
func main() {
  println(plus(1, 2))
}
​
func add(a, b int) int {
  return a + b
}
​
// file module/another.go
package main
​
func plus(a, b int) int {
  return add(a, b)
}
  • 大写的变量会暴露给外界

数组

var array []int
a := []int{1,2,3,4,5}
s := []string{"am", "is", "are"}
  • 数组切片,和python类似array[begin, end]

    • 切片相当于view
  • 范围[begin, end)

  • 和python不同的是:end不能是负数,begin不能超过数组长度

general

type T int
var arrayT []T
arrayT = []T{a T, b T, c T}

切片

切片拥有 长度容量

切片的长度就是它所包含的元素个数。

切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。

切片 s 的长度和容量可通过表达式 len(s)cap(s) 来获取。

var s []int

切片的零值是 nil

nil 切片的长度和容量为 0 且没有底层数组。

  • 从头切片不会影响底层数组长度
  • 不从头切片会影响底层数组长度
a := make([]int, 5, 5)
​
// 从头切片不会影响底层数组长度
b := a[:3] 
println(len(b), cap(b)) // 3 5// 不从头切片会新建底层数组(?)
c := a[1:3]
println(len(b), cap(b)) // 2 4

make

用于给slice或map预分配空间

创建切片

l := 5   // len
c := 10  // caps
a := make([]int, l, c)

创建map

m := make(map[string]int, 5)
println(len(m)) //5

append

向切片追加元素

func append(s []T, vs ...T) []T

append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。

append 的结果是一个包含原切片所有元素加上新添加元素的切片。

s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。

Go 切片:用法和本质 - Go 语言博客 (go-zh.org)

map

// key:   string
// value: int
var m map[string]int
  • 未初始化时值为nil,无法添加key

  • map没有切片,固只有长度,没有容量

    • cap(m)会报错
  • 会自动扩容,可以用make预分配大小

m := map[string]int{
    "a": 1,
    "b": 2,
}
println(len(m)) // 2
​
m = make(map[string]int, 0)
println(len(m)) // 0
m["c"] = 123
println(len(m)) // 1

删除元素

delete(m, key)

检测元素是否存在

elem, ok := m[key]
// ok为true -> 存在
// 否则elem = nil

循环

for i:=0; i < 10; i ++ {
  // expresion
}

while等价

i := 10
for i < 10 {
  // expresion
}

死循环

for {}

range

for 循环的 range 形式可遍历切片或映射。

当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。

for idx, value := range pow
​
// 只需要索引的语法糖
for idx := range pow

switch

func main() {
  fmt.Print("Go runs on ")
  switch os := runtime.GOOS; os {
  case "darwin":
    fmt.Println("OS X.")
  case "linux":
    fmt.Println("Linux.")
  default:
    // freebsd, openbsd,
    // plan9, windows...
    fmt.Printf("%s.\n", os)
  }
}

不会fallthrough

defer

外层函数结束后会调用该函数

defer file.Close()

defer栈

推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。

func main() {
  fmt.Println("counting")
​
  for i := 0; i < 10; i++ {
    defer fmt.Println(i)
  }
​
  fmt.Println("done")
}
  • 上面代码会输出9 - 0

指针

指针保存值的地址

类型 *T 是指向 T 类型值的指针。其零值为 nil

var p *int

& 操作符会生成一个指向其操作数的指针。

i := 42
p = &i

* 操作符表示指针指向的底层值。

fmt.Println(*p) // 通过指针 p 读取 i
*p = 21         // 通过指针 p 设置 i

函数

func funcName(params Type) returnType {
  // body
}

类型

f := func(i, j int) func(int) int {
  return func(num int) int {
    return num*i + num/j
  }
}
fmt.Printf("%T\n", f)
//func(int, int) func(int) int

结构体

//定义
type MyStruct struct {
  field1 int
  field2 string
  Field3 string // 包外可见
}
​
//赋值
var myStruct MyStruct
myStruct = MyStruct{
  field1 1,
  field2 "hello"
  Field3 "exported"
}
//结构体指针
structPtr := &myStruct
//访问结构体字段
myStruct.field1
  • 小写的字段包外不可见

结构体方法

接受者参数的函数

func (r Receiver) fn(param T) rType {
  //body
}

example

type MyStruct struct {
  X int
  Y int
  z int // unexported
}
​
func (myStruct MyStruct) sum() {
  return myStruct.X + myStruct.Y + myStruct.z
}

无法对包外的结构体类型赋予方法

你只能为在同一包内定义的类型的接收者声明方法,而不能为其它包内定义的类型(包括 int 之类的内建类型)的接收者声明方法。

(译注:就是接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。)

在myS包中定义结构体

package myS
​
type MyStruct struct {
  X int
  Y int
  z int // unexported
}

尝试在main包中对myS.MyStruct赋予方法

package main
​
import (
  "myS"
  "fmt"
)
​
// Cannot define new methods on the non-local type 'myS.MyStruct'
//func (s myS.MyStruct) sum() int 
​
// 新建类别来赋予method
type MyStruct myS.MyStruct
​
func (myStruct MyStruct) sum() int {
  return myStruct.X + myStruct.Y
}
​
func main() {
  nonlocal := myS.MyStruct{
    X: 0,
    Y: 0,
  }
  local := MyStruct{
    X: 1,
    Y: 3,
  }
  fmt.Printf("%#v\n", nonlocal) //myS.MyStruct{X:0, Y:0, z:0}
  fmt.Printf("%#v\n", local)    //main.MyStruct{X:1, Y:3, z:0}
  // nonlocal.sum() 无法调用
  println(local.sum())
}

指针接收者

接收者为指针,则传递的是引用而不是值的副本

type MyStruct struct {
  X int
  Y int
}
​
func (my MyStruct) changeX(i int) {
  my.X = i
}
​
func (my *MyStruct) changeXptr(i int) {
  my.X = i // 是(*my).X = i 的语法糖
}
​
func main() {
  my := MyStruct{
    1, 2
  }
  my.changeX(3) // 不影响my
  println(my.X) // 1
  
  my.changeXptr(3) //相当于(&my).changeXptr(3) 语法糖
  // my.X改变
  println(my.X) // 3 
}

go中方法语法糖

  1. 以指针为接收者的方法被调用时,接收者既能为值又能为指针
  2. 以值为接收者的方法被调用时,接收者既能为值又能为指针

选择值或指针作为接收者

Go 语言之旅 (go-zh.org)

使用指针接收者的原因有二:

首先,方法能够修改其接收者指向的值。

其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。

在本例中,ScaleAbs 接收者的类型为 *Vertex,即便 Abs 并不需要修改其接收者。

通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用

接口

接口类型定义一组方法签名,其类型变可以保存任何实现了这些方法的值

type Abser interface {
  Abs() float64
}
​
​
type Vertex struct {
  X, Y float64
}
​
func (v *Vertex) Abs() float64 {
  return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
​
func main() {
  var abser Abser
  v := Vertex {1.23,3.14}
  a = *v // *v实现了Abs(),固是Abser类型
  // 但是v本身没有实现Abs(),固不是Abser类型
  // a = v 报错
}

go中接口是隐式实现的,没有类似java中implements关键字

感觉有点像鸭子类型

interface{} 空接口可以保存任何值

接口使用实例,go中的"toString()"

type Stringer interface {
  String() string
}

类型断言

判断某个值是否是类型T,并生成转换后新值

反射

t := i.(T)

example

var i interface{} = "hello"
​
s, ok := i.(string)

type-switch

switch v := i.(type) {
case T:
    // v 的类型为 T
case S:
    // v 的类型为 S
default:
    // 没有匹配,v 与 i 的类型相同
}
  • i.(type)只用用于type-switch,无法作为表达式

    • 类型不是一等公民

Error

类型定义

type error interface {
    Error() string
}

泛型

A Tour of Go

类型参数

func Index[T comparable](s []T, x T) int

generics

package main
​
// List represents a singly-linked list that holds
// values of any type.
type List[T any] struct {
  next *List[T]
  val  T
}
​
func main() {
}