这是我参与「第三届青训营 -后端场」笔记创作活动的的第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
- 文件结构
> tree module
module
├── another.go
└── main.go
- 文件内容
// 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 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
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中方法语法糖
- 以指针为接收者的方法被调用时,接收者既能为值又能为指针
- 以值为接收者的方法被调用时,接收者既能为值又能为指针
选择值或指针作为接收者
使用指针接收者的原因有二:
首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。
在本例中,
Scale和Abs接收者的类型为*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
}
泛型
类型参数
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() {
}