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

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

在 Go (Golang)中删除/移除一个文件夹

来源:golangbyexample.com/delete-folder-go/

os.Remove()函数可以用于删除 Golang 中的文件夹。以下是该函数的签名。

func Remove(name string) error

代码:

package main

import (
    "log"
    "os"
)

func main() {
    err := os.Remove("sample")
    if err != nil {
        log.Fatal(err)
    }
}

输出:

Deletes sample folder from the current working directory

在 Golang 中检测链表的循环起始节点。

来源:golangbyexample.com/cycle-start-node-linked-list-go/

目录

  • 概述

  • 程序

概述

目标是找出给定链表中的循环起始节点。如果链表中的最后一个节点指向前面的某个节点,则链表中存在循环。

示例

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

上述链表存在循环。循环起始节点是节点 2。下面是我们可以遵循的方法。

  • 首先,检测给定的链表是否有循环。设置两个指针,一个是慢指针,另一个是快指针。两者最初都指向头节点。

  • 现在将慢指针移动 1 个节点,快指针移动 2 个节点。

slow := slow.Next
fast := fast.Next.Next
  • 如果在任何时刻慢指针和快指针相同,则链表存在循环。

  • 快指针和慢指针只能在循环内的节点相遇。假设它们在节点 3 相遇。现在计算循环的长度。长度为 3。

  • 现在在节点头部保持一个指针,另一个指针与它保持一个循环长度的距离。因此,一个指针将指向节点 1,另一个指针将指向节点 4。

  • 移动两个指针,直到它们相同。它们将在循环起始节点相遇,即节点 2。

程序

这是相应的程序。

package main

import "fmt"

func main() {
	first := initList()
	ele4 := first.AddFront(4)
	first.AddFront(3)
	ele2 := first.AddFront(2)
	first.AddFront(1)

	//Create cycle
	ele4.Next = ele2

	output := cycleStartNode(first.Head)
	fmt.Println(output.Val)

}

type ListNode struct {
	Val  int
	Next *ListNode
}

type SingleList struct {
	Len  int
	Head *ListNode
}

func (s *SingleList) AddFront(num int) *ListNode {
	ele := &ListNode{
		Val: num,
	}
	if s.Head == nil {
		s.Head = ele
	} else {
		ele.Next = s.Head
		s.Head = ele
	}
	s.Len++
	return ele
}

func initList() *SingleList {
	return &SingleList{}
}
func cycleStartNode(head *ListNode) *ListNode {
	if head == nil || head.Next == nil {
		return nil
	}

	slow := head
	fast := head

	cycleExists := false

	for slow != nil && fast != nil && fast.Next != nil {
		slow = slow.Next
		fast = fast.Next.Next

		if slow == fast {
			cycleExists = true
			break
		}
	}

	if !cycleExists {
		return nil
	}

	cycleNode := slow

	curr := cycleNode

	lengthCycle := 1

	for curr.Next != cycleNode {
		lengthCycle++
		curr = curr.Next
	}

	curr = head

	for i := 0; i < lengthCycle; i++ {
		curr = curr.Next
	}

	for head != curr {
		head = head.Next
		curr = curr.Next
	}

	return head
}

输出

2

注意: 查看我们的 Golang 高级教程。本系列教程内容详尽,我们尝试涵盖所有概念及示例。本教程适合希望获得专业知识和深入理解 Golang 的读者 - Golang 高级教程

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

在 Go (Golang) 中检测操作系统

来源:golangbyexample.com/detect-os-golang/

runtime.GOOS 常量可以用于在运行时检测操作系统,因为这个常量仅在运行时设置。

runtime.GOARCH 可以用来知道正在运行的程序的架构目标。

要查看所有可能的 GOOS 和 GOARCH 的组合,请运行以下命令

go tool dist list

以下是获取当前运行操作系统的代码:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    os := runtime.GOOS
    switch os {
    case "windows":
        fmt.Println("Windows")
    case "darwin":
        fmt.Println("MAC operating system")
    case "linux":
        fmt.Println("Linux")
    default:
        fmt.Printf("%s.\n", os)
    }
}

输出:

Your current operating system

确定矩阵是否可以通过旋转在 Go (Golang)中获得

来源:golangbyexample.com/matrix-rotation-target-golang/

目录

  • 概述

  • 程序

概述

给定两个 n*n 矩阵,目标。我们需要确定矩阵是否可以通过任意次数的 90 度旋转转换为目标矩阵

示例 1

Input: source = [2,1],[1,2]], target = [[1,2],[2,1]]
Output: true

源矩阵可以旋转 90 度一次以获得目标矩阵

示例 2

Input:  source = [[1,2],[2,2]], target = [[2,1],[1,2]]
Output: false

即使我们将源矩阵旋转 3 次 90 度,也无法获得目标矩阵。

程序

下面是相同的程序

package main

import "fmt"

func findRotation(mat [][]int, target [][]int) bool {
	n, tmp := len(mat), make([]bool, 4)
	for i := range tmp {
		tmp[i] = true
	}
	for i := 0; i < n; i++ {
		for j := 0; j < n; j++ {
			if mat[i][j] != target[i][j] {
				tmp[0] = false
			}
			if mat[i][j] != target[j][n-i-1] {
				tmp[1] = false
			}
			if mat[i][j] != target[n-i-1][n-j-1] {
				tmp[2] = false
			}
			if mat[i][j] != target[n-j-1][i] {
				tmp[3] = false
			}
		}
	}
	return tmp[0] || tmp[1] || tmp[2] || tmp[3]
}

func main() {
	output := findRotation([][]int{{2, 1}, {1, 2}}, [][]int{{1, 2}, {2, 1}})
	fmt.Println(output)

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

}

输出

true
false

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

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

另外,请查看我们的系统设计教程系列 - 系统设计教程系列

在 Go (Golang) 中引爆最大炸弹的程序

来源:golangbyexample.com/detonate-maximum-bombs-golang/

目录

  • 概述

  • 程序

概述

