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等待事件发送。

posted @ 2023-07-08 16:58  好人~  阅读(16)  评论(0编辑  收藏  举报