通过示例学习-Go-语言-2023-二十二-

通过示例学习 Go 语言 2023(二十二)

Go (Golang) 中一组句子中的最大单词数

来源:golangbyexample.com/maximum-words-group-sentences-go/

目录

  • 概述

  • 程序

概述

给定一组句子。找到在该组句子中出现的最大单词数。

示例

Input: ["Hello World", "This is hat]
Output: 3

Input: ["Hello World", "This is hat", "The cat is brown"]
Output: 4

程序

这里是相应的程序。

package main

import "fmt"

func mostWordsFound(sentences []string) int {
	lenSentences := len(sentences)

	max := 0
	for i := 0; i < lenSentences; i++ {
		countWord := countW(sentences[i])
		if countWord > max {
			max = countWord
		}
	}

	return max
}

func countW(s string) int {

	lenS := len(s)
	numWords := 0

	for i := 0; i < lenS; {
		for i < lenS && string(s[i]) == " " {
			i++
		}

		if i < lenS {
			numWords++
		}

		for i < lenS && string(s[i]) != " " {
			i++
		}
	}

	return numWords
}

func main() {
	output := mostWordsFound([]string{"Hello World", "This is hat"})
	fmt.Println(output)

	output = mostWordsFound([]string{"Hello World", "This is hat", "The cat is brown"})
	fmt.Println(output)
}

输出

3
4

注意: 请查看我们的 Golang 高级教程。本系列的教程内容详尽,我们尝试覆盖所有概念并附带示例。本教程适合希望掌握和深入理解 Golang 的人 - Golang 高级教程

如果你有兴趣了解如何在 Golang 中实现所有设计模式。如果是这样,那么这篇文章就是为你准备的 - 所有设计模式 Golang

Go (Golang)中的中介设计模式

来源:golangbyexample.com/mediator-design-pattern-golang/

注意:有兴趣了解如何在 GO 中实现所有其他设计模式,请查看此完整参考 – Go 中的所有设计模式

目录

介绍:

  • 实用示例

  • 完整工作代码:

介绍:

中介设计模式是一种行为设计模式。该模式建议创建一个中介对象,以防止对象之间的直接通信,从而避免它们之间的直接依赖。

中介模式的一个很好的例子是铁路系统平台。两个火车之间不会直接通信以了解平台的可用性。stationManager充当中介,仅使平台对一列火车可用。火车与stationManager连接并相应地行动。它维护等待火车的队列。如果有任何火车离开平台,它会通知其中一列火车下次到达平台。

注意stationManger在下面代码中如何充当火车平台之间的中介。

  • passengerTrain 和 goodsTrain 实现了火车接口。

  • stationManger 实现了中介接口。

实用示例

train.go

package main

type train interface {
    requestArrival()
    departure()
    permitArrival()
}

passengerTrain.go

package main

import "fmt"

type passengerTrain struct {
    mediator mediator
}

func (g *passengerTrain) requestArrival() {
    if g.mediator.canLand(g) {
        fmt.Println("PassengerTrain: Landing")
    } else {
        fmt.Println("PassengerTrain: Waiting")
    }
}

func (g *passengerTrain) departure() {
    fmt.Println("PassengerTrain: Leaving")
    g.mediator.notifyFree()
}

func (g *passengerTrain) permitArrival() {
    fmt.Println("PassengerTrain: Arrival Permitted. Landing")
}

goodsTrain.go

package main

import "fmt"

type goodsTrain struct {
    mediator mediator
}

func (g *goodsTrain) requestArrival() {
    if g.mediator.canLand(g) {
        fmt.Println("GoodsTrain: Landing")
    } else {
        fmt.Println("GoodsTrain: Waiting")
    }
}

func (g *goodsTrain) departure() {
    g.mediator.notifyFree()
    fmt.Println("GoodsTrain: Leaving")
}

func (g *goodsTrain) permitArrival() {
    fmt.Println("GoodsTrain: Arrival Permitted. Landing")
}

mediator.go

package main

type mediator interface {
    canLand(train) bool
    notifyFree()
}

stationManager.go

package main

import "sync"

type stationManager struct {
    isPlatformFree bool
    lock           *sync.Mutex
    trainQueue     []train
}

func newStationManger() *stationManager {
    return &stationManager{
        isPlatformFree: true,
        lock:           &sync.Mutex{},
    }
}

func (s *stationManager) canLand(t train) bool {
    s.lock.Lock()
    defer s.lock.Unlock()
    if s.isPlatformFree {
        s.isPlatformFree = false
        return true
    }
    s.trainQueue = append(s.trainQueue, t)
    return false
}

func (s *stationManager) notifyFree() {
    s.lock.Lock()
    defer s.lock.Unlock()
    if !s.isPlatformFree {
        s.isPlatformFree = true
    }
    if len(s.trainQueue) > 0 {
        firstTrainInQueue := s.trainQueue[0]
        s.trainQueue = s.trainQueue[1:]
        firstTrainInQueue.permitArrival()
    }
}

main.go

package main

func main() {
    stationManager := newStationManger()
    passengerTrain := &passengerTrain{
        mediator: stationManager,
    }
    goodsTrain := &goodsTrain{
        mediator: stationManager,
    }
    passengerTrain.requestArrival()
    goodsTrain.requestArrival()
    passengerTrain.departure()
}

输出:

PassengerTrain: Landing
GoodsTrain: Waiting
PassengerTrain: Leaving
GoodsTrain: Arrival Permitted. Landing

完整工作代码:

package main

import (
    "fmt"
    "sync"
)

type train interface {
    requestArrival()
    departure()
    permitArrival()
}

type passengerTrain struct {
    mediator mediator
}

func (g *passengerTrain) requestArrival() {
    if g.mediator.canLand(g) {
        fmt.Println("PassengerTrain: Landing")
    } else {
        fmt.Println("PassengerTrain: Waiting")
    }
}

func (g *passengerTrain) departure() {
    fmt.Println("PassengerTrain: Leaving")
    g.mediator.notifyFree()
}

func (g *passengerTrain) permitArrival() {
    fmt.Println("PassengerTrain: Arrival Permitted. Landing")
}

type goodsTrain struct {
    mediator mediator
}

func (g *goodsTrain) requestArrival() {
    if g.mediator.canLand(g) {
        fmt.Println("GoodsTrain: Landing")
    } else {
        fmt.Println("GoodsTrain: Waiting")
    }
}

func (g *goodsTrain) departure() {
    g.mediator.notifyFree()
    fmt.Println("GoodsTrain: Leaving")
}

func (g *goodsTrain) permitArrival() {
    fmt.Println("GoodsTrain: Arrival Permitted. Landing")
}

type mediator interface {
    canLand(train) bool
    notifyFree()
}

type stationManager struct {
    isPlatformFree bool
    lock           *sync.Mutex
    trainQueue     []train
}

func newStationManger() *stationManager {
    return &stationManager{
        isPlatformFree: true,
        lock:           &sync.Mutex{},
    }
}

func (s *stationManager) canLand(t train) bool {
    s.lock.Lock()
    defer s.lock.Unlock()
    if s.isPlatformFree {
        s.isPlatformFree = false
        return true
    }
    s.trainQueue = append(s.trainQueue, t)
    return false
}

func (s *stationManager) notifyFree() {
    s.lock.Lock()
    defer s.lock.Unlock()
    if !s.isPlatformFree {
        s.isPlatformFree = true
    }
    if len(s.trainQueue) > 0 {
        firstTrainInQueue := s.trainQueue[0]
        s.trainQueue = s.trainQueue[1:]
        firstTrainInQueue.permitArrival()
    }
}

func main() {
    stationManager := newStationManger()
    passengerTrain := &passengerTrain{
        mediator: stationManager,
    }
    goodsTrain := &goodsTrain{
        mediator: stationManager,
    }
    passengerTrain.requestArrival()
    goodsTrain.requestArrival()
    passengerTrain.departure()
}

输出:

PassengerTrain: Landing
GoodsTrain: Waiting
PassengerTrain: Leaving
GoodsTrain: Arrival Permitted. Landing

在 Go(Golang)中两个已排序数组的中位数

来源:golangbyexample.com/medium-two-sorted-arrays-golang/

目录

  • 概述

  • 程序

概述

目标是返回两个已排序数组的中位数。例如,如果输入是

[1,5]
[3,4,6]

那么中位数是 4,因为如果我们对两个数组进行排序,4 正好在中间。

[1,3,4,5,6]

如果总元素个数是偶数,也就是说两个数组的总长度是偶数。在这种情况下,中位数将会

(n/2 + (n/2 +1 ))/2

其中n是两个数组的总长度。

例如,如果输入是

[1,2]
[3,4]

那么中位数是(2+3)/2=2.5,因为如果我们对两个数组进行排序,2 和 3 正好在中间。

[1,2,3,4]

所以如果两个数组的总长度为n,那么有两种情况。

  • 如果n是奇数,那么中位数位于位置n/2+1

  • 如果n是偶数,那么中位数位于位置(n/2 + (n/2 +1 ))/2

程序

package main

import "fmt"

func main() {
	median := findMedianSortedArrays([]int{1, 2}, []int{3, 4})
	fmt.Println(median)
}

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
	firstArrayLen := len(nums1)
	secondArrayLen := len(nums2)

	var mid int
	i := 0
	j := 0
	var k int
	mid = (firstArrayLen+secondArrayLen)/2 + 1

	//This is the case in which the total lenght of two arrays is odd and there is only one median
	if (firstArrayLen+secondArrayLen)%2 == 1 {
		var median float64

		for k < mid {
			if i < firstArrayLen && j < secondArrayLen {
				if nums1[i] <= nums2[j] {
					median = float64(nums1[i])
					i++
					k++
				} else {
					median = float64(nums2[j])
					j++
					k++
				}
			} else if i < firstArrayLen {
				median = float64(nums1[i])
				i++
				k++
			} else {
				median = float64(nums2[j])
				j++
				k++
			}

		}
		return median
	} else { //This is the case in which the total lenght of two arrays is even and there is only two medians. We need to return average of these two medians
		var median1 float64
		var median2 float64

		for k < mid {
			median1 = median2
			if i < firstArrayLen && j < secondArrayLen {
				if nums1[i] <= nums2[j] {
					median2 = float64(nums1[i])
					i++
					k++
				} else {
					median2 = float64(nums2[j])
					j++
					k++
				}
			} else if i < firstArrayLen {
				median2 = float64(nums1[i])
				i++
				k++
			} else {
				median2 = float64(nums2[j])
				j++
				k++
			}

		}
		return (median1 + median2) / 2
	}
}