给定一个二维数组,其中每个数组的条目有三个值

  • i - 表示炸弹的 x 坐标

  • j - 表示炸弹的 y 坐标

  • r - 表示炸弹范围的半径

一个炸弹爆炸时会导致其范围内所有炸弹的爆炸。当这些炸弹被引爆时,它们又会导致其范围内所有炸弹的爆炸。

你只能引爆一个炸弹。目标是找到可以引爆的最大炸弹数量

示例 1

Input: [[1,0,3],[5,0,4]]
Output: 2

第一个炸弹位于第二个炸弹的范围内。因此,当我们引爆第二个炸弹时,第二个和第一个炸弹都会被引爆。

示例 2

Input:  [[2,2,2],[10,10,5]]
Output: 1

两个炸弹彼此不在范围内。

解决这个问题的思路是将一切视为有向图,如果第二个炸弹位于第一个炸弹的范围内,则从第一个炸弹到第二个炸弹存在一个有向节点。

一旦构建了这个图,我们可以从每个节点进行深度优先搜索(DFS),以获取它可以引爆的最大炸弹数量。我们还会存储先前计算的结果。

程序

下面是相应的程序

package main

import (
	"fmt"
	"math"
)

func maximumDetonation(bombs [][]int) int {

	if len(bombs) == 0 {
		return 0
	}
	max := 1
	detonationMap := make(map[int][]int)

	for i := 0; i < len(bombs); i++ {
		for j := 0; j < len(bombs); j++ {
			if i != j {
				if float64(bombs[i][2]) >= distance(bombs[i], bombs[j]) {
					if arr, ok := detonationMap[i]; ok {
						arr = append(arr, j)
						detonationMap[i] = arr
					} else {
						var arr []int
						arr = append(arr, j)
						detonationMap[i] = arr
					}
				}
			}
		}
	}

	for key := range detonationMap {
		detonated := 1
		queue := []int{key}
		visited := make(map[int]bool)
		visited[key] = true

		for len(queue) > 0 {
			cur := queue[0]
			queue = queue[1:]

			for _, val := range detonationMap[cur] {
				if !visited[val] {
					detonated++
					visited[val] = true
					queue = append(queue, val)
				}
			}
		}
		if detonated == len(bombs) {
			return len(bombs)
		}

		if detonated > max {
			max = detonated
		}
	}
	return max
}

func distance(a []int, b []int) float64 {
	ret := math.Sqrt(math.Pow(float64(a[0]-b[0]), 2) + math.Pow(float64(a[1]-b[1]), 2))
	return ret
}

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

	output = maximumDetonation([][]int{{2, 2, 2}, {10, 10, 5}})
	fmt.Println(output)

}

输出:

2
1

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

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

另外,请查看我们的系统设计教程系列 – 系统设计教程系列

GRPC 与 REST 的区别

来源:golangbyexample.com/grpc-vs-rest/

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

目录

  • 概述

  • 链接

    • GRPC

    • REST

  • 协议

  • 性质

  • 数据传输模式

  • 模型

  • 性能

  • 代码生成

  • 类型安全

  • 设置

  • 何时使用

  • 结论

概述

REST 是一种建立在 HTTP/1 上的架构风格。GRPC 不是一种风格,而是一个建立在 HTTP/2 之上的 RPC 框架,并在后台使用协议缓冲区。因此,GRPC 实际上是一个实现,或者可以说是一个库,而 REST 只是一组规则和原则。

虽然我们在比较一个实现的 GRPC 框架与 REST(架构风格和原则),这听起来可能有些奇怪,但理解高层架构的区别仍然很重要,这一点如果你遵循某种方法而非另一种方法会更加明显。

这里是两者之间的主要区别。此外,值得一提的是,本文假设你已经对 GRPC、HTTP2 和 REST 有一定了解。

链接

GRPC

这里是关于 GRPC 的进一步阅读链接 grpc.io/

REST

这里是关于 REST 的进一步阅读链接 – en.wikipedia.org/wiki/Representational_state_transfer

协议

  • GRPC 是一个 RPC 框架,构建在 HTTP/2 之上。

  • REST 架构风格是在 HTTP/1 之上指定的。HTTP/2 保留了 HTTP/1.1 的所有语义。因此,即使使用 HTTP/2,REST API 仍应继续正常工作。

性质

  • GRPC 的思维方式是面向 API 或面向动作的。

  • REST 是面向资源的。

数据传输模式

  • GRPC 仅支持使用协议缓冲区在服务器和客户端之间传输数据。

  • REST 支持 JSON、XML 和其他数据格式。REST 也可以很容易地与协议缓冲区一起使用。

模型

  • GRPC 提供四种不同的客户端和服务器之间的通信方式。这四种方式是单一请求、服务器流、客户端流和双向流。因此,在 GRPC 中,客户端和服务器可以相互交谈。

    • 单一请求 – 这是最简单的一种。客户端发送请求,服务器发送响应。

    • 客户端流 – 客户端可以发送多个消息的流,而服务器只需对所有客户端请求返回单个响应。

    • 服务器流 – 客户端将只发送一条消息,而服务器可以向其发送消息流。

    • 双向流——客户端和服务器都可以流式传输多个消息。流式传输将并行进行且无顺序。此外,它是非阻塞的。客户端和服务器在发送下一个消息之前无需等待响应。

  • REST 工作基于请求-响应模型。基本上,你发送请求,然后收到响应。因此,REST 仅提供单向通信。在 REST 中,只有客户端与服务器进行交谈。

性能

  • 由于 GRPC 固有地使用 HTTP/2,因此所有应用于 HTTP/2 的性能优化自动适用于 GRPC。HTTP/2 引入了多项相较于 HTTP/1 的性能优化,例如

    • 双向流

    • 多路复用

    • 头部压缩

    • 等等

此外,GRPC 在内部使用协议缓冲区,由于协议缓冲区是二进制数据且体积较小,它们在网络上快速传输。GRPC 可以有效利用每个 TCP 连接。基于这两个原因,GRPC 非常快。

  • REST 在 HTTP/1 上的速度会比 GRPC 慢。它使用 JSON、XML 来表示相同的数据,所需大小超过协议缓冲区。

