GO总结一

栏目: Go · 发布时间: 5年前

内容简介:#小引之前学习过goland,但一直没有总结过。最近自己一直在使用goland语言,回头又看了一遍GO圣经和其他资料,做了这个总结。

#小引

之前学习过goland,但一直没有总结过。最近自己一直在使用goland语言,回头又看了一遍 GO 圣经和其他资料,做了这个总结。

GOLAND学习

一、动词(verb ) golang术语在格式化打印时用

GO总结一

二、变量生命周期

声明:var a = "" or  var a string
简短声明: a:=1 这种声明适用在函数内部

包级声明的变量生命周期:和整个程序运行周期一致。
局部声明的变量生命周期:动态的,从创建一个新变量的声明开始,到该变量不再被引用,
然后变量的存储空间可能被回收。

编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,不是用var或new声明变量的方式决定。

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。
    此变量是栈变量。
}

如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止 对短生命周期对象的垃圾回收(从而可能影响程序的性能)

三、变量可赋值性

显式的赋值形式
var a int
a = 1 

程序中还有很多地方会发生隐式的赋值行为:
函数调用会隐式地将调 用参数的值赋值给函数的参数变量
如果用struct作参数,struct属性较多时使用指针。

一个返回语句将隐式地将返回操作的值赋值给结果变量

复合类型的字面量隐式赋值。
例如:
medals := []string{"gold", "silver", "bronze"}
编绎器隐式地对slice的每个元素进行赋值操作
medals[0] = "gold"
medals[1] = "silver"
medals[2] = "bronze"

四、iota 常量生成器

常量声明可以使用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在同一行值相同
    )

五、数组

值类型。函数参数变量接收的是一个复制的副本,所以数据复杂时传递是低效的。

数组顺序初始化

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

> 切片由三部分组成:指针、长度、容量。
> 指针指向第一个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

找了一张图,内容不一样,原理相同 。 GO总结一

下面例子可以看出操作切片虽然超出了len,但是因为没有超出cap所以没有报错,并且自动从底层数组中取值。
sx3 := sx2[:5]
输出 sx3结果:
sx3:[蛇 马 羊 猴 鸡]

sx4:=sx2[:10]
由于操作超出cap,直接panic slice bounds out of range

结构体struct

嵌入:

第一种写法:
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、偶然性的,或由不可预知的问题导致的。一个明智的选择是重新尝试失败的操作。在重试时,我们需要限制重试的时间间隔或重试的次数,防止无限制的重试.

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,才能在库函数中结束程序。

if err := WaitForServer(url); err != nil {
     log.Fatalf("Site is down: %v\n", err)
}

4、输出错误信息 5、忽略

二、错误处理

Go语言引入了一个关于错误处理的标准模式,即error接口

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按照先进后出原则执行。

用法: 一、关闭资源:打开、关闭、连接、断开连接、加锁、释放锁 二、计时

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执行

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")

输出: GO总结一

panic recover

panic后的defer不会执行 recover只接收最近的panic

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

三、匿名函数,闭包

匿名函数由一个不带函数名的函数声明和函数体组成,匿名函数可以直接赋值给一个变量或者直接执行。

//闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在
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语言中接口类型的独特之处在于它是满足隐式实现的。 也就是说,我们没有必要对于给定的具体类型定义所有满足的接口类型。

接口内嵌
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)

接口赋值 一、将对象实例赋值给接口 首先,该对象实例必须实现接口要求的所有方法。例如上面的例子中:

var as *AS
var c C
c =as

二、将一个接口赋值给别一个接口 只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等同的,可以相互赋值。 或者接口A的方法列表是接口B的方法列表的子集, 那么接口B可以赋值给接口A,反过来不成立。

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

接口查询 查询某个对象实例是否实现了某个接口。

var l3 LessAdder2
aa, ok := l3.(LessAdder)

七、goruntine 协程

语法 :在函数或方法调用前加关键字 go

main函数在单独的goruntine中运行,叫main goruntine.

例子:

//每过一秒钟将当前时间返回给客户端
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)
	}
//客户端,连接客户端
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)
	}
}

启动两个终端分别运行客户端代码,效果如下: 两个客户端同时只能有一个客户端可以打印时间。另一个等待中 GO总结一

GO总结一

将服务端代码进行修改:

go handleConn(conn)

之后两个客户端可以同时打印时间了。

八、channels

一个channels是一个 通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。

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 阻塞等待

下图中手进入通道模拟了上锁。也就是进入了阻塞。 GO总结一

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 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞 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可以正常退出。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Letting Go of the Words

Letting Go of the Words

Janice (Ginny) Redish / Morgan Kaufmann / 2007-06-11 / USD 49.95

"Redish has done her homework and created a thorough overview of the issues in writing for the Web. Ironically, I must recommend that you read her every word so that you can find out why your customer......一起来看看 《Letting Go of the Words》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具