go基础

go语法与基本命令

go语法

基本上是A Tour of 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
}

泛型

怎么都2022年了,还有语言刚支持泛型啊?

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

go命令

使用方法

go <command> [arguments]

命令列表

bug         start a bug report
build       compile packages and dependencies
clean       remove object files and cached files
doc         show documentation for package or symbol
env         print Go environment information
fix         update packages to use new APIs
fmt         gofmt (reformat) package sources
generate    generate Go files by processing source
get         add dependencies to current module and install them
install     compile and install packages and dependencies
list        list packages or modules
mod         module maintenance
work        workspace maintenance
run         compile and run Go program
test        test packages
tool        run specified go tool
version     print Go version
vet         report likely mistakes in packages

使用案例

创建go.mod文件

go mod init [module-path]

下载并添加依赖到当前模块

go get <module-name>

添加未添加的依赖到go.mod文件/移除没有使用的依赖

go mod tidy
  • 比如在代码中引入了gorm.io/gorm,但没有将其添加到go.mod
  • 或者go.mod中存在没有使用的依赖

安装依赖时,遇事不决请使用go mod tidy

执行文件

go run 

配置国内代理

七牛云 - Goproxy.cn

$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
  • go 1.13以上

pkg文档

pkg.go.dev