输出

2.5

Go 语言中的备忘录设计模式(Memento Design Pattern in Go (Golang))

来源:golangbyexample.com/memento-design-pattern-go/

注意:如有兴趣了解其他设计模式在 Go 中的实现,请查看此完整参考 – Go 语言中的所有设计模式

目录

**介绍:

  • 实际例子:

  • 完整工作代码:

介绍:

备忘录设计模式是一种行为设计模式。它允许我们为对象保存检查点,从而使对象能够恢复到之前的状态。基本上,它有助于对象的撤销-重做操作。以下是备忘录设计模式的设计组件。

  • 发起者:这是实际保存状态的对象,状态被保存为备忘录。

  • 备忘录:这是保存发起者状态的对象。

  • 照管者:这是保存多个备忘录的对象。给定一个索引,它返回相应的备忘录。

发起者定义了两个方法。savememento()restorememento()

  • savememento()- 在此方法中,发起者将其内部状态保存到备忘录对象中。

  • restorememento()- 此方法以备忘录对象为输入。发起者将自身恢复到过去的备忘录。因此,恢复了之前的状态。

实际例子:

originator.go

package main

type originator struct {
    state string
}

func (e *originator) createMemento() *memento {
    return &memento{state: e.state}
}

func (e *originator) restorememento(m *memento) {
    e.state = m.getSavedState()
}

func (e *originator) setState(state string) {
    e.state = state
}

