go语言:没有class、指针、goroutine与channel、select与time.After
没有class
go语言没有class,但是可以将方法可以被关联到你声明的struct类型,如下介绍了如何将方法关联到类型和构造函数:
package main
import (
"fmt"
)
// 矩形结构体
type Rectangle struct {
Length int
Width int
}
// 计算矩形面积
func (r *Rectangle) Area() int { // 将方法Area()关联到Rectangle类型上。 // Area开头的大写代表方法是public
return r.Length * r.Width
}
// 模拟构造函数
func New(Length, Width int) Rectangle{ // 一般将new开头的函数当作类型的构造函数,当然也可以直接叫new
return Rectangle{Length ,Width }
}
func main() {
r := Rectangle{4, 2}
// 调用 Area() 方法,计算面积
fmt.Println(r.Area())
}
func (r *Rectangle) Area() int
代表可以修改r,func (r Rectangle) Area() int
不可以修改r。
组合、嵌入与转发
组合就是一个类是另一个类的成员变量,这里主要介绍转发。一般嵌入实现方法的转发,即让当前结构体直接使用其他结构体中的方法和字段,如下代码:
package main
import "fmt"
type report struct {
sol int
temperature
location
}
type temperature struct {
high, low celsius
}
type location struct {
lat, long float64
}
type celsius float64
func (t temperature) average() celsius {
return (t.high + t.low) / 2
}
func main() {
report := report{
sol: 15,
location: location{-4.5895, 137.4417},
temperature: temperature{high: -1.0, low: -78.0},
}
fmt.Printf("average %vº C\n", report.average()) // 直接使用temperature中的average()方法
fmt.Printf("average %vº C\n", report.temperature.average())
fmt.Printf("%vº C\n", report.high)
report.high = 32
fmt.Printf("%vº C\n", report.temperature.high)
}
代码解析:
- sruct嵌入:report中temperature和location没有给定字段名,只给定字段类型,此时report对象可以直接使用temperature和location中的方法和字段。
如果嵌入的两个类型中拥有相同方法就可能产生冲突(相当于多继承中函数冲突?),如下:
package main
import "fmt"
type sol int
type report struct {
sol
location
temperature
}
type temperature struct {
high, low celsius
}
type location struct {
lat, long float64
}
type celsius float64
func (s sol) days(s2 sol) int {
days := int(s2 - s)
if days < 0 {
days = -days
}
return days
}
func (l location) days(l2 location) int {
// To-do: complicated distance calculation
return 5
}
// func (r report) days(s2 sol) int { // 取消注释,代码就不会报错
// return r.sol.days(s2)
// }
func main() {
report := report{sol: 15}
d := report.days(1446)
fmt.Println(d)
}
- report.days(1446)调用的是哪个day(),并不明确,从而导致报错。
- 但是如果report自己也定义了day()方法,那么就不会报错。(取消注释部分)
指针
go指针与c++指针的区别:
- 指针和普通变量访问数据成员和方法,都是使用".",而不是"->"
- 使用指针作为方法的接收者,那么才能在方法中修改数据成员,即将方法可以被关联到你声明的struct类型。
接口
接口用于实现多态。
package main
import(
"fmt"
"strings"
)
type talker interface {
talk() string
}
func shout(t talker) {
louder := strings.ToUpper(t.talk())
fmt.Println(louder)
}
type martian struct{}
func (m martian) talk() string {
return "nack nack "
}
type laser int
func(l *laser) talk() string{
return strings.Repeat("pew", int(*l))
}
func main() {
shout(martian{})
shout(&martian{})
pew := laser(2)
shout(&pew)
// shout(pew) // 错误
}
上面代码中shout(pew)
会报错,这是因为:指向laser类型的指针满足了接口的类型,laser的类型并不满足接口的类型,所以shout(pew)会出错。
如果嵌入的类型中实现了接口的函数,那么当前类型也符合接口的类型:
package main
import(
"fmt"
"strings"
)
type talker interface {
talk() string
}
func shout(t talker) {
louder := strings.ToUpper(t.talk())
fmt.Println(louder)
}
type martian struct{}
func (m martian) talk() string {
return "nack nack "
}
type laser int
func(l *laser) talk() string{
return strings.Repeat("pew", int(*l))
}
type startship struct{
laser
}
func main() {
shout(martian{})
shout(&martian{})
pew := laser(2)
shout(&pew)
// shout(pew) // 错误
s:=startship{laser(3)}
shout(&s)
}
再举一个例子:fmt包声明的Stringer接口如下:
type Stringer interface {
String() string
}
所以我们的自定义类型,只要我们的类型定义了String()函数的实现,就可以使用fmt.Println来打印对象,如下:
package main
import "fmt"
// location with a latitude, longitude in decimal degrees.
type location struct {
lat, long float64
}
// String formats a location with latitude, longitude.
func (l location) String() string {
return fmt.Sprintf("%v, %v", l.lat, l.long)
}
func main() {
curiosity := location{-4.5895, 137.4417}
fmt.Println(curiosity)
}
如上面代码所示,Go标准库导出了很多只有一个方法的接口,这样我们自己定义的类型时,就可以很容易实现接口,从而调用标准库的方法来实现一些功能。
goroutine与channel
goroutine:协程,并发执行的代码。
package main
import (
"fmt"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello() // 开启一个协程
fmt.Println("main function")
}
channel:
ch := make(chan int) // ch 的类型是 chan int
ch <- x // 将x发送到通道中
x = <-ch // 将通道中的数据接收到x上
<-ch // 丢弃通道中接收的东西
close(ch) // 关闭
接受时:等待另一个goroutine向通道中发送数据。
发送时:等待另一个goroutine将通道中的数据取走。
简单的channel实例:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
for i:=0; i<5; i++ {
go sleepyGopher(i, c)
}
for i:=0; i<5;i++ {
gopherID := <-c // 多个goroutine向通道中发送数据,每次接收一个
fmt.Println( "gopher" ,gopherID," has finished sleeping")
}
}
func sleepyGopher(id int, c chan int) {
time.Sleep(3*time.Second)
fmt.Println(" ..",id," snore ...")
c<-id
}
结果是无序的输出:
.. 0 snore ...
.. 2 snore ...
gopher 0 has finished sleeping
.. 3 snore ...
.. 4 snore ...
gopher 2 has finished sleeping
gopher 3 has finished sleeping
gopher 4 has finished sleeping
.. 1 snore ...
gopher 1 has finished sleeping
select与time.After:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
fori:=0;i<5;i++ {
go sleepyGopher(i, c)
}
timeout := time.After(2*time.Second) // timeout是通道,会在两秒以后向timeout中发送数据
for i:=0;i<5;i++{
select{ // 两秒以后timeout中将会有数据,程序就会return
case gopherID :=<-c: // 所以只执行睡眠在两秒以内的goroutine
fmt.Println("gopher ",gopherID, " has finished sleeping" )
case <-timeout:
fmt.Println("my patience ran out")
return
}
}
}
func sleepyGopher(id int, c chan int) {
time.Sleep( time.Duration( rand.Intn(4000))*time.Millisecond ) // 0~4秒的随机时间
c <- id
}
C++协程一般指的是可以挂起和恢复的函数,go中通过goroutine执行并发代码,通过channel等待事件发送。