#小引
之前学习过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可以正常退出。
有疑问加站长微信联系(非本文作者))