func (e *originator) getState() string {
    return e.state

memento.go

package main

type memento struct {
    state string
}

func (m *memento) getSavedState() string {
    return m.state
}

caretaker.go

注意,照管者包含了 mementoArray,它保存了所有备忘录。

package main

type caretaker struct {
    mementoArray []*memento
}

func (c *caretaker) addMemento(m *memento) {
    c.mementoArray = append(c.mementoArray, m)
}

func (c *caretaker) getMenento(index int) *memento {
    return c.mementoArray[index]
}

main.go

package main

import "fmt"

func main() {
    caretaker := &caretaker{
        mementoArray: make([]*memento, 0),
    }
    originator := &originator{
        state: "A",
    }
    fmt.Printf("Originator Current State: %s\n", originator.getState())
    caretaker.addMemento(originator.createMemento())

    originator.setState("B")
    fmt.Printf("Originator Current State: %s\n", originator.getState())

    caretaker.addMemento(originator.createMemento())
    originator.setState("C")

    fmt.Printf("Originator Current State: %s\n", originator.getState())
    caretaker.addMemento(originator.createMemento())

    originator.restorememento(caretaker.getMenento(1))
    fmt.Printf("Restored to State: %s\n", originator.getState())

    originator.restorememento(caretaker.getMenento(0))
    fmt.Printf("Restored to State: %s\n", originator.getState())
}

输出:

originator Current State: A
originator Current State: B
originator Current State: C
Restored to State: B
Restored to State: A 

完整工作代码:

package main

import "fmt"

type originator struct {
    state string
}

func (e *originator) createMemento() *memento {
    return &memento{state: e.state}
}

func (e *originator) restoreState(m *memento) {
    e.state = m.getSavedState()
}

func (e *originator) setState(state string) {
    e.state = state
}

func (e *originator) getState() string {
    return e.state
}

type memento struct {
    state string
}

func (m *memento) getSavedState() string {
    return m.state
}

type caretaker struct {
    mementoArray []*memento
}

func (c *caretaker) addMemento(m *memento) {
    c.mementoArray = append(c.mementoArray, m)
}

func (c *caretaker) getMenento(index int) *memento {
    return c.mementoArray[index]
}

func main() {
    caretaker := &caretaker{
        mementoArray: make([]*memento, 0),
    }
    originator := &originator{
        state: "A",
    }
    fmt.Printf("Originator Current State: %s\n", originator.getState())
    caretaker.addMemento(originator.createMemento())
    originator.setState("B")
    fmt.Printf("Originator Current State: %s\n", originator.getState())
    caretaker.addMemento(originator.createMemento())
    originator.setState("C")
    fmt.Printf("Originator Current State: %s\n", originator.getState())
    caretaker.addMemento(originator.createMemento())
    originator.restoreState(caretaker.getMenento(1))
    fmt.Printf("Restored to State: %s\n", originator.getState())
    originator.restoreState(caretaker.getMenento(0))
    fmt.Printf("Restored to State: %s\n", originator.getState())
}

输出:

originator Current State: A
originator Current State: B
originator Current State: C
Restored to State: B
Restored to State: A

在 Go 中合并重叠区间(Golang)

来源:golangbyexample.com/merge-overlapping-intervals-golang/

目录

  • 概述

  • 程序

概述

给定一个区间数组,每个区间都有开始时间和结束时间,合并重叠区间。如果第一个区间的结束时间大于第二个区间的开始时间,则这两个区间被称为重叠。假设两个区间都是按开始时间排序的。

示例。

假设我们有下面两个区间

[2,6]
[5,8]

然后这些区间是重叠的,因为第一个区间的结束时间大于第二个区间的开始时间。同时,上述区间是按开始时间排序的。

类似地,下面两个区间不重叠

[2,6]
[8,9]

因此目标是合并给定数组中的重叠区间

例如。

Input: [[1,4], [8,10], [9,12], [3,5]]
Output: [[1,5], [8,12]]

程序

以下将是逻辑

  • 根据开始时间对区间数组进行排序。

  • 从索引 0 开始合并重叠区间。如上所述,如果第一个区间的结束时间大于第二个区间的开始时间,则这两个区间被称为重叠。

package main

import (
	"fmt"
	"sort"
)

func main() {
	output := merge([][]int{{1, 4}, {8, 10}, {9, 12}, {3, 5}})
	fmt.Println(output)

	output = merge([][]int{{1, 4}, {4, 5}})
	fmt.Println(output)

	output = merge([][]int{{2, 2}, {2, 2}})
	fmt.Println(output)

	output = merge([][]int{{2, 3}, {4, 5}, {6, 7}, {8, 9}, {1, 10}})
	fmt.Println(output)
}

type intervalsArray [][]int

func (intA intervalsArray) Len() int {
	return len(intA)
}

func (intA intervalsArray) Swap(i, j int) {
	intA[i], intA[j] = intA[j], intA[i]
}

func (intA intervalsArray) Less(i, j int) bool {
	return intA[i][0] < intA[j][0]
}

func merge(intervals [][]int) [][]int {

	intA := intervalsArray(intervals)

	sort.Sort(intA)

	intervalsSorted := [][]int(intA)
	//fmt.Println(intervalsSorted)

	var output [][]int
	currentIntervalStart := intervalsSorted[0][0]
	currentIntervalEnd := intervalsSorted[0][1]
	for j := 1; j < len(intervalsSorted); j++ {
		if currentIntervalEnd >= intervalsSorted[j][0] {
			if intervalsSorted[j][1] > currentIntervalEnd {
				currentIntervalEnd = intervalsSorted[j][1]
			}
		} else {
			output = append(output, []int{currentIntervalStart, currentIntervalEnd})
			currentIntervalStart = intervalsSorted[j][0]
			currentIntervalEnd = intervalsSorted[j][1]
		}
	}
	output = append(output, []int{currentIntervalStart, currentIntervalEnd})
	return output

}

输出

[[1 5] [8 12]]
[[1 5]]
[[2 2]]
[[1 10]]

注意: 查看我们的 Golang 高级教程。本系列的教程内容详尽,我们尝试覆盖所有概念并附有例子。此教程适合那些希望获得专业知识和扎实理解 Golang 的人——Golang 高级教程

如果你有兴趣了解如何在 Golang 中实现所有设计模式。如果是的话,这篇文章适合你——所有设计模式 Golang

在 Go 中合并两个已排序数组(Golang)

来源:golangbyexample.com/merge-two-sorted-arrays-golang/

目录

  • 概述

  • 程序

概述

给定两个数组。两个数组均已排序。

  • 第一个数组的长度为 m+n

  • 第二个数组的长度为 n

目标是合并这些已排序的数组。第一个数组的长度足够,因此只需修改第一个数组

Input1: [2,3,4,0,0]
Input2: [1,5]
Output: [1, 2, 3, 4, 5]

Input1: [4,5,0,0,0,0]
Input2: [1, 2, 3, 7]
Output: [1, 2, 3, 4, 5, 7]

这是我们可以采取的方法

  • 将第一个数组中的所有元素按排序顺序移动到末尾。第一个数组将变为
[0,0,2,3,4]
  • 现在从第m个索引元素开始,第一个数组的0个索引在第二个数组中。

  • 比较两个数组,并将较小的放置在第0个索引中。第一个数组将变为

[1, 0, 2, 3, 4]
  • 重复此过程。第一个数组末尾的值将在覆盖之前放置在前面,因为我们有足够的空间

程序

这是相应的程序。

package main

import "fmt"

func merge(nums1 []int, m int, nums2 []int, n int) []int {

	if m == 0 {
		for k := 0; k < n; k++ {
			nums1[k] = nums2[k]
		}
		return nums1
	}
	nums1 = moveToEnd(nums1, m)
	i := n
	j := 0
	for k := 0; k < m+n; k++ {
		if i < m+n && j < n {
			if nums1[i] < nums2[j] {
				nums1[k] = nums1[i]
				i++
			} else {
				nums1[k] = nums2[j]
				j++
			}
		} else if j < n {
			nums1[k] = nums2[j]
			j++
		}

	}

	return nums1

}

func moveToEnd(nums []int, m int) []int {
	lenNums := len(nums)

	k := lenNums

	for i := m - 1; i >= 0; i-- {
		nums[k-1] = nums[i]
		k--
	}

	return nums
}

func main() {
	output := merge([]int{2, 3, 4, 0, 0}, 3, []int{1, 5}, 2)
	fmt.Println(output)

	output = merge([]int{4, 5, 0, 0, 0, 0}, 2, []int{1, 2, 3, 7}, 4)
	fmt.Println(output)
}

输出

[1 2 3 4 5]
[1 2 3 4 5 7]

注意: 请查看我们的 Golang 高级教程。本系列教程详细阐述了所有概念,并附有示例。该教程适合希望获得专业知识和扎实理解 Golang 的学习者 – Golang 高级教程

如果您有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,这篇文章就是为您准备的 – 所有设计模式 Golang

Go(Golang)中的方法链

来源:golangbyexample.com/method-chaining-go/

为了实现方法链的功能,链中的方法应该返回接收者。链中最后一个方法返回接收者是可选的。

让我们看看方法链的一个示例。

package main

import "fmt"

type employee struct {
	name   string
	age    int
	salary int
}

func (e employee) printName() employee {
	fmt.Printf("Name: %s\n", e.name)
	return e
}

func (e employee) printAge() employee {
	fmt.Printf("Age: %d\n", e.age)
	return e
}

func (e employee) printSalary() {
	fmt.Printf("Salary: %d\n", e.salary)
}

func main() {
	emp := employee{name: "Sam", age: 31, salary: 2000}
	emp.printName().printAge().printSalary()
}

输出

Name: Sam
Age: 31
Salary: 2000

Go(Golang)中的方法

来源:golangbyexample.com/method-in-golang/

这是 Golang 综合教程系列的第二十章。有关该系列其他章节的信息,请参考这个链接 – Golang 综合教程系列

下一个教程接口

上一个教程地图

现在让我们查看当前教程。以下是当前教程的目录。

目录

**概述

  • 为什么使用方法

  • 方法的格式

  • 结构体上的方法

  • 指针接收者上的方法

  • 何时使用指针接收者

  • 关于方法的更多注意事项

  • 匿名嵌套结构字段上的方法

  • 导出方法

  • 方法链

  • 非结构类型的方法

  • 结论

概述

在 Golang 中,方法实际上就是一个带接收者的函数。接收者是某种特定类型的实例,如结构体,但它可以是任何其他自定义类型的实例。因此,当你将一个函数附加到一个类型时,这个函数就成为该类型的方法。方法将可以访问接收者的属性,并可以调用接收者的其他方法。

为什么使用方法

由于方法允许你在类型上定义函数,因此它使你能够在 Golang 中编写面向对象的代码。此外,还有一些其他好处,比如在同一个包中两个不同的方法可以拥有相同的名称,而这在函数中是不可能的。

方法的格式

以下是方法的格式

func (receiver receiver_type) some_func_name(arguments) return_values

方法接收者和接收者类型出现在func关键字和函数名称之间。返回值出现在最后。

此外,让我们了解函数和方法之间的更多区别。它们之间有一些重要的区别。以下是函数的签名

函数:

func some_func_name(arguments) return_values

我们已经看到了方法的签名

方法:

func (receiver receiver_type) some_func_name(arguments) return_values

从上述签名中可以看出,方法有一个接收者参数。这是函数和方法之间唯一的区别,但由于这个区别,它们在功能上有所不同。

  • 函数可以作为一等对象使用并可以传递,而方法则不能。

  • 方法可以用于接收器上的链式调用,而函数不能以相同的方式使用。

  • 可以存在不同接收器下同名的方法,但同一包内不能存在两个不同名称的函数。

结构体上的方法

Golang 不是一种面向对象的语言。它不支持类型继承,但它允许我们在任何自定义类型(包括结构体)上定义方法。由于结构体是字段的命名集合,方法也可以在其上定义。因此,Golang 中的结构体可以与面向对象语言中的类进行比较。

让我们看一个结构体上方法的例子。

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func (e employee) details() {
    fmt.Printf("Name: %s\n", e.name)
    fmt.Printf("Age: %d\n", e.age)
}

func (e employee) getSalary() int {
    return e.salary
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}
    emp.details()
    fmt.Printf("Salary %d\n", emp.getSalary())
}

输出

Name: Sam
Age: 31
Salary 2000

请注意,接收器在方法内部是可用的,接收器的字段可以在方法内部访问。