代码生成

  • 由于 GRPC 建立在协议缓冲区之上,因此提供了自动代码生成。事实上,使用协议缓冲区时,代码生成是使用 GRPC 的必备条件。

  • REST 也通过 Swagger、OPEN API 提供代码生成,但这只是提供的额外功能,并不如协议缓冲区的代码生成有效。

类型安全

  • 由于在 GRPC 的情况下使用协议缓冲区进行代码生成,因此在某种程度上为 GRPC 提供了类型安全。GRPC 不允许你为期望字符串的字段发送 int。API 契约由 proto 文件定义,并且是严格的。

  • REST 没有任何此类限制。API 契约大多数情况下只是使用 OPEN API 或 Swagger 的文档,因此比较松散。

设置

  • GRPC 需要你在本地设置一个客户端,以能够进行 GRPC 调用。

  • REST 调用不需要客户端设置。你可以通过浏览器、Postman、curl 等进行调用。

何时使用

  • GRPC 主要适用于需要低延迟和高吞吐量的内部微服务。目前不适合将你的服务暴露为 GRPC,因为没有可供外部服务集成的 API。未来当 GRPC 完全发展时,这可能会变得可行。

  • REST 更适合将你的 API 暴露给外部服务。

结论

这些是 GRPC 和 REST 之间的一些主要区别。希望你喜欢这篇文章。请在评论中分享反馈。

注意:如果你有兴趣学习 Golang,我们有一个全面的 Golang 教程系列。请查看——Golang 全面教程系列

GO 中方法和函数的区别

来源:golangbyexample.com/difference-between-method-function-go/

方法和函数之间有一些重要的区别。让我们来看一下两者的签名。

功能:

func some_func_name(arguments) return_values

方法:

func (receiver receiver_type) some_func_name(arguments) return_values

从上面的签名来看,方法有一个接收者参数。接收者可以是一个结构体或任何其他类型。该方法将可以访问接收者的属性,并可以调用接收者的其他方法。

这是函数和方法之间唯一的区别,但正因如此,它们在功能上有所不同。

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

  • 方法可以在接收者上进行链式调用,而函数则不能用于相同的目的。

  • 可以存在具有相同名称但接收者不同的方法,但在同一包中不能存在两个具有相同名称的不同函数。

  • 函数* go* golang* 方法

Go (Golang) 中创建错误的不同方式

来源:golangbyexample.com/different-ways-of-creating-an-error-in-go-golang/

在学习 Go 中创建错误的不同方式之前,首先了解错误。错误是一个具有以下签名的接口类型。

type error interface {  
    Error() string
}

根据 interface 的定义,任何实现 Error() 方法的类型都成为 error 类型。

让我们看看创建 error 的不同方法

1. 使用 errors.New(“some_error_message”)

package main

import (
    "errors"
    "fmt"
)

func main() {
    sampleErr := errors.New("error occured")
    fmt.Println(sampleErr)
}

输出:

error occured

2. 使用 fmt.Errorf(“error is %s”, “some_error_message”)。这种方式可以创建带格式的错误信息

package main

import (
    "fmt"
)

func main() {
    sampleErr := fmt.Errorf("Err is: %s", "database connection issue")
    fmt.Println(sampleErr)
}

输出:

Err is: database connection issue

3. 创建自定义错误

下面的示例说明了自定义错误的使用。在下面的示例中

  • inputError 是一个结构体,具有 Error() 方法,因此它属于 error 类型。

  • 你还可以通过扩展其字段或添加新方法来向自定义错误添加额外信息。inputError 有一个名为 missingFields 的附加字段和一个 getMissingFields 函数。

  • 我们可以使用类型断言将 error 转换为 inputError

示例:

package main

import "fmt"

type inputError struct {
    message      string
    missingField string
}

func (i *inputError) Error() string {
    return i.message
}

func (i *inputError) getMissingField() string {
    return i.missingField
}

func main() {
    err := validate("", "")
    if err != nil {
        if err, ok := err.(*inputError); ok {
            fmt.Println(err)
            fmt.Printf("Missing Field is %s\n", err.getMissingField())
        }
    }
}

func validate(name, gender string) error {
    if name == "" {
        return &inputError{message: "Name is mandatory", missingField: "name"}
    }
    if gender == "" {
        return &inputError{message: "Gender is mandatory", missingField: "gender"}
    }
    return nil
}

输出:

Name is mandatory
Missing Field is name

在 Go(Golang)中迭代映射的不同方式

来源:golangbyexample.com/different-ways-iterating-over-map-go/

范围操作符可用于在 Go 中迭代映射

首先定义一个映射

sample := map[string]string{
        "a": "x",
        "b": "y",
}
  • 迭代所有键和值
for k, v := range sample {
   fmt.Printf("key :%s value: %s\n", k, v)
}

输出:

key :a value: x
key :b value: y
  • 仅迭代键
for k := range sample {
   fmt.Printf("key :%s\n", k)
}

输出:

key :a
key :b
  • 仅迭代值
for _, v := range sample {
   fmt.Printf("value :%s\n", v)
}

输出:

value :x
value :y
  • 获取所有键的列表
keys := getAllKeys(sample)
fmt.Println(keys)

func getAllKeys(sample map[string]string) []string {
    var keys []string
    for k := range sample {
        keys = append(keys, k)
    }
    return keys
}

输出:

[a b]

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

来源:golangbyexample.com/direct-indirect-dependency-module-go/

目录

  • 概述

  • 直接和间接依赖的示例

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

概述

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

  • 模块导入路径。

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

模块的依赖可以分为两种类型

  • 直接依赖 - 直接依赖是模块直接导入的依赖。

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

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

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

  • 任何在模块的源文件中未被导入的依赖。

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

直接和间接依赖的示例

让我们来看一个直接依赖的示例。为此,首先创建一个模块

git mod init learn

现在创建一个文件 learn.go

package main

import (
	"github.com/pborman/uuid"
)

func main() {
	_ = uuid.NewRandom()
}

请注意,我们在 learn.go 中指定的依赖是

