掌握GO:释放方法,接口,通用和并发的潜力|第3部分
#初学者 #编程 #go #microservices

在编程语言的世界中,Golang(也称为GO)以其简单,效率和鲁棒性而获得了巨大的知名度。 Golang凭借其独特的语法和功能强大的功能,为开发人员提供了一个多功能工具包,以构建可扩展和高性能应用程序。在此博客中,我们将深入研究Golang语法的一些关键方面,即方法,接口,仿制药和并发。让我们详细探讨每个主题:

方法

  • GO没有课。但是,您可以定义类型的方法。
  • 一种方法是具有特殊receiver参数的函数。 =接收器出现在func关键字和方法名称之间的自己的参数列表中。
  • 在此示例中,Abs方法具有Vertex类型的接收器,名为v
package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

// Remember: a method is just a function with a receiver argument.
// Here's Abs written as a regular function with no change in functionality.

func Abs_func(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())

    f := Vertex{12, 5}
    fmt.Println(Abs_func(f))
}

输出:

5
13
  • 您只能使用与该方法相同的软件包定义的接收器来声明一种方法。您无法用一个接收器声明其类型在另一个软件包中定义的接收器(其中包括内置类型,例如int)。
package main

import (
    "fmt"
    "math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    f := MyFloat(-math.Sqrt(2))
    fmt.Println(f)
    fmt.Println(f.Abs())
}

输出

-1.4142135623730951
1.4142135623730951

指针接收器

  • GO允许使用指针接收器声明方法。指针接收器具有语法 *t,其中t是一种类型(不是指针类型本身,例如 *int)。这些方法可以修改接收器点的值。相反,具有值接收器的方法以原始值的副本进行操作。
  • 例如,考虑*Vertex上定义的比例方法。如果将*从接收器声明中删除,则该方法将不再能够修改原始顶点值。当方法需要修改其接收器时,通常使用指针接收器。
  • 从接收方声明中删除 * *更改行为以在值的副本上操作。
package main 

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// Method With Pointer Receiver
func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
    fmt.Println(v.X,v.Y)
}

// Scale Function
func (v Vertex) Scale_(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
    fmt.Println(v.X,v.Y)
}

func main() {
    v := Vertex{3, 4}
    v.Scale(10)
    fmt.Println(v.X, v.Y)
    fmt.Println(v.Abs(),"\n")

    f := Vertex{3, 4}
    f.Scale_(10)
    fmt.Println(f.X, f.Y)
    fmt.Println(f.Abs())
}

输出:

30 40
30 40
50 

30 40
3 4
5

方法和指针间接

  • 您可能会注意到,用指针参数的功能必须采用指针:
var v Vertex
ScaleFunc(v, 5)  // Compile error!
ScaleFunc(&v, 5) // OK
  • 虽然使用指针接收器的方法将值或指针作为接收器,但被称为接收器:
var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK

考虑以下示例:

package main

import "fmt"

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    v.Scale(2)
    fmt.Println(v)
    ScaleFunc(&v, 10)
    fmt.Println(v,"\n")

    p := &Vertex{3, 4}
    p.Scale(2)
    fmt.Println(p)
    ScaleFunc(p,10)
    fmt.Println(p)
}

输出:

{6 8}
{60 80} 

&{6 8}
&{60 80}
  • 等效的事情发生在反向方向上。
  • 采用价值参数的函数必须采用该特定类型的值:
var v Vertex
fmt.Println(AbsFunc(v))  // OK
fmt.Println(AbsFunc(&v)) // Compile error!
  • 虽然具有值接收器的方法将值或指针作为接收器被称为接收器:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
  • 在这种情况下,方法调用p.abs()被解释为(*p).abs()。
package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func AbsFunc(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
    fmt.Println(AbsFunc(v),"\n")

    p := &Vertex{3, 4}
    fmt.Println(p.Abs())
    fmt.Println(AbsFunc(*p))
}

输出:

5
5 

5
5

选择一个值或指针接收器

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := &Vertex{3, 4}
    fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
    v.Scale(5)
    fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}

输出:

Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:15 Y:20}, Abs: 25

接口

在GO中,接口提供了一种定义类型必须实现的方法集的方法。这可以使多态性并允许不同类型的类型在满足接口合同的情况下进行互换处理。这是在GO中使用接口的示例:

package main

import (
    "fmt"
    "math"
)

// Shape is an interface for geometric shapes
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Rectangle represents a rectangle shape
type Rectangle struct {
    Width  float64
    Height float64
}

// Area calculates the area of the rectangle
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Perimeter calculates the perimeter of the rectangle
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// Circle represents a circle shape
type Circle struct {
    Radius float64
}

// Area calculates the area of the circle
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// Perimeter calculates the circumference of the circle
func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

func main() {
    rect := Rectangle{Width: 5, Height: 3}
    circle := Circle{Radius: 2.5}

    shapes := []Shape{rect, circle}

    for _, shape := range shapes {
        fmt.Printf("Area: %f\n", shape.Area())
        fmt.Printf("Perimeter: %f\n", shape.Perimeter())
        fmt.Println("------------------")
    }
}

输出:

Area: 15.000000
Perimeter: 16.000000
------------------
Area: 19.634954
Perimeter: 15.707963
------------------

弦乐器