接收器的字段也可以在方法内部更改吗?

让我们来看看。

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func (e employee) setNewName(newName string) {
    e.name = newName
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}
    emp.setNewName("John")
    fmt.Printf("Name: %s\n", emp.name)
}

输出

Name: Sam

在上面的代码中,定义了一个方法setNewName在员工结构体上。在这个方法中,我们这样更新员工的名字。

e.name = newName

在设置新名字后,当我们在主函数中再次打印员工的名字时,我们看到打印的是旧名字“Sam”,而不是“John”。这是因为方法是在值接收器上定义的。

func (e employee) setNewName(newName string)

由于该方法是在值接收器上定义的,因此当调用该方法时,会创建接收器的副本,并且该副本在方法内部可用。由于它是一个副本,对值接收器所做的任何更改对调用者都是不可见的。这就是为什么它打印旧名字“Sam”,而不是“John”。现在,心中浮现出一个问题,是否有任何方法可以解决这个问题。答案是肯定的,这就是指针接收器的作用所在。

指针接收器上的方法

在上面的例子中,我们看到了一种关于值接收器的方法。对值接收器所做的任何更改对调用者都是不可见的。方法也可以在指针接收器上定义。对指针接收器所做的任何更改将对调用者可见。让我们看一个例子。

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func (e *employee) setNewName(newName string) {
    e.name = newName
}

func main() {
    emp := &employee{name: "Sam", age: 31, salary: 2000}
    emp.setNewName("John")
    fmt.Printf("Name: %s\n", emp.name)
}

输出

Name: John

在上面的程序中,我们在指针接收器上定义了方法setNewName

func (e *employee) setNewName(newName string)

然后我们创建了一个员工指针并在其上调用了setNewName方法。我们看到在setNewName内部对员工指针所做的更改对调用者是可见的,并且打印出新的名字。

调用一个具有指针接收器的方法是否有必要创建员工指针?不,没必要。可以在员工实例上调用该方法,语言会处理正确将其作为指针传递给方法。这种灵活性是语言提供的。

让我们看一个例子。

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func (e *employee) setNewName(newName string) {
    e.name = newName
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}
    emp.setNewName("John")

    fmt.Printf("Name: %s\n", emp.name)

    (&emp).setNewName("Mike")
    fmt.Printf("Name: %s\n", emp.name)
}

输出

Name: John
Name: Mike

我们在上面的程序中看到,即使方法是在指针接收器上定义的,但我们用非指针的员工实例来调用这个方法。

emp.setNewName("John")

但语言将接收器作为指针传递,因此更改对调用者是可见的。

这种调用方式也是有效的。

(&emp).setNewName("Mike")

现在,另一种情况。如果在值接收者上定义了一个方法,可以用接收者的指针调用该方法吗?

是的,即使这样也是有效的,语言会确保正确地将参数作为值接收者传递,无论该方法是在指针或普通结构上调用的。

让我们看一个示例。

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func (e employee) setNewName(newName string) {
    e.name = newName
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}
    emp.setNewName("John")

    fmt.Printf("Name: %s\n", emp.name)
    (&emp).setNewName("Mike")

    fmt.Printf("Name: %s\n", emp.name)
    emp2 := &employee{name: "Sam", age: 31, salary: 2000}
    emp2.setNewName("John")
    fmt.Printf("Name: %s\n", emp2.name)
}

输出

Name: Sam
Name: Sam
Name: Sam

请注意,在这三种情况下,setNewName方法有一个值接收者,因此更改对调用者不可见,因为值作为副本传递。它在所有三种情况下都打印旧名称。

总结一下我们上面学到的内容。

  • 如果方法有一个值接收者,它支持用值接收者和指针接收者调用该方法。

  • 如果方法有一个指针接收者,则也支持用值接收者和指针接收者调用该方法。

这与函数不同,如果

  • 如果函数有一个指针参数,则它只会接受一个指针作为参数。

  • 如果函数有一个值参数,则它只会接受一个值作为参数。

何时使用指针接收者

  • 当方法内部对接收者的更改需要对调用者可见时。

  • 当结构体很大时,最好使用指针接收者,否则每次调用方法时都会生成结构体的副本,这样开销很大。

关于方法的更多注意事项

  • 接收者类型必须在与方法定义相同的包中定义。在定义一个存在于不同包中的接收者上的方法时,将会引发以下错误。
ERROR: cannot define new methods on non-local types
  • 到目前为止,我们已经看到使用点操作符进行方法调用的方法。还有另一种调用方法的方式,如下面的示例所示。
package main

import "fmt"

type employee struct {
	name   string
	age    int
	salary int
}

func (e employee) details() {
	fmt.Printf("Name: %s\n", e.name)
	fmt.Printf("Age: %d\n", e.age)
}

func (e *employee) setName(newName string) {
	e.name = newName
}

func main() {
	emp := employee{name: "Sam", age: 31, salary: 2000}
	employee.details(emp)

	(*employee).setName(&emp, "John")

	fmt.Printf("Name: %s\n", emp.name)
}

输出

Name: Sam
Age: 31
Name: John

在上面的示例中,我们看到了一种不同的方法来调用方法。有两种情况。

  • 当方法有一个值接收者时,可以如下调用,即结构体名称后跟方法名称。第一个参数是值接收者本身。
employee.details(emp)
  • 当方法有一个指针接收者时,可以如下调用,即指向结构体名称的指针后跟方法名称。第一个参数是指针接收者。
(*employee).setName(&emp, "John")

还需注意,方法的参数从第二个参数开始,正如上面 setName 函数所示:

(*employee).setName(&emp, "John")

这种风格很少被使用,而我们之前讨论的点表示法是推荐的,也是最常见的方式。

匿名嵌套结构字段上的方法

让我们看一个程序。

package main

import "fmt"

type employee struct {
	name   string
	age    int
	salary int
	address
}

type address struct {
	city    string
	country string
}

func (a address) details() {
	fmt.Printf("City: %s\n", a.city)
	fmt.Printf("Country: %s\n", a.country)
}

func main() {
	address := address{city: "London", country: "UK"}

	emp := employee{name: "Sam", age: 31, salary: 2000, address: address}

	emp.details()

	emp.address.details()
}

输出

City: London
Country: UK
City: London
Country: UK

请注意,在上面的程序中,地址结构的 details 方法可以通过两种方式访问。

emp.details()
emp.address.details()

因此,在匿名嵌套结构的情况下,可以直接访问该结构的方法。

导出方法

Go 没有任何公共、私有或保护关键字。控制包外可见性的唯一机制是使用大写和小写格式。

  • 大写标识符是导出的。大写字母表示这是一个导出标识符,可以在包外使用。

  • 非首字母大写的标识符 是非导出的。小写字母表示该标识符是非导出的,只能从同一包内访问。

因此,任何以大写字母开头的结构体都是导出的。同样,任何以大写字母开头的结构体字段也是导出的,反之亦然。任何以大写字母开头的结构体方法也是导出的。让我们看一个示例,展示结构体、结构体字段和方法的导出与非导出。见下面的 model.gotest.go。它们都属于 main 包。

  • 结构

    • 结构体 Person 是导出的

    • 结构体 company 是非导出的

  • 结构的字段

    • Person 结构体字段 Name 是导出的

    • Person 结构体字段 age 是非导出的,但 Name 是导出的

  • 结构的方法

    • Person 结构体的方法 GetAge() 是导出的

    • Person 结构体的方法 getName() 是非导出的

model.go

package main

import "fmt"

//Person struct
type Person struct {
    Name string
    age  int
}

//GetAge of person
func (p *Person) GetAge() int {
    return p.age
}

func (p *Person) getName() string {
    return p.Name
}

type company struct {
}

让我们在同一个 main 包中编写一个文件 test.go。见下文。

test.go

package main

import "fmt"

//Test function
func Test() {
    //STRUCTURE IDENTIFIER
    p := &Person{
        Name: "test",
        age:  21,
    }
    fmt.Println(p)
    c := &company{}
    fmt.Println(c)

    //STRUCTURE'S FIELDS
    fmt.Println(p.Name)
    fmt.Println(p.age)

    //STRUCTURE'S METHOD
    fmt.Println(p.GetAge())
    fmt.Println(p.getName())

}

