当前位置 博文首页 > shelgi的博客:go语言几个最重要知识点的总结(1)
接口这个词从开始学java就有接触到,所以先说说什么是接口。说的正式一点,接口(Interface)是一些方法特征的集合,而在go语言中接口就是一种抽象的数据类型。可能这样很不好理解,那就写点例子结合着讲。
package main
import "fmt"
type dog struct{}
func (d dog) say() {
fmt.Println("汪汪汪")
}
type cat struct{}
func (c cat) say() {
fmt.Println("喵喵喵")
}
type person struct {
name string
}
func (p person) say() {
fmt.Println("啊啊啊")
}
//接口不注重类型,只注重实现的方法
//只要实现了say()方法的类型就能成为sayer这个接口类型
type sayer interface {
say()
}
func hit(arg sayer) {
arg.say()
}
func main() {
c1 := cat{}
hit(c1)
d1 := dog{}
hit(d1)
p1 := person{name: "123"}
hit(p1)
}
这个例子里面我们首先有三个结构体,狗、猫、人,并且对应的有他们叫的方法,比如狗会汪汪汪的叫。这个时候我们再定义一个接口sayer,只要实现了say()方法的数据类型就能成为sayer这个接口类型。这个时候再写个函数hit(),传入的参数就是接口类型的变量,其实也就是所有实现了say()方法的类型,然后会调用传入参数对应的say()方法。
经过这个例子再来看看接口到底有什么用: 如果像往常一样,hit()函数传入的参数是特定的某个结构体,那就只能实现某一种结构体的方法,对于其他两个结构体就要写hit2,hit3来分别实现。但是有了接口,我们可以把所有实现同一个方法的类型变成一种集合,从而可以在一个函数调用不同数据类型下的同一方法。
然后接口大概作用知道了,再来说说接口一些进阶点的东西
在这里强调两句话:
1.一个类型可以实现多个接口
2.不同的类型也可以实现同一个接口
刚才的例子就属于不同类型同一接口,下面再来一个同一类型不同接口以及接口嵌套的例子
package main
import "fmt"
type mover interface {
move()
}
type sayer2 interface {
say()
}
type Person struct {
name string
age int
}
//指针接收者实现接口,赋值的时候只能传入指针!!!而使用值接收者则可以传入值和指针
func (p *Person) move() {
fmt.Printf("%s在跑\n", p.name)
}
func (p *Person) say() {
fmt.Printf("%s在叫\n", p.name)
}
//一个类型可以实现多个接口
//不同的类型也可以实现同一个接口
//接口的嵌套
type animal interface {
mover
sayer2
}
func main() {
var m animal
p1 := &Person{
name: "hahah",
age: 18,
}
m = p1
m.move()
m.say()
fmt.Println(m)
}
我定义一个人的结构体,有姓名和年纪,同时还实现了人的动和叫两个方法。此外我还有两个针对动和叫的接口mover和sayer2,这个时候我再把用animal这个接口把这两个接口嵌套,也就是说我的人这个数据类型因为实现了动和叫,也就可以是animal这个接口类型。
在这里还有个细节要注意,如果我把我的实现方法写成
func (p Person) move() {
fmt.Printf("%s在跑\n", p.name)
}
func (p Person) say() {
fmt.Printf("%s在叫\n", p.name)
}
那么主函数实例化的时候无论传入指针还是值都可以赋值给animal这个接口,但是如果像上面那样,我们的接口是指针接收者那实例化Person的时候赋值的p1只能是指针。也就是我代码中的那句指针接收者实现接口,赋值的时候只能传入指针!!!而使用值接收者则可以传入值和指针
go语言其实在指针这块已经很友好了,通常你传入指针或者值都能帮你自动转换,只是特殊的几个地方需要自己注意。
然后就是空接口的应用以及接口的断言
//空接口的应用
//1.可以作为函数的参数,例子:fmt.Println()的参数就是空接口
//2.作为map的value
var x = make(map[string]interface{}, 10)
x["name"] = "xjj"
x["age"] = 20
x["hobby"] = []string{"篮球", "唱歌", "敲代码", "玩游戏", "摄影"}
fmt.Println(x)
//接口的值由两部分组成:具体的类型+具体类型的值
//接口的断言
//开始猜一下接口的类型
var i interface{}
i = 123
ret, ok := i.(string)
if !ok {
fmt.Println("不是字符串类型")
} else {
fmt.Println("是字符串类型", ret)
}
//使用switch进行断言
switch t := i.(type) {
case string:
fmt.Println("是字符串类型", t)
case bool:
fmt.Println("是bool类型", t)
case int:
fmt.Println("是int类型", t)
}
空接口可以传入任何数据类型,所以它经常被用作传入函数的参数或者是map的value
上面所有代码运行的结果
谈到goroutine,最先想到的肯定是并发,但是要谈并发又得从大的进程、线程说起,所以这里我就简单的说一下这些概念,毕竟我自己学操作系统的时候没认真就没特别清晰,呜呜呜。
当我们运行一个应用的时候,那操作系统就会为这个应用程序启动一个进程。而每个进程至少包含一个线程,也就是主线程,线程呢就是执行空间,也就可以用来运行我们所写的代码。那go语言它有什么特别的呢,一般操作系统都是在物理处理器上调用线程来运行,而go它是在逻辑处理器上调用goroutine来运行。
再就是谈谈并发和并行:用下面的例子来理解
并发和并行
并发:同一时间段,我同时和两个人聊天
并行:同一时刻,我和朋友都在和老师聊天
其实这些既然go语言的开发者都封装好了,那我们一开始还不用那么关注原理,等日后进阶一点再去看看实现的原理也更方便理解一点,所以还是先来几个例子。
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
wg.Add(2)
fmt.Println("Start Goroutines")
//声明一个匿名函数创建goroutine
go func() {
defer wg.Done()
//显示小写字母表3次
for count := 0; count < 3; count++ {
for char := 'a'; char < 'a'+26; char++ {
fmt.Printf("%c ", char)
}
fmt.Println()
}
}()
//声明一个匿名函数创建goroutine
go func() {
defer wg.Done()
//显示大写字母表3次
for count := 0; count < 3; count++ {
for char := 'A'; char < 'A'+26; char++ {
fmt.Printf("%c ", char)
}
fmt.Println()
}
}()
//等待goroutine结束
fmt.Println("Waiting to finish")
wg.Wait()
fmt.Println("Finish!")
}
这个例子是并发显示大小写字母表,其实程序是并发的,只不过第一个goroutine完成的太快,所以每次看到的都是先大写再小写。这个简单的例子里面也有些小细节需要注意。
1.runtime.GOMAXPROCS(),这个是指定调度器的逻辑处理器数量,1.5版本之前默认是1,之后默认是全部核数,所以需要的话可以调用这个函数进行配置。
2.sync这个包主要是用来记录维护goroutine,sync.WaitGroup是一个计数的信号量,可以记录运行的goroutine数,我们代码中Add(2)就说明我们用了两个goroutine,然后需要使用goroutine也很简单,只需要前面加上go关键字。wg.Done()就是表示任务完成,此时会把之前WaitGroup的计数量-1.
3.wg.Wait()会等所有任务结束才停止等待,也就是等计数量为0
还是上面那个代码的例子,为了实现上面说的“同时和两个人聊天的效果”,我们把指定的处理器数量增大,再来看看并发的效果
当我们把处理器设置为5,就可以看到大小写交替的情况了,由于随机性,尝试的时候可以多试几次。
还有一个简单的例子也可以说明这个
package main
import (
"fmt"
"runtime"
"sync"
)
//并发和并行
//并发:同一时间段,我同时和两个人聊天
//并行:同一时刻,我和朋友都在和老师聊天
var wg sync.WaitGroup
//goroutine类似于线程(用户态线程)
func hello(i int) {
fmt.Println("hello goroutine", i)
wg.Done() //计数器-1
}
func main() {
runtime.GOMAXPROCS(3) //占用的cpu核数,1.5+默认使用全部核数
wg.Add(100) //计数器
for i := 0; i < 100; i++ {
go hello(i)
}
fmt.Println("hello main")
//time.Sleep(time.Second)
wg.Wait() //等待计数器为0才退出
}
channel通道主要是为了进行同步,当一个资源需要共享时用channel就可以在goroutine之间确保同步交换数据。
channel有两种:无缓冲通道和有缓冲通道,区别还得从它的创建开始讲。
unbuf:=make(chan int)//无缓冲通道
buf:=make(chan int