GO总结一

my_onion · · 1463 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

#小引 之前学习过goland,但一直没有总结过。最近自己一直在使用goland语言,回头又看了一遍GO圣经和其他资料,做了这个总结。 # **GOLAND学习** # 一、动词(verb ) golang术语在格式化打印时用 ![image.png](https://static.studygolang.com/190114/0bf93e2c125c7ce1cd65dd86e3b891fe.png) **** # 二、变量生命周期 ```go 声明:var a = "" or var a string 简短声明: a:=1 这种声明适用在函数内部 包级声明的变量生命周期:和整个程序运行周期一致。 局部声明的变量生命周期:动态的,从创建一个新变量的声明开始,到该变量不再被引用, 然后变量的存储空间可能被回收。 ``` **编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,不是用var或new声明变量的方式决定**。 ```go var global *int func f() { var x int x=1 global = &x //x指针赋值给全局变量,所以x从f函数中逃逸。此变量是堆变量 } func g() { y := new(int) *y = 1 //y虽然是new创建的,但是g函数调用结束后,无法再访问到y。 此变量是栈变量。 } ``` **如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止 对短生命周期对象的垃圾回收(从而可能影响程序的性能)** **** # 三、变量可赋值性 ```go 显式的赋值形式 var a int a = 1 程序中还有很多地方会发生隐式的赋值行为: 函数调用会隐式地将调 用参数的值赋值给函数的参数变量 如果用struct作参数,struct属性较多时使用指针。 一个返回语句将隐式地将返回操作的值赋值给结果变量 复合类型的字面量隐式赋值。 例如: medals := []string{"gold", "silver", "bronze"} 编绎器隐式地对slice的每个元素进行赋值操作 medals[0] = "gold" medals[1] = "silver" medals[2] = "bronze" ``` # 四、iota 常量生成器 ```go 常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用 每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将 会被置为0,然后在每一个有常量声明的行加一。 const (         x = iota // x == 0         y = iota // y == 1         z = iota // z == 2         w // 这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota"     ) const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0 const (         h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同     ) ``` # 五、数组 值类型。函数参数变量接收的是一个复制的副本,所以数据复杂时传递是低效的。 ```go 数组顺序初始化 q :=[3]int{1,2,3} q2 :=[...]int{1,2,3} fmt.Printf("type:%T\n",q) fmt.Printf("type:%T\n",q2) 执行结果: type:[3]int type:[3]int 指定一个索引和对应值列表的方式初始化 const ( USD = iota //美元 EUR //欧元 GBP //英镑 RMB //人民币 ) sypmbol := [...]string{USD:"$",EUR:"€",GBP:"£",RMB: "¥"} fmt.Printf("type:%T\n",sypmbol) fmt.Printf("money:%v,sypmbol:%v\n",RMB,sypmbol[RMB]) 执行结果: type:[4]string money:3,sypmbol:¥ 省略索引初始化 r := [...]int{2:-1} 打印r结果: [0 0 -1] ``` # 六、切片slice ```go > 切片由三部分组成:指针、长度、容量。 > 指针指向第一个slice元素对应的底层数组元素的地址 > 长度对应slice中元素的数目;长度不能超过容量 > 容量一般是从slice的开始位置到底层数据的结尾位置 > 多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠 q3:= []int{1,2,3} fmt.Printf("type:%T\n",q3) 执行结果: type:[]int sx定义了生肖数组 sx[0]=“”, 所以是一个13长度的数组。 sx := [...]string{1: "鼠",2:"牛",3:"虎",4:"兔",5:"龙", 6:"蛇",7:"马",8:"羊",9:"猴",10: "鸡",11:"狗",12:"猪",} [4:7] 是半开半闭区间,代表下标4到6,从下标4到数组结尾13一共9的长度,所以容量是9. sx1 := sx[4:7] sx2 := sx[6:9] fmt.Printf("sx:%v,len:%v,cap:%v\n",sx,len(sx),cap(sx)) fmt.Printf("sx1:%v, len:%v, cap:%v \n",sx1,len(sx1),cap(sx1)) fmt.Printf("sx2:%v, len:%v, cap:%v \n",sx2,len(sx2),cap(sx2)) 执行结果: sx:[ 鼠 牛 虎 兔 龙 蛇 马 羊 猴 鸡 狗 猪],len:13,cap:13 sx1:[兔 龙 蛇], len:3, cap:9 sx1:[蛇 马 羊], len:3, cap:7 ``` 找了一张图,内容不一样,原理相同 。 ![image.png](https://static.studygolang.com/190114/c786c5092eebb25d4c7f36f7e551fc68.png) ```go 下面例子可以看出操作切片虽然超出了len,但是因为没有超出cap所以没有报错,并且自动从底层数组中取值。 sx3 := sx2[:5] 输出 sx3结果: sx3:[蛇 马 羊 猴 鸡] sx4:=sx2[:10] 由于操作超出cap,直接panic slice bounds out of range ``` **** # 结构体struct **嵌入:** ```go 第一种写法: type A struct { a string } type B struct { b string } type C struct { A A B B } c := C{ A:A{"a"}, B:B{"b"}, } fmt.Println(c.A.a) fmt.Println(c.a) //编绎时报错 第二种写法:匿名成员,可以直接使用匿名成员的成员及实现方法。 type C struct { A B } 可以直接.访问匿名成员中的成员 fmt.Println(c.A.a) fmt.Println(c.a) ``` # 函数 function 实参通过值的方式传递,因此函数的形参是实参的拷贝。对形参进行修改不会影响实参。但是,如果实 参包括引用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会由于函数的简介 引用被修改。 ## 一、错误处理策略 1、最常用的方式是传播错误,return err 2、偶然性的,或由不可预知的问题导致的。一个明智的选择是重新尝试失败的操作。在重试时,我们需要限制重试的时间间隔或重试的次数,防止无限制的重试. ```go func WaitForServer(url string) error{ const timeout = 1 * time.Minute deadline := time.Now().Add(timeout) for tries := 0 ;time.Now().Before(deadline);tries++ { _,err := http.Head(url) if err == nil { return nil } fmt.Println("server not responding;retrying....") time.Sleep(time.Second << uint(tries)) } return fmt.Errorf("server %s failed to respond after %s",url,timeout) } ``` 3、输出错误信息并结束程序 这种策略只应在main中执行。对库函数而言,应仅向上传播错误。除非该错误意味着程序内部 包含不一致性,即遇到了bug,才能在库函数中结束程序。 ```go if err := WaitForServer(url); err != nil { log.Fatalf("Site is down: %v\n", err) } ``` 4、输出错误信息 5、忽略 ## 二、错误处理 Go语言引入了一个关于错误处理的标准模式,即error接口 ```go type error interface { Error() string } //自定义错误类型 type MyError struct { Msg string Time time.Time Err error } func (m *MyError) Error() string { return m.Time.String()+ m.Msg + m.Err.Error() } ``` ### defer 当defer语句被执行 时,跟在defer后面的函数会被延迟执行.多条defer按照先进后出原则执行。 **用法**: 一、关闭资源:打开、关闭、连接、断开连接、加锁、释放锁 二、计时 ```go func bigSlowOperation(){ defer trace("bigSlowOperation")() time.Sleep(10*time.Second) } func trace(msg string) func() { star := time.Now() log.Printf("enter %s",msg) return func() { log.Printf("exit %s (%s)",msg,time.Since(star)) } } 调用bigSlowOperation函数,输出: 2018/12/17 15:11:34 enter bigSlowOperation 2018/12/17 15:11:44 exit bigSlowOperation (10.003011214s) ``` **执行顺序** 后进前出,即使defer后执行函数panic,也不影响其他defer执行 ```go func test(i int) { fmt.Println(100/i) } defer fmt.Println("1") defer fmt.Println("2") defer fmt.Println("3") defer test(0) defer fmt.Println("5") ``` 输出: ![image.png](https://static.studygolang.com/190114/514f3972ea3309f7182aa44e7e45370a.png) ### panic recover panic后的defer不会执行 recover只接收最近的panic ```go defer func() { p:=recover() fmt.Println(p) }() defer fmt.Println("1") defer fmt.Println("2") defer fmt.Println("3") defer test(0) defer fmt.Println("5") panic("my panic") 输出结果: 5 3 2 1 runtime error: integer divide by zero ``` ## 三、匿名函数,闭包 匿名函数由一个不带函数名的函数声明和函数体组成,匿名函数可以直接赋值给一个变量或者直接执行。 ```go //闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在 func squares() func() int { var x int fmt.Println("x:",x) return func() int { j := 0 x++ j++ return x * j } } f := squares() //f变量指向一个闭包函数。闭包内变量j被隔离,可保证其安全性。 fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) 执行结果: x: 0 1 2 3 ``` # 方法 在声明一个method的receiver该是指针还是非指针类型时,你需要考虑两方面: 第一方面是这 个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷贝; 第二方面是如果你用指 针类型作为receiver,那么你一定要注意,这种指针类型指向的始终是一块内存地址,就算你对其进行了拷贝。 # 接口 interface Go语言中接口类型的独特之处在于它是满足隐式实现的。 也就是说,我们没有必要对于给定的具体类型定义所有满足的接口类型。 ```go 接口内嵌 type ReadWriter interface { Reader Writer } 实现接口: 一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口 type C interface { Aprint() string Bprint() string Cprint() string } type AS struct { } func (as *AS) Aprint() string { return "A" } func (as *AS) Bprint() string { return "B" } func (as *AS) Cprint() string { return "C" } var c C c = new(AS) ``` 接口赋值 一、将对象实例赋值给接口 首先,该对象实例必须实现接口要求的所有方法。例如上面的例子中: ```go var as *AS var c C c =as ``` 二、将一个接口赋值给别一个接口 只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等同的,可以相互赋值。 或者接口A的方法列表是接口B的方法列表的子集, 那么接口B可以赋值给接口A,反过来不成立。 ```go type Integer int type LessAdder interface { Less(b Integer) bool Add(b Integer) } func (a Integer) Less(b Integer) bool { return a < b } func (a *Integer) Add(b Integer) { *a += b } type LessAdder2 interface { Less(b Integer) bool } //Integer实现了LessAdder接口所有方法 //LessAdder2是LessAdder接口子集 var l1 LessAdder = new(Integer) var l2 LessAdder2 = l1 ``` 接口查询 查询某个对象实例是否实现了某个接口。 ```go var l3 LessAdder2 aa, ok := l3.(LessAdder) ``` # 七、goruntine 协程 语法 :在函数或方法调用前加关键字 go main函数在单独的goruntine中运行,叫main goruntine. 例子: ```go //每过一秒钟将当前时间返回给客户端 func handleConn(conn net.Conn) { defer conn.Close() for { _, err := io.WriteString(conn, time.Now().Format("15:04:05\n")) if err != nil { log.Info("write string to conn err.") return } time.Sleep(1 * time.Second) } } main方法启动,监听8888端口。等待客户端连接,连接成功执行handleConn listener, err := net.Listen("tcp", "localhost:8888") if err != nil { log.Fatal("listen address faild.") } for { conn, err := listener.Accept() if err != nil { log.Info("accept conn faild.") continue } handleConn(conn) } ``` ```go //客户端,连接客户端 func main() { conn, err := net.Dial("tcp", "localhost:8888") if err != nil { log.Fatal(err) } defer conn.Close() mushCopy(os.Stdout, conn) } func mushCopy(dst io.Writer, src io.Reader) { if _, err := io.Copy(dst, src); err != nil { log.Fatal(err) } } ``` 启动两个终端分别运行客户端代码,效果如下: 两个客户端同时只能有一个客户端可以打印时间。另一个等待中 ![image.png](https://static.studygolang.com/190114/b06feabc63b23c2a20f2e3df8aff07d4.png) ![image.png](https://static.studygolang.com/190114/0ad594be86dbfe805260e1c37a006240.png) 将服务端代码进行修改: ```go go handleConn(conn) ``` 之后两个客户端可以同时打印时间了。 # 八、channels 一个channels是一个 通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。 ```go ch := make(chan int,cap) //cap大于零代表带缓存的通道 通道操作:  channel <- value      //发送value到channel  <-channel             //接收并将其丢弃  x := <-channel        //从channel中接收数据,并赋值给x  x, ok := <-channel    //功能同上,同时检查通道是否已关闭或者是否为空 向已经关闭的channel发送数据会报panic异常。可以取数据,没有数据时取通道类型的零值。 ``` 8.1 无缓存的通道 这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的 goroutine 阻塞等待 下图中手进入通道模拟了上锁。也就是进入了阻塞。 ![image.png](https://static.studygolang.com/190114/fd33ed02ec149b279044d6543562ddc8.png) 8.2串联的通道---管道(pipeline) Channels也可以用于将多个goroutine链接在一起,一个Channels的输出作为下一个Channels的输入。这 种串联的Channels就是所谓的管道(pipeline) 8.3单方向channel ,只接受或者只发送 类型chan<- int表示一个只发送int的channel,只能发送不能接收。相反,类型<-chan int表示一个只接收int的channel,只能接收不能发送 任何双向的channel向单向channel变量赋值都将导致双向通道隐式转换为单向通道。 8.4带缓存的channel 这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞 ![image.png](https://static.studygolang.com/190114/12f70c834462e7354a2c680059ac8b22.png) ```go func main() { respnose := make(chan string, 3) go func() { respnose <- request(time.Duration(1), "1") }() go func() { respnose <- request(time.Duration(2), "2") }() go func() { respnose <- request(time.Duration(3), "3") }() fmt.Println(<-respnose) } func request(d time.Duration, s string) string { time.Sleep(d * time.Second) return s } ``` 如果使用无缓存,将导致两个慢的goroutine因为没有接收者而一直处于阻塞状态,这种情况叫goroutine泄露,是一个BUG,GC不会自动回收,所以使用时要考虑不再使用的goroutine可以正常退出。

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1463 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传