透過示例學習-Go-語言-2023-二十二-

绝不原创的飞龙發表於2024-10-19

透過示例學習 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* 中介*

在 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
  • golang* memento* pattern*

在 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 *

在 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/mod**中的不同目錄,而不會相互覆蓋。**\)GOPATH/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 中的模組的全部內容。希望你喜歡這篇文章。請在評論中分享反饋。

相關文章