"github.com/pborman/uuid"

所以 github.com/pborman/uuid 是 learn 模块的直接依赖,因为它在模块中被直接导入。现在让我们运行以下命令

go mod tidy

此命令将下载你源文件中所需的所有依赖。运行此命令后,我们再检查一下 go.mod 文件的内容

执行 cat go.mod

module learn

go 1.14

require github.com/pborman/uuid v1.2.1

它列出了在 learn.go 文件中指定的直接依赖以及依赖的确切版本。现在我们也来检查 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 mod tidy命令下载该依赖并将其添加到go.mod文件中。

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

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

git mod init learn

现在创建一个文件learn.go

package main

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

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

请注意,我们在learn.go中指定了依赖,如下所示。

github.com/gocolly/colly

因此,github.com/gocolly/colly 是 learn 模块的直接依赖,因为它在模块中直接导入。让我们将 colly 版本 v1.2.0 作为依赖添加到 go.mod 文件中。

module learn

go 1.14

require	github.com/gocolly/colly v1.2.0

现在让我们运行下面的命令。

go mod tidy

在运行此命令后,让我们再次检查go.mod文件的内容。由于 colly 版本 v1.2.0 没有 go.mod 文件,colly 所需的所有依赖项将以//indirect为后缀添加到go.mod文件中。

执行cat 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 文件中。

我们还提到,任何未在任何源文件中导入的依赖项将标记为//indirect。为了说明这一点,删除上面创建的learn.go。同时清理go.mod文件以删除所有 require 行。现在运行下面的命令。

go get github.com/pborman/uuid

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

module learn

go 1.14

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

请注意,该依赖记录为//indirect,因为它未在任何源文件中导入。

字符串或数组在 Go(Golang)中的不同或唯一排列

来源:golangbyexample.com/unique-permutations-golang/

目录

  • 概述

  • 程序

概述

给定一个可以包含重复元素的整数数组,仅查找所有不同的排列。

示例

Input: [2, 2, 1]
Output: [[2 2 1] [2 1 2] [1 2 2]]

Input: [2, 2, 1, 1]
Output: [[2 2 1 1] [2 1 2 1] [2 1 1 2] [2 1 1 2] [2 1 2 1] [1 2 2 1] [1 2 1 2] [1 1 2 2] [1 2 1 2] [1 2 2 1] [1 1 2 2]]

程序

这里是相应的程序。

package main

import "fmt"

func permuteUnique(nums []int) [][]int {
	return permuteUtil(nums, 0, len(nums), len(nums))
}

func shouldSwap(nums []int, start, index int) bool {
	for i := start; i < index; i++ {
		if nums[start] == nums[index] {
			return false
		}

	}
	return true

}
func permuteUtil(nums []int, start, end int, length int) [][]int {
	output := make([][]int, 0)
	if start == end-1 {
		return [][]int{nums}
	} else {
		for i := start; i < end; i++ {
			if shouldSwap(nums, start, i) {
				nums[start], nums[i] = nums[i], nums[start]
				n := make([]int, length)
				for k := 0; k < length; k++ {
					n[k] = nums[k]
				}
				o := permuteUtil(n, start+1, end, length)
				output = append(output, o...)
				nums[i], nums[start] = nums[start], nums[i]
			}

		}
	}
	return output
}

func main() {
	output := permuteUnique([]int{2, 2, 1})
	fmt.Println(output)

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

输出

[[2 2 1] [2 1 2] [1 2 2]]
[[2 2 1 1] [2 1 2 1] [2 1 1 2] [2 1 1 2] [2 1 2 1] [1 2 2 1] [1 2 1 2] [1 1 2 2] [1 2 1 2] [1 2 2 1] [1 1 2 2]]

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

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

在 Go (Golang) 中不使用乘法或除法运算符来除以两个整数。

来源:golangbyexample.com/divide-two-int-golang/

目录

  • 概述

  • 程序

概述

给定两个数字。目标是将这两个数字相除并返回商。在解决方案中忽略余数。但需要在不使用乘法或除法运算符的情况下进行除法。

  • 第一个数字是被除数。

  • 第二个数字是除数。

例如

Input: 15,2
Ouput: 7

Input: -15,2
Ouput: -7

Input: 15,-2
Ouput: -7

Input: -15,-2
Ouput: 7

这里是如何实现它的思路。首先要注意的是

  • 如果被除数和除数都是正数或都是负数,则商为正。

  • 如果被除数和除数其中一个为负,则商为负。

所以被除数和除数的符号之间存在异或关系。我们可以遵循以下步骤来编写程序。

  • 首先,根据上述异或逻辑确定商的符号。

  • 然后将被除数和除数都变为正数。

  • 现在,持续增加除数,直到它小于或等于被除数。同时,保持每次递增的计数器。

  • 计数器*符号将是答案。

程序

这里是相同程序的代码。

package main

import (
	"fmt"
	"math"
)

func divide(dividend int, divisor int) int {

	sign := 1
	if dividend < 0 || divisor < 0 {
		sign = -1
	}

	if dividend < 0 && divisor < 0 {
		sign = 1
	}

	if dividend < 0 {
		dividend = -1 * dividend
	}

	if divisor < 0 {
		divisor = -1 * divisor
	}

	start := divisor

	i := 0

	for start <= dividend {
		start = start + divisor
		i++
	}

	output := i * sign

	return output
}

func main() {
	output := divide(15, 2)
	fmt.Println(output)

	output = divide(-15, 2)
	fmt.Println(output)

	output = divide(15, -2)
	fmt.Println(output)

	output = divide(-15, -2)
	fmt.Println(output)
}

输出

7
-7
-7
7

注意: 查看我们的 Golang 高级教程。本系列的教程详尽,我们试图用例子覆盖所有概念。本教程适合那些希望获得高水平理解和扎实知识的学习者 - Golang 高级教程

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

Go (Golang) 中的双引号、单引号和反引号

来源:golangbyexample.com/double-single-back-quotes-go/

目录

** 双引号

  • 反引号

  • 单引号

  • 示例:

双引号

用于定义字符串。用双引号定义的字符串会遵循转义字符。例如,当打印一个包含\n 的字符串时,会打印出换行。同样,\t 会打印制表符。

反引号

也用于定义字符串。用反引号编码的字符串是原始字面字符串,不会遵循任何转义。

单引号

要声明字节rune,我们使用单引号。在声明字节时,我们必须指定类型。如果不指定类型,则默认为rune。单引号只允许一个字符。在单引号中声明一个包含两个字符的字节或 rune 时,编译器会产生如下错误。

invalid character literal (more than one character)

让我们来看一下上述讨论的所有内容的示例。

  • 请注意下面的输出,反引号包围的字符串不遵循\n 或\t

  • 取消注释下面的行以查看我们上面讨论的编译器错误。

r = 'ab'

示例:

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    //String in double quotes
    x := "tit\nfor\ttat"
    fmt.Println("Priting String in Double Quotes:")
    fmt.Printf("x is: %s\n", x)

   //String in back quotes
    y := `tit\nfor\ttat`
    fmt.Println("\nPriting String in Back Quotes:")
    fmt.Printf("y is: %s\n", y)

    //Declaring a byte with single quotes
    var b byte = 'a'
    fmt.Println("\nPriting Byte:")
    //Print Size, Type and Character
    fmt.Printf("Size: %d\nType: %s\nCharacter: %c\n", unsafe.Sizeof(b), reflect.TypeOf(b), b)

    //Declaring a rune with single quotes
    r := '£'
    fmt.Println("\nPriting Rune:")
    //Print Size, Type, CodePoint and Character
    fmt.Printf("Size: %d\nType: %s\nUnicode CodePoint: %U\nCharacter: %c\n", unsafe.Sizeof(r), reflect.TypeOf(r), r, r)
    //Below will raise a compiler error - invalid character literal (more than one character)
    //r = 'ab'
}