在GO中,Stringer接口是一个内置接口,允许类型定义自己的字符串表示形式。 Stringer接口的String()方法返回类型的字符串表示。这是在GO中使用Stringer接口的示例:

package main

import "fmt"

type Person struct {
    Name string
    other string
}

func (p Person) String() string {
    return fmt.Sprintf("%v %v", p.Name, p.other)
}

func main() {
    a := Person{"Jay!", "Shiya Ram"}
    z := Person{"Jay Shree!", "Radhe Krishna"}
    fmt.Println(a, z)
}

输出:

Jay! Shiya Ram Jay Shree! Radhe Krishna

类型参数

  • GO功能可以写入使用类型参数在多种类型上工作。函数的类型参数在函数的参数之前出现在括号之间。
func Index[T comparable](s []T, x T) int
  • 此声明意味着s是任何类型的T的切片,可满足内置约束comparablex也是相同类型的值。
  • comparable是一个有用的约束,可以在类型的值上使用==!=运算符。在此示例中,我们使用它将值与所有切片元素进行比较,直到找到匹配为止。此Index功能适用于支持比较的任何类型。
package main

import "fmt"

// Index returns the index of x in s, or -1 if not found.
func Index[T comparable](s []T, x T) int {
    for i, v := range s {
        // v and x are type T, which has the comparable
        // constraint, so we can use == here.
        if v == x {
            return i
        }
    }
    return -1
}

func main() {
    // Index works on a slice of ints
    si := []int{10, 20, 15, -10}
    fmt.Println(Index(si, 15))

    // Index also works on a slice of strings
    ss := []string{"foo", "bar", "baz"}
    fmt.Println(Index(ss, "hello"))
}

输出:

2
-1

goroutines

  • Goroutine是由GO运行时管理的轻量级线程。
go f(x, y, z)
  • 开始新的Goroutine运行
f(x, y, z)
  • F,X,Y和Z的评估发生在当前的Goroutine中,而F的执行发生在新的Goroutine中。
  • goroutines在同一地址空间中运行,因此必须同步对共享内存的访问。同步软件包提供了有用的原始图,尽管您不需要太多,因为还有其他原始图。
package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

输出:

hello
world
world
hello
world
hello
hello
world
world
hello
  • 在此代码中,我们有两个函数:saymainsay函数采用字符串参数s,并在每种打印之间以100毫秒的延迟打印五次。
  • main函数中,我们通过调用go say("world")启动了一个新的Goroutine。这意味着say具有参数“世界”的功能将与主要Goroutine同时执行。
  • 同时,主要goroutine继续执行并调用say("hello")。结果,“ Hello”将在主要Goroutine中打印五次。
  • 由于Goroutines的同时性质,该程序的输出将是不可预测的。每个执行情况可能会有所不同,但是您可以期望看到交错的“ Hello”和“ World”消息。

频道

渠道为Goroutines提供了一种通信和同步执行的方法。渠道用于传递Goroutines之间的数据,并确保安全同时访问共享资源。这是GO中的频道的解释:

1。频道创建:

  • 为了创建一个频道,您可以将Make函数与Chan关键字一起使用,然后使用通道将传输的数据类型。例如:
ch := make(chan int) // Creates an unbuffered channel of type int

2。频道操作:

渠道支持两个基本操作:发送和接收数据。

  • 发送数据:要通过频道发送数据,请使用channel <- value表格的<-操作员。例如:
ch <- 177 // Sends the value 177 into the channel 
  • 接收数据:要从频道接收数据,请使用分配左侧的<-操作员。例如:
value := <-ch // Receives a value from the channel and assigns it to the variable "value"

考虑以下示例:

package main

import "fmt"

func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // send sum to c
}

func main() {
    s := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)
}

输出:

-5 17 12

示例代码总结了一个切片中的数字,在两个goroutines之间分配工作。一旦两个goroutines都完成了计算,它就会计算最终结果。

缓冲通道

  • 可以缓冲通道。提供缓冲长度作为初始化缓冲通道的第二个参数:
ch := make(chan int, 100)
  • 仅当缓冲区已满时,才发送到缓冲通道块。缓冲区为空时接收块。
package main

import (
    "fmt"
)

func main() {
    // Create a buffered channel with a capacity of 3
    ch := make(chan int, 3)

    // Send values to the channel
    ch <- 1
    ch <- 2
    ch <- 3

    // Attempting to send another value to the channel would block since the buffer is full

    // Receive values from the channel
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)

    // Attempting to receive another value from the channel would block since the buffer is empty
}

输出:

1
2
3
  • 在此示例中,我们通过将容量指定为make函数的第二个参数来创建一个具有3个容量的缓冲通道ch
  • 然后,我们使用<-操作员向频道发送三个值(1, 2, and 3)。由于通道的缓冲能力为3,因此这些发送不会阻止。
  • 发送值后,我们使用<-操作员和fmt.Println()语句接收并打印它们。同样,由于通道被缓冲并包含三个值,因此这些接收不会阻止。
  • 但是,如果我们尝试将或从频道发送或接收更多值,它将阻止。例如,当缓冲区已满或接收值时,尝试发送值时,当缓冲区为空时会导致相应的goroutine阻止相应的goroutine,直到空间可用或发送值。 当您想将发送和接收操作的时间安排在时间安排方面,屏蔽频道很有用,允许发件人和接收器独立运行到缓冲能力。