go学习笔记-语法
1.包
每个Go程序都由包组成,程序在main包中启动。按照惯例,包名与文件所在路径的最后一个元素相同。例如"math/rand"->package rand
2.引用
多个引用:
import( "fmt" "math" )
3.导出名
在Go语言中,如果以大写字母开头命名,它是导出名。
引用包时,可以只引用它的导出名,任何非导出名在包外均不可访问
声明简写
x int, y int x,y int
4.返回多个结果
默认只返回类型即可,也可以命名返回,应该是函数内部定义的变量,此时return可以为空。
func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return } func split(sum int)(int,int){ x = sum * 4 / 9 y = sum - x return x,y }
没有返回参数的return语句,返回命名参数的值,也就是"直接"返回,直接返回应该只在短函数中,影响可读性
5.变量
声明:var 变量名 类型
变量初始化:var i,j int=1,2
var ( ToBe bool = false MaxInt uint64 = 1<<64 - 1 z complex128 = cmplx.Sqrt(-5 + 12i) )
短变量声明 := (赋值)
6.基本类型
bool
string
int int8 int16 int32 int64(int,uint,uintptr类型在32位系统上通常是32位,64位系统上是64位)
uint uint8 uint16 uint32 uint64 uintptr
byte(unit8简写)
rune(int32 简写)
float32 float64
complex64 complex128
只声明未初始化变量默认值
数值类型:0
bool类型:false
string类型:""
7.类型转换
表达式T(v)将v转换成T
需要强制转换,报错
var m int =5.1
constant 5.1 truncated to integer
8.类型推断
声明变量不指定具体类型(:= 或var =),变量类型由右边的值推断。
1 2 | var i int j := i // j is an int |
i := 42 // int f := 3.142 // float64 g := 0.867 + 0.5i // complex128
当右边的值是非类型数值常量时,新变量可能是int,float64,或complex128,取决于常量的精确度。
9.常量
常量像变量一样声明,但是是用const关键字。
常量可以是字符,字符串,boolean或数值,常量不可以用:=语法声明。
10.for循环
Go语言只有一种循环结构,就是for循环。
for循环三要素(;)
for i := 0; i < 10; i++ { sum += i }
没有(),但需要{}
for 即是while
1 2 3 4 | for i < 100 { sum += i; i++; } |
11. if语句
和for类似,表达式不用(),但代码块需要用{}
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } return lim }
和for类似,在条件判断前,可以使用短语句,声明的变量作用域只在if内
if..else..
if 声明的短变量可以在if的任一else块使用。
12.switch
Go只执行匹配的第一条,不会执行后续case。Go自动提供break,不需要额外break.
Go的switch..case不能是常量,值不能是整型。
case可以接条件判断
switch { case t.Hour() < 12: fmt.Println("Good morning!") case t.Hour() < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") }
相当于if else if else,等同于:
1 2 3 4 5 | do { if () break ; } while ( false ) |
13.defer
defer语句延迟函数的执行,直到其他函数返回了。
defer函数会立刻进行参数检测,但在其他函数返回后才会调用执行。
回调概念
堆栈defer
延迟函数调用入栈。当函数返回时,它以最后入最先出的顺序执行。
14.指针
Go 语言有指针,指针保存值的内存地址
*T 是指向T的指针,默认值是nil。
15.结构体
字段集合
type *** struct{
}
结构体不支持下标访问,支持 .形式访问设置
指向结构体的指针
结构体字段可以通过结构体指针访问。(*p).x->p.x
Name:value
16.数组
[n]T 是类型为T有n个值的数组
数组大小不能改变
17.切片
[]T
数组大小是固定的,切片大小是变化的,随数组元素变化。
a[low:high] low<=index<high
primes := [6]int{2, 3, 5, 7, 11, 13} var s []int = primes[1:4]
数组截取一段是切片
切片-像数组的引用
切片不存储数据,它只是描述底层数组的一个片段,修改切片的元素会影响底层数组的相关元素。其他共享相同底层数组的切片会变化。
切片初始化
切片初始化和没有长度的数组初始化类似。
数组初始化:
[3]bool{true, true, false}
切片初始化:
[]bool{true, true, false}
会创建一个和上面相同的数组,然后构建一个切片指向它。
切片默认值
在切片的时候,可以忽略上界或下界,使用默认值。
下界默认值是0,上界默认值是长度。
a[0:10] a[:10] a[0:] a[:]
切片长度和容量
切片既有长度,也有容量。
长度是它包含的元素的个数。len(s)
容量是底层数组元素从切片的第一个元素开始的个数,cap(s)
如果切片有足够的容量,可以扩展切片的长度。
nil 切片
切片的默认值是nil.
nil切片长度为0,容量为0,无底层数组。
用make创建一个切片
切片可以用内建make函数创建,这是创建动态数组的方式。
make函数创建一个默认数组,并返回数组的引用给切片。
1 | a := make([] int , 5) 切片类型,长度 |
可以通过make第三个参数指定容量
b := make([]int, 0, 5) // len(b)=0, cap(b)=5 b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
切片嵌套
切片可以包含任何类型,包括其他切片
向切片追加
内置append函数:
func append(s []T, vs ...T) []T
第一个参数:类型为T的切片,其他为追加到切片的值。返回值是一个包含初始切片和追加值的切片.
如果底层s太小了不能适应所给的值将会分配一个更大的数组,返回的切片将会指向新分配的数组。
底层数组随元素追加的变化并不是一致的,增加了一个元素7,底层数组增了6个
len=6 cap=6 [88 1 2 3 4 6]
len=7 cap=12 [88 1 2 3 4 6 7]
这里有点奇怪,改变append后切片的值会影响原切面的值,不是新数组吗?猜测,可能需要扩大原数组时才是新数组,不扩大仍是旧的。
18.range
切片或映射for循环迭代
遍历一个切片时,每次迭代会返回2个值,第一个值是小标,第二个是这个下标元素的复制。
切片联系题没有看明白
18.映射
键值对
map的默认值是nil,nil映射没有键,也不能添加键
使用make函数可以返回给定类型的映射。
映射初始化和结构体初始化类似,只是需要键值。
type Vertex struct { Lat, Long float64 } var m = map[string]Vertex{ "Bell Labs": Vertex{ 40.68433, -74.39967, }, "Google": Vertex{ 37.42202, -122.08408, }, }
可以省略Vertex
type Vertex struct { Lat, Long float64 } var m = map[string]Vertex{ "Bell Labs": {40.68433, -74.39967}, "Google": {37.42202, -122.08408}, }
改变map
插入或更新map中的元素
m[key]=elem
获得一个元素:
elem=m[key]
删除元素
delete(m,key)
elem, ok := m[key]如果key在m中,ok为true;否则,ok为false。如果key不在map中,元素是类型的默认值。
17.函数闭包
Go函数可以是闭包。函数嵌套另一个函数,内部函数引用了外部函数的变量,则可能产生闭包。闭包可以用在一个函数与一组"私有"变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持永久性。
在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
18.方法
Go没有类。然而,可以在类型上定义方法。
方法就是有指定接收参数的函数。接收参数在func关键字和方法名之间。
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) }
调用的时候可以直接通过v调用,等同于C#中的 扩展方法
只对自定义类型有用?对基本类型无用?将基本类型转换成自定义类型
type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) }
只能声明带接收器(方法和类型在同一个包中定义。不能声明方法,其接收的类型在另一个包中定义,包括内建类型如int)的方法
接收指针
可以声明接收器为指针的方法。这意味着接收类型有如下语法:*T类型T(T本身不能是指针)。
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func(v Vertex) Scale1(f float64){ v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} v.Scale1(10) //v.Scale(10) fmt.Println(v.Abs()) }
接收:结构体,值类型的复制,不会改变原来的结构体;指针,地址的复制,改变会影响原结构体
带指针的接收器可以改变接收器指向的值,通常方法需要修改接收器,所以指针接收器比值接收器用的更多。
方法和函数的不同
对于函数,声明参数为指针,则必须传入指针,不能传递变量本身
func ScaleFunc(v *Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f }
var v Vertex ScaleFunc(v, 5) // 编译错误 ScaleFunc(&v, 5) // 可以
然而,对于指针接收类型的方法,当她们被调用时可以使用值或指针作为接收器。
var v Vertex v.Scale(5) // 可以 p := &v p.Scale(10) // 可以
func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f }
Go语言将语句v.Scale(5)解释为(&v).Scale(5),因为Scale方法有一个指针接收器。
函数输入参数是变量,非指针,传入参数必须是变量,不能是指针。
func AbsFunc(v Vertex) float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
var v Vertex fmt.Println(AbsFunc(v)) // OK fmt.Println(AbsFunc(&v)) // Compile error!
方法接收是变量,可以传入指针或变量。
func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
var v Vertex fmt.Println(v.Abs()) // 变量调用 p := &v fmt.Println(p.Abs()) // 指针调用
选择值还是指针接收器
有两个选择指针接收器的原因:
第一,可以改变指针指向的值。
第二,避免每次方法调用时都拷贝值,这会更有效率,例如,接收器是大型结构体。
一般而言,指定类型的所有方法都应该有值或指针接收器,但不能两者都有,不然调用哪个呢?
19.接口
接口定义为一系列方法声明的集合。
type Abser interface { Abs() float64 }
类型中有定义Abs()方法的如Vertex和MyFloat
type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) }
type Vertex struct { X, Y float64 } func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
1 2 3 4 | f := MyFloat(-math.Sqrt2) v := Vertex{3, 4} a = f // a MyFloat implements Abser a = &v // a *Vertex implements Abser |
a=v会报错,实现Abs()的是接收器是指针,值没有实现Abs()。
类型通过实现接口的方法来实现接口。没有明确的实现声明(如:)也没有实现关键词如"implements"。
接口值
确定类型值元组,接口值指定底层确定类型值。
调用接口值的方法执行底层类型的同名方法。
接口值-nil底层值
如果接口的指定值是nil,方法会再nil接收器上调用。
在某些语言中这会触发空指针异常,但是Go语言中,以nil作为接收器写函数是很常见的。
注意,接口并不为nil.
type I interface { M() } type T struct { S string } func (t *T) M() { if t == nil { fmt.Println("<nil>") return } fmt.Println(t.S) }
1 2 3 4 5 6 | var i I var t *T //T是没有初始化的 i = t describe(i) i.M() |
i不为nil, t未初始化,t为nil。
Nil 接口值
nil 接口值既无值也无特定类型。在nil接口上调用方法会有运行时错误因为在接口元组内没有类型表明应该调用哪个方法。
空接口
不定义任何方法的接口。
空接口可以包含任何类型的值(任何至少实现一个方法的类型)。空接口在代码中用于处理未知类型的值。例如,fmt.Print处理nterface{}类型的任意参数。
类型断言
类型断言 获取接口值的底层指定值。
t := i.(T)
这个语句获取接口i的底层类型T并将底层值赋值给变量t。
如果i不包含类型,语句会触发panic。
t, ok := i.(T)
测试接口值是否包含指定类型,类型断言可以返回两个值:底层值,boolean类型值(报告断言是否成功)
如果i包含类型T,返回值t是底层值,ok是true。
如果没有,ok是false,t将会是类型T的默认值,没有panic。
类型switch
switch v := i.(type) { case T: // here v has type T case S: // here v has type S default: // no match; here v has the same type as i }
package main import "fmt" func do(i interface{}) { switch v := i.(type) { case int: fmt.Printf("Twice %v is %v\n", v, v*2) case string: fmt.Printf("%q is %v bytes long\n", v, len(v)) default: fmt.Printf("I don't know about type %T!\n", v) } } func main() { do(21) do("hello") do(true) }
接口Stringers
fmt包定义接口Stringers:
type Stringer interface { String() string }
Stringer 类型可以描述自己为string。fmt包寻找这个接口(toString())来打印值。
20 Errors
Go程序用error值表示错误状态。
error类型是内建接口,类似fmt.Stringer:
type error interface { Error() string }
和fmt.Stringer一样,在打印值的时候fmt包也会查找error接口。
函数通常会返回error值,调用代码应该通过测试error是否为nil来处理error。
nil Error值表示成功,非空表示失败。
21.Readers
io包定义了io.Reader接口,表示读取数据流。
Go标准库包含很多这个接口的实现,包括文件,网络连接,压缩等。接口有Read方法:
func (T) Read(b []byte) (n int, err error)
Read用数据填充传入的字节切片,返回字节数和error值,流结束时返回io.EOF error。
22.Images
image包定义Image接口。
package image type Image interface { ColorModel() color.Model Bounds() Rectangle //image.Rectangle同一个包定义 At(x, y int) color.Color }
23.goroutine
由Go运行时管理的轻量级线程。
go f(x, y, z)
f,x,y,z的评估(语法审核)在当前线程发生,执行在新的线程
Goroutines在相同的地址空间运行,因此访问共享内存必须同步。sync包提供有用的原语,在Go中通常不使用它们因为有其他原语。
24. Channels
指定导管,通过Channel可以发送和接收值,<-。
ch <- v // Send v to channel ch. v := <-ch // Receive from ch, and // assign value to v.
数据沿着箭头方向流动。
和映射,切片类似,channel必须在使用前创建:
ch := make(chan int)
默认,发送和接收阻塞等到另一方准备好,这使得goRoutines同步不需要指明locks或条件变量。
缓存Channel
Channel可以缓存。提供缓存长度作为第二个参数给make来初始化缓存Channel
ch := make(chan int, 100)
发送给缓存channel只有当buffer满时才会阻塞。当缓存是空时,接收阻塞。
发送者可以关闭channel来表明没有更多的值需要发送。接受者可以通过给第二个参数测试channel是否关闭
v, ok := <-ch
如果没有更多需要接收的值ok是false,channel关闭。
以下循环重复从channel获取值直到它关闭。
1 | for i := range c |
注意:只有发送方需要关闭channel,接收方不需要。向关闭channel发送会引起panic.
Channel和文件不同,不需要经常去关闭它们。关闭仅限于接收方需要被告知没有更多数据进来,例如range循环的结束。
25.Select
select语句让goroutine等待多个通信操作。
select阻塞直到它的一个示例可以运行,然后它就运行这个示例。如果多个准备好,就随机选择一个。
26.sync.Mutex
channel->goroutine的通信
如果不需要通信,只需要确保同一时间只有一个go线程能访问变量来避免冲突。
这个概念称为互斥,mutex是这种数据结构的惯例名称。
Go的标准库用sync.Mutex和它的两种方法来提供互斥:
Lock Unlock
扩展学习
CSP(Communicating Sequential Processes)的并发模型
并发相关
Codewalk: Share Memory By Communicating
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步