输出:

打印双引号中的字符串:

x 是:tit

for tat

打印反引号中的字符串:

y 是:tit\nfor\ttat

打印字节:

大小:1

类型:uint8

字符:a

打印 rune:

大小:4

类型:int32

Unicode 代码点:U+00A3

字符:£

* [golang](https://golangbyexample.com/tag/golang/)* [引号](https://golangbyexample.com/tag/quotes/)* [单引号](https://golangbyexample.com/tag/single/)*

Go (Golang) 中的双向链表

来源:golangbyexample.com/doubly-linked-list-golang/

目录

  • 概述

  • 程序

概述

一个双向链表的节点包含三个字段。

  • 数据字段

  • 一个下一个指针指向列表中的下一个节点。

  • 一个前一个指针指向列表中的上一个节点。

这里“数据”“下一个”字段与单向链表相同。“前一个”指针字段是新的,这使得链表成为双向链表。

参考这篇文章了解单向链表。

golangbyexample.com/singly-linked-list-in-golang/

以下是一个双向链表的示例。头节点的前指针指向空。类似地,最后一个节点的下一个指针指向空。

![双向链表](https://gitee.com/OpenDocCN/geekdoc-golang-zh/raw/master/docs/go-exam-2023/img/e89fb825a0cb856cfc39da333a6770e2.png)

程序

在 Go 语言中实现双向链表,创建一个包含数据指针和下一个指针的节点结构,添加节点的方法(从前端和后端),以及向前和向后遍历的方法。

package main

import "fmt"

type node struct {
	data string
	prev *node
	next *node
}

type doublyLinkedList struct {
	len  int
	tail *node
	head *node
}

func initDoublyList() *doublyLinkedList {
	return &doublyLinkedList{}
}

func (d *doublyLinkedList) AddFrontNodeDLL(data string) {
	newNode := &node{
		data: data,
	}
	if d.head == nil {
		d.head = newNode
		d.tail = newNode
	} else {
		newNode.next = d.head
		d.head.prev = newNode
		d.head = newNode
	}
	d.len++
	return
}

func (d *doublyLinkedList) AddEndNodeDLL(data string) {
	newNode := &node{
		data: data,
	}
	if d.head == nil {
		d.head = newNode
		d.tail = newNode
	} else {
		currentNode := d.head
		for currentNode.next != nil {
			currentNode = currentNode.next
		}
		newNode.prev = currentNode
		currentNode.next = newNode
		d.tail = newNode
	}
	d.len++
	return
}
func (d *doublyLinkedList) TraverseForward() error {
	if d.head == nil {
		return fmt.Errorf("TraverseError: List is empty")
	}
	temp := d.head
	for temp != nil {
		fmt.Printf("value = %v, prev = %v, next = %v\n", temp.data, temp.prev, temp.next)
		temp = temp.next
	}
	fmt.Println()
	return nil
}

func (d *doublyLinkedList) TraverseReverse() error {
	if d.head == nil {
		return fmt.Errorf("TraverseError: List is empty")
	}
	temp := d.tail
	for temp != nil {
		fmt.Printf("value = %v, prev = %v, next = %v\n", temp.data, temp.prev, temp.next)
		temp = temp.prev
	}
	fmt.Println()
	return nil
}

func (d *doublyLinkedList) Size() int {
	return d.len
}
func main() {
	doublyList := initDoublyList()
	fmt.Printf("Add Front Node: C\n")
	doublyList.AddFrontNodeDLL("C")
	fmt.Printf("Add Front Node: B\n")
	doublyList.AddFrontNodeDLL("B")
	fmt.Printf("Add Front Node: A\n")
	doublyList.AddFrontNodeDLL("A")
	fmt.Printf("Add End Node: D\n")
	doublyList.AddEndNodeDLL("D")
	fmt.Printf("Add End Node: E\n")
	doublyList.AddEndNodeDLL("E")

	fmt.Printf("Size of doubly linked ist: %d\n", doublyList.Size())

	err := doublyList.TraverseForward()
	if err != nil {
		fmt.Println(err.Error())
	}

	err = doublyList.TraverseReverse()
	if err != nil {
		fmt.Println(err.Error())
	}
}

输出:

Add Front Node: C
Add Front Node: B
Add Front Node: A
Add End Node: D
Add End Node: E
Size of doubly linked ist: 5
value = A, prev = <nil>, next = &{B 0xc000070060 0xc000070020}
value = B, prev = &{A <nil> 0xc000070040}, next = &{C 0xc000070040 0xc000070080}
value = C, prev = &{B 0xc000070060 0xc000070020}, next = &{D 0xc000070020 0xc0000700a0}
value = D, prev = &{C 0xc000070040 0xc000070080}, next = &{E 0xc000070080 <nil>}
value = E, prev = &{D 0xc000070020 0xc0000700a0}, next = <nil>

value = E, prev = &{D 0xc000070020 0xc0000700a0}, next = <nil>
value = D, prev = &{C 0xc000070040 0xc000070080}, next = &{E 0xc000070080 <nil>}
value = C, prev = &{B 0xc000070060 0xc000070020}, next = &{D 0xc000070020 0xc0000700a0}
value = B, prev = &{A <nil> 0xc000070040}, next = &{C 0xc000070040 0xc000070080}
value = A, prev = <nil>, next = &{B 0xc000070060 0xc000070020}</nil></nil></nil></nil></nil></nil></nil></nil>
```*


<!--yml

分类:未分类

日期:2024-10-13 06:09:16

-->

# 在 Go (Golang) 中从 URL 下载图像或文件。

> 来源:[`golangbyexample.com/download-image-file-url-golang/`](https://golangbyexample.com/download-image-file-url-golang/)

目录

+   概述

+   代码

# 概述

以下是从 URL 下载图像或文件的步骤。

+   使用 **http.Get(URL)** 函数从 URL 获取文件的字节。

+   使用 **os.Create()** 函数创建一个空文件。

+   使用 **io.Copy()** 函数将下载的字节复制到第 2 步中创建的文件中。

# 代码

```go
package main

import (
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

func main() {
	fileName := "sample.pdf"
	URL := "http://www.africau.edu/images/default/sample.pdf"
	err := downloadFile(URL, fileName)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("File %s downlaod in current working directory", fileName)
}

func downloadFile(URL, fileName string) error {
	//Get the response bytes from the url
	response, err := http.Get(URL)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	if response.StatusCode != 200 {
		return errors.New("Received non 200 response code")
	}
	//Create a empty file
	file, err := os.Create(fileName)
	if err != nil {
		return err
	}
	defer file.Close()

	//Write the bytes to the fiel
	_, err = io.Copy(file, response.Body)
	if err != nil {
		return err
	}

	return nil
} 

输出:

File sample.pdf downlaod in current working directory

在 Go 语言中计算两个字符串之间的编辑距离(Golang)。

来源:golangbyexample.com/edit-distance-two-strings-golang/

目录

  • 概述**

  • 递归解决方案

  • 动态规划解决方案

概述

给定两个字符串,找出将一个字符串转换为另一个字符串所需的最小操作次数。可以通过执行以下三种操作将一个字符串转换为另一个字符串:

  • 插入

  • 移除

  • 替换

所有操作的成本相等。让我们来看一些示例。

示例 1:

First String: abc
Second String: abcd
Output: 1

我们可以在第一个字符串中插入‘d’

示例 2:

First String: abc
Second String: ab
Output: 1

我们可以从第一个字符串中移除‘c’

示例 3:

First String: abc
Second String: abd
Output: 1

我们可以在第一个字符串中将‘c’替换为‘d’

示例 4:

First String: abce
Second String: abd
Output: 2
  • 我们可以从第一个字符串中移除‘e’

  • 我们可以在第一个字符串中将‘c’替换为‘d’

一些基本情况。假设第一个字符串的长度为m,第二个字符串的长度为n

  • 如果第一个字符串和第二个字符串的长度均为零,则输出为 0。

  • 如果只有第一个字符串为空,则输出为第二个字符串的长度。

  • 如果只有第二个字符串为空,则输出为第一个字符串的长度。

否则

如果第一个字符串和第二个字符串的最后一个字符匹配,则输出的是对第一个字符串长度为m-1和第二个字符串长度为n-1的最小编辑操作。这意味着递归计算(m-1, n-1)。如果最后一个字符不匹配,则我们可以在第一个字符串中进行插入、删除或替换操作。

  • 替换 – 递归计算(m-1, n-1)

  • 移除 – 递归计算(m,-1 n)

  • 插入 – 递归计算(m, n-1)

递归解决方案

下面是相同问题的递归解决方案。

package main

import (
	"fmt"
	"math"
)

func main() {
	output := minDistance("abc", "abcd")
	fmt.Println(output)

	output = minDistance("abc", "ab")
	fmt.Println(output)

	output = minDistance("abc", "abd")
	fmt.Println(output)

	output = minDistance("abce", "abd")
	fmt.Println(output)
}

func minDistance(word1 string, word2 string) int {
	word1Rune := []rune(word1)
	word2Rune := []rune(word2)
	lenWord1 := len(word1Rune)
	lenWord2 := len(word2Rune)

	return minDistanceUtil(word1Rune, word2Rune, lenWord1, lenWord2)
}

func minDistanceUtil(word1 []rune, word2 []rune, lenWord1, lenWord2 int) int {
	if lenWord1 == 0 && lenWord2 == 0 {
		return 0
	}

	if lenWord1 == 0 {
		return lenWord2
	}

	if lenWord2 == 0 {
		return lenWord1
	}

	if word1[lenWord1-1] == word2[lenWord2-1] {
		return minDistanceUtil(word1, word2, lenWord1-1, lenWord2-1)
	} else {
		x := minDistanceUtil(word1, word2, lenWord1-1, lenWord2-1)
		y := minDistanceUtil(word1, word2, lenWord1, lenWord2-1)
		z := minDistanceUtil(word1, word2, lenWord1-1, lenWord2)
		return 1 + minOfThree(x, y, z)
	}
}

func minOfThree(x, y, z int) int {
	output := int(math.Min(float64(x), math.Min(float64(y), float64(z))))
	return output
}

输出

1
1
1
2

如果你注意到上面的程序,许多子问题被反复计算,因此上述解决方案的复杂度是指数级的。因此,我们也可以在这里使用动态规划来降低整体时间复杂度。

这是相同的程序。

动态规划解决方案

package main

import (
	"fmt"
	"math"
)

func main() {
	output := minDistance("abc", "abcd")
	fmt.Println(output)

	output = minDistance("abc", "ab")
	fmt.Println(output)

	output = minDistance("abc", "abd")
	fmt.Println(output)

	output = minDistance("abce", "abd")
	fmt.Println(output)
}

func minDistance(word1 string, word2 string) int {
	word1Rune := []rune(word1)
	word2Rune := []rune(word2)
	lenWord1 := len(word1Rune)
	lenWord2 := len(word2Rune)

	editDistanceMatrix := make([][]int, lenWord1+1)

	for i := range editDistanceMatrix {
		editDistanceMatrix[i] = make([]int, lenWord2+1)
	}

	for i := 1; i <= lenWord2; i++ {
		editDistanceMatrix[0][i] = i
	}

	for i := 1; i <= lenWord1; i++ {
		editDistanceMatrix[i][0] = i
	}
	for i := 1; i <= lenWord1; i++ {
		for j := 1; j <= lenWord2; j++ {

			if word1Rune[i-1] == word2Rune[j-1] {
				editDistanceMatrix[i][j] = editDistanceMatrix[i-1][j-1]
			} else {
				editDistanceMatrix[i][j] = 1 + minOfThree(editDistanceMatrix[i-1][j], editDistanceMatrix[i][j-1], editDistanceMatrix[i-1][j-1])
			}
		}
	}
	return editDistanceMatrix[lenWord1][lenWord2]
}

func minOfThree(x, y, z int) int {
	output := int(math.Min(float64(x), math.Min(float64(y), float64(z))))
	return output
}

输出

1
1
1
2

注意: 请查看我们的 Go 语言高级教程。本系列的教程内容详尽,我们试图通过示例覆盖所有概念。本教程适合那些希望获得专业知识和对 Go 语言有深入理解的人 - Go 语言高级教程

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

在 Go (Golang)中嵌入接口。

来源:golangbyexample.com/embedding-interfaces-go/

接口可以嵌入另一个接口,也可以嵌入到结构中。让我们逐一查看。

目录

在另一个接口中嵌入接口

  • 在结构中嵌入接口 * # 在另一个接口中嵌入接口

接口可以嵌入任意数量的接口,也可以嵌入到任意接口中。嵌入接口的所有方法成为嵌入接口的一部分。这是一种通过合并一些小接口来创建新接口的方法。让我们通过一个例子来理解。

假设我们有一个接口动物如下。

type animal interface {
    breathe()
    walk()
}

假设有另一个接口名为人类,它嵌入了动物接口。

type human interface {
    animal
    speak()
}

所以,如果任何类型需要实现人类接口,那么它必须定义。

  • breathe()walk()方法的动物接口被嵌入在人类中。

  • speak()方法的人类接口。

package main

import "fmt"

type animal interface {
	breathe()
	walk()
}

type human interface {
	animal
	speak()
}

type employee struct {
	name string
}

func (e employee) breathe() {
	fmt.Println("Employee breathes")
}

func (e employee) walk() {
	fmt.Println("Employee walk")
}

func (e employee) speak() {
	fmt.Println("Employee speaks")
}

func main() {
	var h human

	h = employee{name: "John"}
	h.breathe()
	h.walk()
	h.speak()
}

输出

Employee breathes
Employee walk
Employee speaks

作为另一个例子,Golang 的io包的 ReaderWriter 接口(golang.org/pkg/io/#ReadWriter)嵌入了另外两个接口。

type ReadWriter interface {
    Reader
    Writer
}

在结构中嵌入接口

接口也可以嵌入到结构中。嵌入接口的所有方法都可以通过该结构调用。如何调用这些方法将取决于嵌入接口是命名字段还是未命名/匿名字段。

  • 如果嵌入的接口是命名字段,则必须通过命名接口名称调用接口方法。

  • 如果嵌入的接口是未命名/匿名字段,则可以直接引用接口方法或通过接口名称引用。

让我们看一个程序来说明上述要点。

package main

import "fmt"

type animal interface {
    breathe()
    walk()
}

type dog struct {
    age int
}

func (d dog) breathe() {
    fmt.Println("Dog breathes")
}

func (d dog) walk() {
    fmt.Println("Dog walk")
}

type pet1 struct {
    a    animal
    name string
}

type pet2 struct {
    animal
    name string
}

func main() {
    d := dog{age: 5}
    p1 := pet1{name: "Milo", a: d}

    fmt.Println(p1.name)
    // p1.breathe()
    // p1.walk()
    p1.a.breathe()
    p1.a.walk()

    p2 := pet2{name: "Oscar", animal: d}
    fmt.Println(p1.name)
    p2.breathe()
    p2.walk()
    p1.a.breathe()
    p1.a.walk()
}

输出

Milo
Dog breathes
Dod walk

Oscar
Dog breathes
Dog walk
Dog breathes
Dog walk

我们声明了两个结构pet1pet2pet1结构中有命名的动物接口。

type pet1 struct {
    a    animal
    name string
}

pet2嵌入了未命名/匿名的动物接口。

type pet2 struct {
    animal
    name string
}

对于pet1结构的一个实例,我们可以这样调用breathe()walk()方法。

p1.a.breathe()
p1.a.walk()

直接调用这些方法会导致编译错误。

//p1.breathe()
//p1.walk()
p1.breathe undefined (type pet1 has no field or method breathe)
p1.walk undefined (type pet1 has no field or method walk)

对于pet2结构的一个实例,我们可以直接调用breathe()walk()方法。

p2.breathe()
p2.walk()

如果嵌入接口是匿名或未命名的,我们可以直接访问嵌入接口的方法。

下面也是有效的另一种调用未命名/匿名嵌入接口的方法。

p2.animal.breathe()
p2.animal.walk()

请注意,在创建pet1pet2结构的实例时,嵌入的接口即animal是用实现该接口的类型初始化的,即dog

p1 := pet1{name: "Milo", a: d}
p2 := pet2{name: "Oscar", animal: d}

如果我们不初始化嵌入的接口animal,那么它将被初始化为接口的零值,即 nil。在这样的 pet1 或 pet2 结构实例上调用breathe()walk()方法将导致恐慌。

Go(Golang)中的空选择或没有案例的选择

来源:golangbyexample.com/empty-select-golang/

目录

  • 概述

  • 代码

概述

没有任何案例语句的选择块称为空选择。空选择将永远阻塞,因为没有案例语句可以执行。我们知道选择语句会被阻塞,直到任何案例语句可以被执行。这也是一个 goroutine 无限期等待的方式。但如果这个空选择放在主 goroutine 中,它将导致死锁。

让我们来看一个程序

代码

package main

func main() {
    select {}
}

输出

fatal error: all goroutines are asleep - deadlock!

在上面的程序中,我们有一个空的选择语句,因此它导致了死锁,这就是你看到输出的原因。

fatal error: all goroutines are asleep - deadlock!

GO 中的空结构体

来源:golangbyexample.com/empty-struct-in-go/

GO 中的空结构体 struct{} 不占用任何内存。它的大小为零字节。

以下是空结构体的一些用途

  • 对于创建不需要数据的实现非常有用。

例如,在实现 GO 中的空对象设计模式时,空对象没有任何数据。查看 GO 中空对象设计模式的示例 – golangbyexample.com/null-object-design-pattern-golang/

  • 空结构体在通道中也是一个非常好的用例,当你只想用通道进行通知而不实际传递任何数据时。

例如:在 GO 的 context 包中,我们有 cancelCtx,如下所示。可以看到,done 通道使用了空结构体,因为它仅用于通知取消,并没有数据值。

type cancelCtx struct {
    Context
    mu       sync.Mutex            // protects following fields
    done     chan struct{}         // created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}
  • 集合数据结构的实现。集合是一种不按特定顺序保存元素的数据结构。元素在集合中只出现一次。我们使用 map[keyType]struct{} 来表示集合。struct{} 仅仅用于让我们知道某个元素是否存在于集合中。

查看此链接了解 Go 中的集合实现 – golangbyexample.com/set-implementation-in-golang/

  • 是布尔通道的完美替代。布尔值占用一定空间,而结构体不占用,因此使用 struct{} 的通道更有效。请看下面的示例。
package main

import (
    "fmt"
    "time"
)

func main() {
    done := make(chan struct{}, 4)
    for i := 0; i < 4; i++ {
        go runasync(done)
    }
    for i := 0; i < 4; i++ {
        <-done
    }
    close(done)
    fmt.Printf("Finish")
}

func runasync(done chan<- struct{}) {
    time.Sleep(1 * time.Second)
    done <- struct{}{}
    return
}

Go(Golang)中的封装

来源:golangbyexample.com/encapsulation-in-go/

Golang 在包级别提供封装。Go 没有公共、私有或受保护的关键字。控制可见性的唯一机制是使用大写和小写格式

  • 大写标识符 是导出的。大写字母表示这是一个导出标识符。

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

有五种标识符可以是导出或非导出的

  1. 结构

  2. 结构的方法

  3. 结构体字段

  4. 函数

  5. 变量

让我们看一个例子,展示上述所有标识符的导出和非导出。见下方的 data.go。包是 model

  • 结构

    • 结构体 Person 是导出的

    • 结构体 company 是非导出的

  • 结构的方法

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

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

  • 结构体字段

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

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

  • 函数

    • 函数 GetPerson() 是导出的

    • 函数 getCompanyName() 是非导出的

  • 变量

    • 变量 CompanyName 是导出的

    • 变量 companyLocation 是非导出的

data.go

package model

import "fmt"

var (
    //CompanyName represents the company name
    CompanyName     = "test"
    companyLocation = "somecity"
)

//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 {
}

//GetPerson get the person object
func GetPerson() *Person {
    p := &Person{
        Name: "test",
        age:  21,
    }
    fmt.Println("Model Package:")
    fmt.Println(p.Name)
    fmt.Println(p.age)
    return p
}

func getCompanyName() string {
    return CompanyName
}

我们在 model 包中写一个文件 test.go。见下文。

test.go

package model

import "fmt"

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

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

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

    //FUNCTION
    person2 := GetPerson()
    fmt.Println(person2)
    companyName := getCompanyName()
    fmt.Println(companyName)

    //VARIBLES
    fmt.Println(companyLocation)
    fmt.Println(CompanyName)
}

运行这个文件时,它能够访问 data.go 中的所有导出和非导出字段,因为两者都位于同一个模型包中。没有编译错误,输出如下

输出:

&{test 21}
&{}
21
test
test
21
Model Package:
test
21
&{test 21}
test
somecity
test

我们将上面的文件 test.go 移动到一个名为 view 的不同包中。现在注意运行‘go build’时的输出。它会产生编译错误。所有的编译错误都是因为无法引用非导出字段

test.go

package view

import "fmt"

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

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

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

    //FUNCTION
    person2 := model.GetPerson()
    fmt.Println(person2)
    companyName := model.getCompanyName()
    fmt.Println(companyName)

    //VARIBLES
    fmt.Println(model.companyLocation)
    fmt.Println(model.CompanyName)
}

输出:

test.go:13:3: unknown field 'age' in struct literal of type model.Person
test.go:17:8: cannot refer to unexported name model.company
test.go:17:8: undefined: model.company
test.go:22:15: p.getName undefined (cannot refer to unexported field or method model.(*Person).getName)
test.go:26:15: p.age undefined (cannot refer to unexported field or method age)
test.go:31:17: cannot refer to unexported name model.getCompanyName
test.go:31:17: undefined: model.getCompanyName
test.go:35:14: cannot refer to unexported name model.companyLocation
test.go:35:14: undefined: model.companyLocation
posted @ 2024-10-19 08:37  绝不原创的飞龙  阅读(0)  评论(0编辑  收藏  举报