运行此文件时,它能够访问 model.go 中所有导出和非导出的字段,因为它们都在同一个 main 包中。没有编译错误,输出如下

输出

&{test 21}
&{}
test
21
21
test

如果我们将文件 model.go 移动到一个名为 model 的不同包中,那么在运行 ‘go build’ 时会出现编译错误。所有编译错误都是因为 test.gomain 包中无法引用 model.go 中的非导出字段。

编译错误将是

p.getName undefined (cannot refer to unexported field or method model.(*Person).getName)

方法链

为了使方法链成为可能,链中的方法应返回接收者。链中最后一个方法返回接收者是可选的。

让我们看看方法链的一个例子。

package main

import "fmt"

type employee struct {
	name   string
	age    int
	salary int
}

func (e employee) printName() employee {
	fmt.Printf("Name: %s\n", e.name)
	return e
}

func (e employee) printAge() employee {
	fmt.Printf("Age: %d\n", e.age)
	return e
}

func (e employee) printSalary() {
	fmt.Printf("Salary: %d\n", e.salary)
}

func main() {
	emp := employee{name: "Sam", age: 31, salary: 2000}
	emp.printName().printAge().printSalary()
}

输出

Name: Sam
Age: 31
Salary: 2000

非结构类型的方法

方法也可以在非结构自定义类型上定义。非结构自定义类型可以通过类型定义创建。以下是创建新自定义类型的格式

type {type_name} {built_in_type}

例如,我们可以创建一个名为 myFloat 的自定义类型,其类型为 float64

type myFloat float64

方法可以在命名的自定义类型上定义。请参见下面的示例:

代码

package main

import (
    "fmt"
    "math"
)

type myFloat float64

func (m myFloat) ceil() float64 {
    return math.Ceil(float64(m))
}

func main() {
    num := myFloat(1.4)
    fmt.Println(num.ceil())
}

输出

2

结论

这就是关于在 Go 中使用方法的全部内容。希望你喜欢这篇文章。请在评论中分享反馈/错误/改进建议

下一个教程接口

前一个教程地图

在 Go(Golang)中对非结构体类型的方法

来源:golangbyexample.com/method-non-struct-type-golang/

方法也可以在非结构体自定义类型上定义。非结构体自定义类型可以通过类型定义创建。以下是创建新自定义类型的格式

type {type_name} {built_in_type}

例如,我们可以定义一个名为myFloat的自定义类型,其类型为float64

type myFloat float64

可以在命名的自定义类型上定义方法。请看下面的示例:

代码

package main

import (
    "fmt"
    "math"
)

type myFloat float64

func (m myFloat) ceil() float64 {
    return math.Ceil(float64(m))
}

func main() {
    num := myFloat(1.4)
    fmt.Println(num.ceil())
}

输出

2

Go(Golang)中的两个数字的最小值

来源:golangbyexample.com/min-of-two-numbers-golang/

目录

  • 概述

  • 代码:

概述

math 包提供了一个 Min 方法,可以用来获取两个数字的最小值。该

以下是函数的签名。它接受两个浮点数作为输入并返回一个浮点数。

func Min(x, y float64) float64

另外,一些 Min 函数的特殊情况是

  • Min(x, +Inf) = Min(+Inf, x) = +Inf

  • Min(x, NaN) = Min(NaN, x) = NaN

  • Min(+0, ±0) = Min(±0, +0) = +0

  • Min(-0, -0) = -0

代码:

package main

import (
    "fmt"
    "math"
)

func main() {
    min := math.Min(2, 3)
    fmt.Println(min)

    min = math.Min(-2.1, -3.3)
    fmt.Println(min)
}

输出:

2
-3.3

Golang 中的最小堆

来源:golangbyexample.com/minheap-in-golang/

目录

** 介绍

  • 最小堆操作

  • 实现

介绍

最小堆是一个完全二叉树,其中父节点的值小于或等于其左子节点和右子节点的值。完全二叉树是指除了最后一层外,所有层都是满的二叉树。

我们使用数组来表示最小堆。根元素是 arr[0]。对于索引 i,我们有

  • 左子节点 – 2*i + 1

  • 右子节点 – 2*i + 2

下面是一个最小堆的表示

