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可以正常退出。


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

查看所有标签

猜你喜欢:

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

Scratch少儿趣味编程

Scratch少儿趣味编程

[ 日] 阿部和广 / 陶 旭 / 人民邮电出版社 / 2014-11 / 59.00元

Scratch 是麻省理工学院设计开发的一款编程工具,是适合少儿学习编程和交流的工具和平台,有中文版且完全免费。本书结合孩子们学习的语文、数学、科学、社会、音乐、体育等科目,手把手地教大家如何用Scratch 设计程序(如设计一个自动写作文的程序),配合各式卡通形象,通俗易懂,寓教于乐。麻省理工学院教授米切尔•瑞斯尼克作序推荐。 本书图文并茂,生动风趣,适合中小学生等初学者自学或在家长的帮助......一起来看看 《Scratch少儿趣味编程》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码