Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。接口可以让不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。Go 语言中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。
接下来定义一个结构体BookGO (书),接口OperateBook 可以对而结构体BookGO 进行读、写、翻页函数。接口实际也是指针,如果想对结构体中成员值进行操作,必须使用指针,否则只能读取。接口中只能定义接口函数与其入参、返回值类型,不得拥有变量。下面是显式使用:
package main
import "fmt"
type BookGO struct {
name string
page int
content string
}
type OperateBook interface {
ReadBook() BookGO
WriteBook(contents string)
TurnPage(page int) int // 需定义参数与返回值类型
}
func (book *BookGO) ReadBook() BookGO {
return *book
}
func (book *BookGO) WriteBook(contents string) {
book.content = contents
}
func (book *BookGO) TurnPage(pages int) int {
book.page = pages
return pages
}
func main() {
var book OperateBook
book = &BookGO{ // 接口指向结构体BookGO
name: "Hello",
page: 1,
content: "this is no.1",
}
oldBook := book.ReadBook()
fmt.Println(oldBook) // {Hello 1 this is no.1}
book.WriteBook("this is fixed!")
newBook := book.ReadBook()
fmt.Println(newBook) // {Hello 1 this is fixed!}
page := book.TurnPage(10)
newPage := book.ReadBook()
fmt.Println(page) // 10
fmt.Println((newPage)) // {Hello 10 this is fixed!}
}
也可以不用定义出接口,直接定义结构体的接口,进行隐式使用:
package main
import "fmt"
type BookGO struct {
name string
page int
content string
}
func (book *BookGO) ReadBook() BookGO {
return *book
}
func (book *BookGO) WriteBook(contents string) {
book.content = contents
}
func (book *BookGO) TurnPage(pages int) int {
book.page = pages
return pages
}
func main() {
book := &BookGO{ // 此处直接定义结构体BookGO
name: "Hello",
page: 1,
content: "this is no.1",
}
oldBook := book.ReadBook()
fmt.Println(oldBook) // {Hello 1 this is no.1}
book.WriteBook("this is fixed!")
newBook := book.ReadBook()
fmt.Println(newBook) // {Hello 1 this is fixed!}
page := book.TurnPage(10)
newPage := book.ReadBook()
fmt.Println(page) // 10
fmt.Println((newPage)) // {Hello 10 this is fixed!}
}
接口可以初步理解为结构体衍生的函数,可直接调用。
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
多线程会引入线程之间的同步问题,在 golang 中可以使用 channel 作为同步的工具。通过 channel 可以实现两个 goroutine 之间的通信。创建一个 channel:
ch :=make(chan TYPE {, NUM})
TYPE 指的是 channel 中传输的数据类型,第二个参数是可选的,指的channel 的缓存大小。操作符 <- 用于指定通道的方向,发送或接收,如果未指定方向,则为双向通道。向 channel 传入数据, CHAN <- DATA , CHAN 指的是目的 channel 即收集数据的一方, DATA 则是要传的数据。从 channel 读取数据, DATA := <-CHAN ,和向 channel 传入数据相反,在数据输送箭头的右侧的是 channel,形象地展现了数据从隧道流出到变量里。
并发进行计算切片元素和:
package main
import "fmt"
func sum(numbers []int, ch chan int) {
re := 0
for _, v := range numbers {
re += v
}
ch <- re // 将结果传递给ch通道
}
func main() {
ch := make(chan int)
numbers := []int{1, 2, 4, -3, 8, 9, -12, 4}
l := len(numbers)
// 两个sum并行运行
go sum(numbers[:l/2], ch)
go sum(numbers[l/2:], ch)
reA, reB := <-ch, <-ch // 将结果取出
fmt.Println(reA, reB, reA+reB) // 9 4 13
}
通道存储数据的方式类似于一个队列(queue)或管道(pipe),采用先进先出(FIFO)。当向通道发送数据时,数据会被添加到通道的末尾。同样地,当从通道接收数据时,数据会从通道的前端被移除并返回。
通道是一个引用类型。当创建一个通道时,会分配一段内存空间来存储数据。通道的容量可以是有限的或无限的。带缓冲的通道(buffered channel)可以在发送操作之后继续接收数据,而阻塞的通道(unbuffered channel)则要求在发送数据之前必须有相应的接收操作,否则发送操作会一直阻塞。当两个goroutine通过通道进行通信时,它们会形成一个同步点。发送操作会阻塞,直到接收者准备好接收数据。这种同步机制使得并发程序更加安全和可靠。
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
package main
import (
"fmt"
)
func sum(n int, c chan int) {
re := 0
for i := 0; i < n; i++ {
c <- i
re += 1
}
close(c) // 关闭通道
}
func main() {
c := make(chan int, 10)
go sum(12, c)
for i := range c {
fmt.Println(i)
}
}
可通过range 关键字来实现遍历读取到的数据,类似于与数组或切片 。遍历每个从通道接收到的数据,因为 c 在发送完 10 个数据之后就关闭了通道,所以这里 range 函数在接收到 10 个数据之后就结束了。如果sum的 c 通道不关闭,那么在接收第 11 个数据的通过就会阻塞,造成死锁。