![](https://gitee.com/OpenDocCN/geekdoc-golang-zh/raw/master/docs/go-exam-2023/img/3a7852c4ca1f9283ebb010132a829a14.png)

相应的数组为[2, 3, 5, 7, 6, 8]

对于索引 0,我们有

  • 左子节点 – 2*0 + 1 = 1

  • 右子节点 – 2*0 + 2 = 2

因此 arr[0]即 2 的左子节点为 arr[1]即 3,右子节点为 arr[2]即 5

由于每个节点值小于或等于其子节点的值,因此根节点的值是最小值。

最小堆操作

  • 插入元素– 需要 O(log n)的时间。如果插入的值小于其父节点,则需要向上遍历以修复。这种遍历会持续到插入的值大于其父节点,或者插入的值成为根节点。第二种情况发生在插入的值是最小的。

  • 移除最小元素 – 需要 O(log n)的时间。它保存根值,然后用数组中的最后一个值替换它。然后对根进行最小堆化,这需要 O(log n)的时间,因为它向下遍历直到小于其父节点。

  • 获取最小值 – 需要 O(1)的时间。返回根值

实现

package main

import "fmt"

type minheap struct {
    heapArray []int
    size      int
    maxsize   int
}

func newMinHeap(maxsize int) *minheap {
    minheap := &minheap{
        heapArray: []int{},
        size:      0,
        maxsize:   maxsize,
    }
    return minheap
}

func (m *minheap) leaf(index int) bool {
    if index >= (m.size/2) && index <= m.size {
        return true
    }
    return false
}

func (m *minheap) parent(index int) int {
    return (index - 1) / 2
}

func (m *minheap) leftchild(index int) int {
    return 2*index + 1
}

func (m *minheap) rightchild(index int) int {
    return 2*index + 2
}

func (m *minheap) insert(item int) error {
    if m.size >= m.maxsize {
        return fmt.Errorf("Heap is full")
    }
    m.heapArray = append(m.heapArray, item)
    m.size++
    m.upHeapify(m.size - 1)
    return nil
}

func (m *minheap) swap(first, second int) {
    temp := m.heapArray[first]
    m.heapArray[first] = m.heapArray[second]
    m.heapArray[second] = temp
}

func (m *minheap) upHeapify(index int) {
    for m.heapArray[index] < m.heapArray[m.parent(index)] {
        m.swap(index, m.parent(index))
        index = m.parent(index)
    }
}

func (m *minheap) downHeapify(current int) {
    if m.leaf(current) {
        return
    }
    smallest := current
    leftChildIndex := m.leftchild(current)
    rightRightIndex := m.rightchild(current)
    //If current is smallest then return
    if leftChildIndex < m.size && m.heapArray[leftChildIndex] < m.heapArray[smallest] {
        smallest = leftChildIndex
    }
    if rightRightIndex < m.size && m.heapArray[rightRightIndex] < m.heapArray[smallest] {
        smallest = rightRightIndex
    }
    if smallest != current {
        m.swap(current, smallest)
        m.downHeapify(smallest)
    }
    return
}
func (m *minheap) buildMinHeap() {
    for index := ((m.size / 2) - 1); index >= 0; index-- {
        m.downHeapify(index)
    }
}

func (m *minheap) remove() int {
    top := m.heapArray[0]
    m.heapArray[0] = m.heapArray[m.size-1]
    m.heapArray = m.heapArray[:(m.size)-1]
    m.size--
    m.downHeapify(0)
    return top
}

func main() {
    inputArray := []int{6, 5, 3, 7, 2, 8}
    minHeap := newMinHeap(len(inputArray))
    for i := 0; i < len(inputArray); i++ {
        minHeap.insert(inputArray[i])
    }
    minHeap.buildMinHeap()
    for i := 0; i < len(inputArray); i++ {
        fmt.Println(minHeap.remove())
    }
    fmt.Scanln()
}

输出:

2
3
5
6
7
8

Go (Golang)中的最小路径和程序

来源:golangbyexample.com/minimum-path-sum-golang/

目录

  • 概述

  • 程序

概述

有一个包含非负整数的 m*n 矩阵。目标是找到从左上角到右下角的最小路径和。你只能向右或向下移动。

例如,假设我们有以下矩阵

![](https://gitee.com/OpenDocCN/geekdoc-golang-zh/raw/master/docs/go-exam-2023/img/695599be49b8f5c57183e0e8a42623c8.png)

然后最小和路径如下。它的和为 1+1+2+2+1 = 7

[{0,0}, {1,0}, {1,1}, {2,1}, {2,2}

这是一个动态规划问题,因为它具有最优子结构。假设矩阵的名称是 input。

  • minPath[0][0] = input[0][0]

  • minPath[i][j] = ming(minPath[i-1][j], minPath[i][j-1])) + input[i][j]

其中 minPath[i][j]表示从{0,0}到{i,j}的最小和

程序

这是相同程序。

package main

import "fmt"

func minPathSum(grid [][]int) int {
	rows := len(grid)
	columns := len(grid[0])
	sums := make([][]int, rows)

	for i := 0; i < rows; i++ {
		sums[i] = make([]int, columns)
	}

	sums[0][0] = grid[0][0]

	for i := 1; i < rows; i++ {
		sums[i][0] = grid[i][0] + sums[i-1][0]
	}

	for i := 1; i < columns; i++ {
		sums[0][i] = grid[0][i] + sums[0][i-1]
	}

	for i := 1; i < rows; i++ {
		for j := 1; j < columns; j++ {
			if sums[i-1][j] < sums[i][j-1] {
				sums[i][j] = grid[i][j] + sums[i-1][j]
			} else {
				sums[i][j] = grid[i][j] + sums[i][j-1]
			}
		}
	}

	return sums[rows-1][columns-1]

}

func main() {
	input := [][]int{{1, 4, 2}, {1, 3, 2}, {2, 2, 1}}
	output := minPathSum(input)
	fmt.Println(output)
}

输出

7

注意:查看我们的 Golang 高级教程。此系列的教程内容详尽,我们尽力覆盖所有概念及示例。本教程适合希望获得专业知识和对 Golang 有扎实理解的人 - Golang 高级教程

如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,这篇文章就是为你准备的 - 所有设计模式 Golang

Go 中的模块 (Golang)

来源:golangbyexample.com/modules-golang/

目录

  • 概述

  • 模块世界之前

    • 在 Go 版本 1.11 之前

    • 在 Go 版本 1.11 中

    • 在 Go 版本 1.13 之后

  • 创建模块

  • 向你的项目添加依赖

    • 直接将其添加到 go.mod 文件

    • 执行 go get

    • 将依赖添加到你的源代码并执行 go mod tidy

  • 添加供应商目录

  • 模块导入路径

    • 该模块是一个实用模块,且你计划发布你的模块

    • 该模块是一个实用模块,你不打算发布你的模块

    • 该模块是一个可执行模块

  • 在同一模块内导入包

  • 从不同模块本地导入包

  • 选择库的版本

    • 在次要或补丁版本中有所不同

    • 在主要版本中有所不同

  • go mod 命令

  • go.mod 文件中的直接与间接依赖

    • go.mod 文件中的间接依赖示例
  • 结论

概述

模块是 Go 对依赖管理的支持。模块的定义是一个相关包的集合,其根目录下有 go.modgo.mod 文件定义了

  • 模块导入路径。

  • 模块的依赖要求以确保成功构建。它定义了项目的依赖要求并将其锁定到正确的版本。

将模块视为包含一组包的目录。这些包也可以嵌套。模块提供了。

  • 依赖管理。

  • 有了模块,Go 项目不必一定位于 $GOPATH/src 文件夹中。

除了 go.mod 文件,Go 还保留了 go.sum 文件,其中包含所有项目依赖模块的加密哈希值。这是为了验证你的项目依赖模块没有变化。

模块之前的世界

让我们逐一查看版本变化,以充分理解之前的限制以及自模块以来的变化。

  • 在 Go 版本 1.11 之前,模块根本不存在。

  • Go 版本 1.11 – 模块引入但尚未最终确定。

  • Go 版本 1.13 – 引入了模块。

在 Go 版本 1.11 之前

在模块之前,Go 只有包。 $GOPATH 位置将有三个目录。

  • src

  • pkg

  • bin

这些是在模块时代之前存在的问题。

  • 所有 Go 项目在 $GOPATH/src 目录中。

  • 没有本地依赖管理支持。

  • 所有依赖项将下载到 $GOPATH/src 目录中而不进行版本控制。

让我们逐一看一下每个问题。

  • 任何 GO 项目都必须位于 $GOPATH/src 目录内。

这是一个很大的限制,因为它限制了你可以放置项目的位置。

  • 没有本地依赖管理支持。

此外,模块之前还有一个问题是没有办法在项目中指定依赖项。虽然有像 dep 和 glide 这样的替代解决方案,但缺乏本地解决方案。

  • 所有依赖项将下载到 $GOPATH/src 目录中而不进行版本控制。

当我们执行 go get 时,它将在 $GOPATH/src 目录中下载所需的包。运行下面的 go get 命令。

go get github.com/pborman/uuid

它将下载位于该位置的包。

$GOPATH/src/github.com/pborman/uuid

请注意上面的 go get 命令没有指定版本,因此它下载了最新版本。同时注意下载的包,甚至没有列出任何版本信息。这是一个问题。如果 github.com/pborman/uuid 包有更新,而你想获取该更新,由于没有版本控制,更新的包将下载到同一位置,替换旧版本。

在 Go 版本 1.11 中

在 Go 1.11 中,引入了模块但尚未最终确定。因此,如果你仍在使用它,最好切换到最新版本。

在 Go 版本 1.13 之后

我们已经讨论了在模块之前存在的所有问题。现在让我们看看这些问题是如何通过引入模块得到解决的。

第一个问题是。

  • 所有 Go 项目在 $GOPATH/src 目录中。

有了模块,这不再是一个要求。

  • 没有本地依赖管理支持。

模块在 Go 中引入了本地依赖管理。通过模块,它提供了两个新文件。

  1. go.mod

  2. go.sum

通过 go.mod 和 go.sum 文件,我们能够安装精确版本的依赖项而不破坏任何内容。我们在本教程开头已经简要介绍了这些文件。稍后在教程中,我们将详细查看它们。

  • 所有依赖项将以版本控制的形式下载到$GOPATH/pkg/mod目录中。

因此,如果你下载同一库的不同版本,那么两者将被下载到GOPATH/pkg/modGOPATH/pkg/mod中将有两个内容。

  • 缓存 - 这是所有依赖项将下载并包含压缩代码的文件夹。

  • 所有下载的依赖项的压缩代码将从缓存目录复制过来。

还有一个新环境变量被引入,名为GO111MODULE

当 GO111MODULE=off 时,go get 将以旧的方式运行,将依赖项下载到$GOPATH/src 文件夹中。

当 GO111MODULE=on 时,go get 将以新方式运行,所有模块将以版本控制的形式下载到$GOPATH/pkg/mod/cache 文件夹中。

当 GO111MODULE=auto 时。

  • 当在$GOPATH/src 文件夹外运行 go get 时,它将表现得像是 GO111MODULE=on。

  • 当在$GOPATH/src 文件夹内运行 go get 时,它将表现得像是 GO111MODULE=off。

现在让我们创建一个模块。我们讨论的内容在那时会更加清晰。

创建模块

可以使用以下命令创建模块。

go mod init {module_import_path}

让我们再看看之前讨论过的go.modgo.sum文件。

go.mod

这是模块依赖文件。它将包含三项内容。

  • 模块名称在顶部。

  • 创建模块时所用的 go 版本。

  • 模块的直接依赖项。

go.sum

该文件列出了所需的直接和间接依赖项的校验和及其版本。需要说明的是,go.mod 文件足以进行成功构建。go.sum 文件中的校验和用于验证每个直接和间接依赖项的校验和。

现在问题是import_path是什么。import_path是任何其他模块用于导入你的模块的前缀路径。

转到$GOPATH/src 文件夹外的任何目录。假设目录名称为learn

mkdir learn
cd learn

假设模块名称也是learn

go mod init learn

此命令将在同一目录中创建一个go.mod文件。那什么是 go.mod 文件呢?

让我们检查一下这个文件的内容。

做一个cat go.mod

module learn

go 1.14

当使用 init 命令首次创建模块时,go.mod 文件只会包含两个内容。

  • 模块名称在顶部。
module learn

创建模块时所用的 go 版本。

go 1.14

由于这是一个空模块,因此尚未指定任何直接依赖项。让我们在同一目录中创建一个名为uuid.go的文件,内容如下。

uuid.go

package main

import (
	"fmt"
	"strings"

	"github.com/pborman/uuid"
)

func main() {
	uuidWithHyphen := uuid.NewRandom()
	uuid := strings.Replace(uuidWithHyphen.String(), "-", "", -1)
	fmt.Println(uuid)
}

请注意,我们在 uuid.go 中也导入了依赖项。

"github.com/pborman/uuid"

让我们运行下面的命令。

go mod tidy

此命令将下载源文件中所需的所有依赖项,并用该依赖项更新 go.mod 文件。运行此命令后,让我们再次检查 go.mod 文件的内容。

执行 cat go.mod

module learn

go 1.14

require github.com/pborman/uuid v1.2.1

它列出了在 uuid 文件中指定的直接依赖项及其确切版本。现在让我们也检查一下 go.sum 文件。

执行 cat go.sum

github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=

go.sum 文件列出了模块所需的直接和间接依赖项的校验和。github.com/google/uuid 是由 github.com/pborman/uuid 内部使用的。它是模块的间接依赖项,因此记录在 go.sum 文件中。

我们现在也可以运行这个文件,它将产生正确的输出。

go run uuid.go

输出

e594dc4d9a754bcb83b56e89b18b4b46

上述方法中,我们在源文件中添加了一个依赖项,并使用 go mod tidy 命令下载该依赖项并将其添加到 go.mod 文件中。

模块的类型

我们了解到模块是一个包含嵌套 Go 包的目录。因此,模块本质上可以被视为一个只包含嵌套包的包。我们在包教程中看到,包可以是可执行包或实用包(非可执行)。与包类似,模块也可以有两种类型。

  • 可执行模块 – 我们已经知道 main 是 GoLang 中的可执行包。因此,包含主包的模块就是可执行模块。主包将包含一个主函数,表示程序的开始。安装包含 main 包的模块时,它将在 $GOBIN 目录中创建可执行文件。

  • 非可执行模块或实用模块 – 除了 main 包以外的任何包都是非可执行包。它不是自我可执行的。它仅包含可以被可执行包使用的实用函数和其他实用功能。因此,如果模块不包含 main 包,它将是一个非可执行或实用模块。该模块旨在作为实用工具使用,将被其他模块导入。

要创建一个模块的可执行文件(仅适用于包含主包的模块)。

  • 进行一次 go build,它将在当前目录创建可执行文件。

  • 执行 go install,它将在 $GOBIN 目录中创建可执行文件。

将依赖项添加到你的项目中

让我们探索一些将依赖项添加到项目中的方法。

  • 直接添加到 go.mod 文件中。

  • 执行 go get

  • 将依赖项添加到你的源代码中,并执行 go mod tidy

在查看每种方法之前,我们先创建一个模块。

go mod init learn

直接添加到 go.mod 文件中

我们也可以直接将依赖项添加到 go.mod 文件中。让我们来做这个。

将以下依赖项添加到 go.mod 文件中。

require github.com/pborman/uuid v1.2.1

添加此依赖项后,go.mod 文件将如下所示。

module learn

go 1.14

require github.com/pborman/uuid v1.2.1

现在我们还需要下载新添加的依赖项。为此,我们可以使用下面的命令。

go mod download

此命令将下载github.com/pborman/uuid模块及其所有依赖项。它还将更新go.sum文件,包含所有直接和间接依赖项的校验和和版本。go build以及go install也将下载依赖项并构建二进制文件。go run也会下载并运行二进制文件。go mod download命令在您希望预先下载依赖项而不构建或运行时使用。

执行go get

只需执行go get也会将依赖项添加到 go.mod 文件中。从 go.mod 文件中删除我们之前添加的 uuid 依赖项,并清理 go.sum 文件。现在运行以下命令。

export GO111MODULE=on
go get github.com/pborman/uuid

现在检查 go.mod 文件的内容。

执行cat go.mod

module learn

go 1.14

require github.com/pborman/uuid v1.2.1 //indirect

该依赖项将被标记为//indirect,因为它未在任何源文件中使用。一旦您在源文件中使用此依赖项并执行go build//indirect将被 go 自动移除。此外,它还将更新go.sum文件,包含所有直接和间接依赖项的校验和和版本。

将依赖项添加到您的源代码并执行go mod tidy

我们在上面的示例中已经看到过这种方法。基本上,go mod tidy命令确保您的 go.mod 文件反映了您在项目中实际使用的依赖项。当我们运行go mod tidy命令时,它将执行两件事情。

  • 添加在源文件中导入的任何依赖项。

  • 删除在go.mod文件中提到但未在任何源文件中导入的依赖项。

添加供应商目录

如果您想要管理您的依赖项,则可以使用以下命令来实现相同的目的。

go mod vendor

这将在您的项目目录中创建一个供应商目录。您还可以将供应商目录检查到您的版本控制系统(VCS)中。这在某种意义上非常有用,因为不需要在运行时下载任何依赖项,因为它已经存在于检查到 VCS 中的供应商文件夹中。

模块导入路径

我们已经看到,模块导入路径是用于导入该模块内所有包的前缀路径。

有三种情况决定可以与模块使用的导入路径名称。

  • 该模块是一个实用模块,您计划发布您的模块。

  • 该模块是一个实用模块,您不打算发布您的模块。

  • 该模块是一个可执行模块。

该模块是一个实用模块,您计划发布您的模块。

如果您计划发布您的模块,则模块名称应与托管该模块的仓库的 URL 匹配。Go 尝试使用相同的模块导入路径从 VCS 下载依赖项。

该模块是一个实用模块,您不打算发布您的模块。

这是您只打算在本地使用实用模块的情况。在这种情况下,导入路径可以是任何内容。

该模块是一个可执行模块。

在这种情况下,模块导入路径可以是任何内容。即使你打算将模块提交到 VCS,模块导入路径也可以是非 URL,因为其他模块不会使用它。

不过,创建模块时使用有意义的导入路径是个好习惯。

在同一模块内导入包

在同一模块内,任何包都可以通过模块的导入路径加上包含该包的目录来导入。为了说明,让我们创建一个模块。

  • 创建一个learn目录。

  • 创建一个导入路径为“learn”的模块。

go mod init learn
  • 现在创建main.go(包含主包和主函数)。

  • 和 math/math.go – math 包。

main.go

package main

import (
	"fmt"
	"learn/math"
)

func main() {
	fmt.Println(math.Add(1, 2))
}

math/math.go

package math

func Add(a, b int) int {
    return a + b
}

查看我们如何在main.go文件中导入 math 包。

"learn/math"

这里的导入路径是模块的导入路径,即learn加上包含该包的目录math。因此是“learn/math”。嵌套目录中的包也可以以同样的方式导入。工作原理是,前缀是模块导入路径,因此 go 会知道你试图从同一模块导入。所以它将直接引用,而不是下载。

从不同模块本地导入包

有时我们想要导入一个本地存在的模块。让我们了解如何导入这样的模块。但首先,我们必须创建一个可以被其他人使用的模块,然后将其导入到另一个模块中。为此,让我们创建两个模块。

  • sample.com/math模块

  • school模块

school模块将调用sample.com/math模块的代码。

让我们先创建sample.com/math模块,该模块将被school模块使用。

  • 创建一个math目录。

  • 创建一个导入路径为sample.com/math的模块。

go mod init sample.com/math
  • math目录中创建一个名为math.go的文件,内容如下。
package math

func Add(a, b int) int {
	return a + b
}

现在让我们创建 school 模块。

  • 现在在与math目录并排的相同路径下创建一个school目录。

  • 创建一个名为school的模块。

go mod init school
  • 现在让我们修改go.mod文件,以在 school 模块中导入 math 模块。要导入一个未推送到 VCS 的本地模块,我们将使用替换目录。替换目录将用你指定的路径替换模块路径。
module school

go 1.14

replace sample.com/math => ../math
  • 创建文件school.go,该文件将使用sample.com/math模块中的 Add 函数。
package main

import (
	"fmt"
	"sample.com/math"
)

func main() {
	fmt.Println(math.Add(2, 4))
}

现在运行go run

go run school.go

它能够调用sample.com/math模块的 Add 函数,并正确输出 6。

它还将使用sample.com/math模块的版本信息更新 go.mod。

module school

go 1.14

replace sample.com/math => ../math

require sample.com/math v0.0.0-00010101000000-000000000000

选择库的版本

要了解 GO 在选择go.mod文件中指定的两个版本的库时的方式,我们首先需要理解语义版本控制。语义版本控制由用点分隔的三个部分组成。以下是版本控制的格式。

v{major_version}.{minor_version}.{patch_version}

其中

  • v – 只是指示它是一个版本。

  • major_version – 它表示库中的不兼容 API 更改。因此,当库中发生不向后兼容的更改时,major_version 将会递增。

  • minor_version – 它表示库在向后兼容的方式下功能的变化。因此,当库中有一些功能变化,但这些变化是向后兼容的,那么在这种情况下,次要版本将递增。

  • patch_version – 它表示库中的错误修复以向后兼容的方式进行。因此,当现有库功能存在错误修复时,在这种情况下,patch_version 将递增。

现在可能有两种情况

  • 使用的两个相同库的版本只在次要和补丁版本上不同。它们的主要版本是相同的。

  • 使用了两个主要版本不同的相同库。

让我们看看 Go 在上述两种情况下遵循什么方法。

次要或补丁版本不同

Go 在选择go.mod文件中指定的两个仅在次要或补丁版本上不同的库版本时,遵循最低版本策略方法。

例如,在使用相同库的两个版本时,可能是

1.2.0

1.3.0

然后 Go 将选择 1.3.0,因为它是最新版本。

主要版本不同

Go 将主要版本视为一个不同的模块。那么,这意味着什么?这基本上意味着导入路径将以主要版本作为后缀。让我们以 VCS 为github.com/sample的任意 Go 库为例。让我们最新的语义版本是

v8.2.3

然后go.mod文件将如下所示

module github.com/sample/v8

go 1.13

..

它在导入路径中具有主要版本。因此,任何使用此示例库的库都必须像这样导入它。

import "github.com/sample/v8"

如果将来发布v9版本,则必须以如下方式在应用程序中导入它。

import "github.com/sample/v9"

此外,go-redis 库将更改其go.mod文件以反映 v9 主要版本。

module github.com/samples/v9

它基本上允许在同一 Go 应用程序中使用相同库的不同主要版本。当在同一应用程序中导入同一库的不同主要版本时,我们还可以给出有意义的名称。例如

import redis_v8 "github.com/sample/v8"
import redis_v9 "github.com/sample/v9"

这也被称为语义导入版本控制。另外请注意

  • 对于第一个版本,在go.mod文件中不指定版本是可以的。

  • 此外,在导入同一库的不同主要版本时要小心。注意新版本中可能提供的新功能。

也因为同样的原因,当你使用更新特定模块时

go get -u

然后它只会升级到适用的最新次要版本或补丁版本。例如,假设当前应用程序使用的版本是

v1.1.3

此外,假设我们有以下可用版本

v1.2.0
v2.1.0

然后当我们运行

go get

然后它会更新到

v1.2.0

原因是因为go get只会更新次要版本或补丁版本,而不会更新主要版本,因为 Go 将模块的主要版本视为完全不同的模块。

要升级主要版本,请在go.mod文件中显式指定升级的依赖,或者执行该版本的 go get。

还有一些关于升级模块的注意事项。

  • 要将依赖升级到其最新的补丁版本,请使用以下命令。
go get -u=patch <dependency_name></dependency_name>
  • 要将依赖升级到特定版本,请使用以下命令。
go get dependency@version
  • 要将依赖升级到特定提交,请使用以下命令。
go get <dependency_name>@commit_number</dependency_name>
  • 要将所有依赖升级到其最新的小版本和补丁版本,请使用以下命令。
go get ./...

go mod 命令

下面是 go mod 命令的一些选项。

  • download – 它将下载所需的依赖到$GOPATH/pkg/mod/cache 文件夹。此外,它将更新 go.sum 文件,包含所有直接和间接依赖的校验和及版本。

  • edit – 该命令用于编辑 go.mod 文件。它提供了一组编辑标志。运行以下命令查看所有可用的编辑标志。

go help mod edit

例如,下面是一些可用的编辑标志。

  1. -fmt标志将格式化 go.mod 文件,但不会做其他更改。

  2. -module标志可用于设置模块的导入路径。

  • graph – 该命令可用于打印模块需求依赖图。

  • init – 我们已经看到过该命令的使用。它用于初始化一个新模块。

  • tidy – 此命令将下载源文件中所需的所有依赖。

  • vendor – 如果您想对依赖进行供应管理,则可以使用以下命令来实现。它将在项目目录内创建一个 vendor 目录。您还可以将 vendor 目录中的内容检查到您的版本控制系统(VCS)中。

  • verify – 此命令检查当前下载的依赖是否被修改。如果任何已下载的依赖被验证,程序将以非零代码退出。

  • why – 此命令分析主模块的包图。它打印从主模块到给定包的最短路径。例如,在“从不同模块本地导入包”部分创建的学校模块,如果我们像下面这样打印 why 命令。

go mod why sample.com/math

然后,下面将是输出。

# sample.com/math
school
sample.com/math

输出显示sample.com/math/math 包在图中距离主模块(这里是学校)为一。

go.mod 文件中的直接与间接依赖

直接依赖是模块直接导入的依赖。间接依赖是由模块的直接依赖导入的依赖。此外,任何在go.mod文件中提到但未在模块的任何源文件中导入的依赖也视为间接依赖。

go.mod文件仅记录直接依赖。然而,在以下情况下,它可能会记录间接依赖。

  • 任何不在直接依赖的 go.mod 文件中列出的间接依赖,或者如果直接依赖没有 go.mod 文件,那么该直接依赖将被添加到 go.mod 文件中,后缀为//direct。

  • 任何未在模块的任何源文件中导入的依赖项(我们在教程中已经看到过这个例子)。

go.sum将记录直接和间接依赖项的校验和。

go.mod 文件中的间接依赖项示例

让我们通过一个例子来理解这一点。为此,让我们首先创建一个模块。

git mod init learn

让我们在 go.mod 文件中添加 colly 库版本 v1.2.0 作为依赖项。colly 版本 v1.2.0 没有 go.mod 文件。

module learn

go 1.14

require github.com/gocolly/colly v1.2.0

现在创建一个文件 learn.go。

package main

import (
	"github.com/gocolly/colly"
)

func main() {
	_ = colly.NewCollector()
}

现在进行 go build。由于 colly 版本 v1.2.0 没有 go.mod 文件,colly 所需的所有依赖项将以//indirect 为后缀添加到 go.mod 文件中。

进行 go build。现在检查 go.mod 文件。你会看到文件的以下内容。

module learn

go 1.14

require (
	github.com/PuerkitoBio/goquery v1.6.0 // indirect
	github.com/antchfx/htmlquery v1.2.3 // indirect
	github.com/antchfx/xmlquery v1.3.3 // indirect
	github.com/gobwas/glob v0.2.3 // indirect
	github.com/gocolly/colly v1.2.0
	github.com/kennygrant/sanitize v1.2.4 // indirect
	github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
	github.com/temoto/robotstxt v1.1.1 // indirect
	golang.org/x/net v0.0.0-20201027133719-8eef5233e2a1 // indirect
	google.golang.org/appengine v1.6.7 // indirect
)

所有其他依赖项后缀为//indirect。还要检查所有直接和间接依赖项是否会记录在 go.sum 文件中。

结论

这就是关于 golang 中的模块的全部内容。希望你喜欢这篇文章。请在评论中分享反馈。

posted @   绝不原创的飞龙  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示