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

龙哥盟 / 2024-10-23 / 原文

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

Go(Golang)中的指针接收者方法

来源:golangbyexample.com/pointer-receiver-method-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,它在employee结构体上。在这个方法中,我们像这样更新员工的名字。

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 方法具有值接收者,因此更改对调用者不可见,因为值是作为副本传递的。在这三种情况下,它都打印旧名称。

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

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

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

这与函数不同,如果

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

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

何时使用指针接收者

  • 当方法内部对接收者所做的更改必须对调用者可见时。

  • 当结构体较大时,最好使用指针接收者,否则每次调用方法时都会创建结构体的副本,这将非常耗费资源。

Go (Golang)中的指向指针

来源:golangbyexample.com/pointer-to-pointer-golang/

目录

  • 概述

  • 程序

概述

在 Go 中也可以创建指向指针的指针。

a := 2
b := &a
c := &b

c在这里是一个指向指针的指针。它存储b的地址,而b又存储a的地址。可以使用***运算符进行双重解引用,从而打印指向指针的值。因此,****c将打印值 2。

下图描绘了指向指针的指针。

  • b包含a的地址。

  • c包含b的地址。

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

程序

让我们来看一个描绘指针的程序。

package main

import "fmt"

func main() {
	a := 2
	b := &a
	c := &b

	fmt.Printf("a: %d\n", a)
	fmt.Printf("b: %x\n", b)
	fmt.Printf("c: %x\n", c)

	fmt.Println()
	fmt.Printf("a: %d\n", a)
	fmt.Printf("*&a: %d\n", *&a)
	fmt.Printf("*b: %d\n", *b)
	fmt.Printf("**c: %d\n", **c)

	fmt.Println()
	fmt.Printf("&a: %d\n", &a)
	fmt.Printf("b: %d\n", b)
	fmt.Printf("&*b: %d\n", &*b)
	fmt.Printf("*&b: %d\n", *b)
	fmt.Printf("*c: %d\n", *c)

	fmt.Println()
	fmt.Printf("b: %d\n", &b)
	fmt.Printf("*c: %d\n", c)
}

输出

a: 2
b: c000018078
c: c00000e028

a: 2
*&a: 2
*b: 2
**c: 2

&a: 824633819256
b: 824633819256
&*b: 824633819256
*&b: 824633819256
*c: 824633819256

b: 824633778216
*c: 824633778216

从输出中可以清楚看出。

以下是等价且值为变量a的 2。

  • a

  • *&a

  • *b

  • **c

以下是等价且值为变量ba的地址。

*** &a

  • b

  • &*b

  • *&b

  • *c

以下是等价且值为变量cb的地址。**

***** b

  • *c

Go(Golang)中的结构体指针

来源:golangbyexample.com/pointer-to-struct-golang/

在 Golang 中,结构体是命名的数据字段集合。这些字段可以是不同类型。结构体充当容器,包含不同的异构数据类型,共同表示一个实体。例如,不同的属性用于表示组织中的员工。员工可以有:

  • 字符串类型的名称

  • 年龄类型为 int

  • DOB 类型为 time.Time

  • 薪水类型为 int

Golang 中的指针是一个变量,它保存另一个变量的内存地址。

现在我们已经理解了结构体和指针,接下来让我们看看如何定义指向结构体的指针。

假设我们有一个员工结构体

type employee struct {
    name   string
    age    int
    salary int
}

创建指向结构体的指针有两种方法

  • 使用&运算符

  • 使用 new 关键字

让我们逐一查看上述方法。

使用 & 运算符

& 运算符可用于获取结构体变量的指针。

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

结构体指针也可以直接创建

empP := &employee{name: "Sam", age: 31, salary: 2000}

让我们看一个程序

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}
    empP := &emp
    fmt.Printf("Emp: %+v\n", empP)
    empP = &employee{name: "John", age: 30, salary: 3000}
    fmt.Printf("Emp: %+v\n", empP)
}

输出

Emp: &{name:Sam age:31 salary:2000}
Emp: &{name:John age:30 salary:3000}

使用 new 关键字

使用 new() 关键字将:

  • 创建结构体

  • 将所有字段初始化为其类型的零默认值

  • 返回指向新创建结构体的指针

这将返回一个指针

empP := new(employee)

指针地址可以使用%p格式修饰符打印

fmt.Printf("Emp Pointer: %p\n", empP)

还要注意,*运算符可用于解引用指针,这意味着获取存储在指针中的地址的值。

fmt.Printf("Emp Value: %+v\n", *empP)

它将打印

Emp Value: {name: age:0 salary:0}

当不使用解引用指针而使用格式标识符%+v时,将在结构体之前附加&符号,表示这是一个指针。

fmt.Printf("Emp Value: %+v\n", empP)

它将打印

Emp Value: &{name: age:0 salary:0}

让我们看看完整程序以说明上述要点

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func main() {
    empP := new(employee)
    fmt.Printf("Emp Pointer Address: %p\n", empP)
    fmt.Printf("Emp Pointer: %+v\n", empP)
    fmt.Printf("Emp Value: %+v\n", *empP)
}

输出

Emp Pointer: 0xc0000a6020
Emp Value: &{name: age:0 salary:0}
Emp Value: {name: age:0 salary:0}

打印的指针地址在你的机器上会有所不同。

在 Go(Golang)中实现接口时,指针与值接收器的方法

来源:golangbyexample.com/pointer-vs-value-receiver-method-golang/

一个类型的方法可以有指针接收器或值接收器。当该类型实现接口时,指针与值接收器有一些注意事项

  • 如果一个类型使用值接收器实现了接口的所有方法,那么该类型的值和指针都可以用于赋值给该接口变量或传递给接受该接口作为参数的函数。

  • 如果一个类型使用指针接收器实现了接口的所有方法,那么只有该类型的指针可以用于赋值给该接口变量或传递给接受该接口作为参数的函数。

示例以演示上述第一个要点

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

type animal interface {
    breathe()
    walk()
}

我们也有一个狮子结构体实现了这个动物接口

type lion struct {
    age int
}

代码

package main

import "fmt"

type animal interface {
    breathe()
    walk()
}

type lion struct {
    age int
}

func (l lion) breathe() {
    fmt.Println("Lion breathes")
}

func (l lion) walk() {
    fmt.Println("Lion walk")
}

func main() {
    var a animal

    a = lion{age: 10}
    a.breathe()
    a.walk()

    a = &lion{age: 5}
    a.breathe()
    a.walk()
}

输出

Lion breathes
Lion walk
Lion breathes
Lion walk

狮子结构体使用值接收器实现了动物接口。因此它适用于狮子类型的两个变量以及指向狮子类型变量的指针。

这样可以

 a = lion{age: 10}

以及这个

a = &lion{age: 5}

示例以演示上述第二个要点。狮子结构体使用指针接收器实现了动物接口。因此它仅适用于指向狮子类型变量的指针。

所以这样可以

a = &lion{age: 5}

但这会导致编译错误

a = lion{age: 10}
cannot use lion literal (type lion) as type animal in assignment:
        lion does not implement animal (breathe method has pointer receiver)

查看完整工作代码

package main

import "fmt"

type animal interface {
	breathe()
	walk()
}

type lion struct {
	age int
}

func (l *lion) breathe() {
	fmt.Println("Lion breathes")
}

func (l *lion) walk() {
	fmt.Println("Lion walk")
}

func main() {
	var a animal

	//a = lion{age: 10}
	a.breathe()
	a.walk()

	a = &lion{age: 5}
	a.breathe()
	a.walk()
}

取消注释这一行

a = lion{age: 10}

这将导致编译错误

cannot use lion literal (type lion) as type animal in assignment:
        lion does not implement animal (breathe method has pointer receiver)

在 Go (Golang) 中的二叉树后序遍历

来源:golangbyexample.com/postorder-binary-tree-golang/

目录

  • 概述

  • 程序

概述

在二叉树的后序遍历中,我们遵循以下顺序

  • 访问左子树

  • 访问右子树

  • 访问根节点

例如,假设我们有如下二叉树

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

然后后序遍历结果为

[4 2 5 6 3 1]

程序

下面是相应的程序

package main

import (
	"fmt"
)

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func postorderTraversal(root *TreeNode) []int {
	if root == nil {
		return nil
	}

	left := postorderTraversal(root.Left)
	right := postorderTraversal(root.Right)

	output := make([]int, 0)

	output = append(output, left...)

	output = append(output, right...)

	output = append(output, root.Val)
	return output
}

func main() {
	root := TreeNode{Val: 1}
	root.Left = &TreeNode{Val: 2}
	root.Left.Left = &TreeNode{Val: 4}
	root.Right = &TreeNode{Val: 3}
	root.Right.Left = &TreeNode{Val: 5}
	root.Right.Right = &TreeNode{Val: 6}

	output := postorderTraversal(&root)
	fmt.Println(output)

}

输出

[4 2 5 6 3 1]

注意: 请查看我们的 Golang 高级教程。本系列教程内容详尽,我们努力覆盖所有概念及示例。本教程适合希望掌握 Golang 知识的用户 – Golang 高级教程

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

在 Go 语言中,二叉树的先序遍历

来源:golangbyexample.com/preorder-binary-tree-golang/

目录

  • 概述

  • 程序

概述

在二叉树的先序遍历中,我们遵循以下顺序

  • 访问根节点

  • 访问左子树

  • 访问右子树

例如,我们有以下二叉树

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

然后先序遍历为

[1 2 4 3 5 6]

程序

这是相应的程序

package main

import (
	"fmt"
)

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func preorderTraversal(root *TreeNode) []int {
	if root == nil {
		return nil
	}

	left := preorderTraversal(root.Left)
	right := preorderTraversal(root.Right)

	output := make([]int, 0)

	output = append(output, root.Val)
	output = append(output, left...)
	output = append(output, right...)
	return output
}

func main() {
	root := TreeNode{Val: 1}
	root.Left = &TreeNode{Val: 2}
	root.Left.Left = &TreeNode{Val: 4}
	root.Right = &TreeNode{Val: 3}
	root.Right.Left = &TreeNode{Val: 5}
	root.Right.Right = &TreeNode{Val: 6}

	output := preorderTraversal(&root)
	fmt.Println(output)

}

输出

[1 2 4 3 5 6]

注意: 查看我们的 Golang 高级教程。本系列教程内容详尽,我们尽力覆盖所有概念并附有示例。本教程适合希望深入掌握 Golang 的用户 – Golang 高级教程

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

在 Go (Golang)中美观地打印结构体变量

来源:golangbyexample.com/print-struct-variables-golang/

注意: 如果你对学习 Golang 感兴趣,我们有一个全面的 Golang 教程系列。请查看一下 – Golang 全面教程系列。现在让我们来看当前的教程。以下是目录。

目录

  • 概述

  • 使用 fmt 包

  • 以 JSON 格式打印结构体

概述

有两种方法可以打印所有结构体变量,包括所有的键和值。

  • 使用fmt

  • 使用json/encoding包以 JSON 格式打印结构体。这也允许美观地打印结构体。

假设我们有一个员工结构体,如下所示:

type employee struct {
    name   string
    age    int
    salary int
}

让我们看看两种打印员工结构体实例的方法。

使用 fmt 包

fmt.Printf()函数可以用来打印一个结构体。可以使用不同的格式标识符以不同的方式打印结构体。让我们看看如何使用不同的格式标识符以不同的格式打印结构体。

首先让我们创建一个员工的实例。

emp := employee{name: "Sam", age: 31, salary: 2000}
  • %v – 仅打印值,字段名不会被打印。这是打印结构体的默认方式。例如:
fmt.Printf("%v", emp)  -  {Sam 31 2000}
  • %+v – 它将打印字段和值。例如:
fmt.Printf("%+v", emp) - {name:Sam age:31 salary:2000}
  • %#v – 它将打印结构体名称,以及字段和值。例如:
fmt.Printf("%#v", emp) - main.employee{name:"Sam", age:31, salary:2000}

fmt.Println()函数也可以用来打印一个结构体。由于fmt.Println()函数的默认值是%v,因此输出将与使用%v 的fmt.Printf()相同。

fmt.Println(emp) - {Sam 31 2000}

我们也来看一个工作程序。

package main

import "fmt"

type employee struct {
    name   string
    age    int
    salary int
}

func main() {
    emp := employee{name: "Sam", age: 31, salary: 2000}
    fmt.Printf("Emp: %v\n", emp)
    fmt.Printf("Emp: %+v\n", emp)
    fmt.Printf("Emp: %#v\n", emp)
    fmt.Println(emp)
}

输出

Emp: {Sam 31 2000}
Emp: {name:Sam age:31 salary:2000}
Emp: main.employee{name:"Sam", age:31, salary:2000}
{Sam 31 2000}

以 JSON 格式打印结构体

第二种方法是以 JSON 格式打印结构体。encoding/json包的MarshalMarshalIndent函数可以用来以 JSON 格式打印结构体。这里是区别:

  • Marshal – 以下是Marshal函数的签名。该函数通过递归遍历值返回v的 JSON 编码。
Marshal(v interface{}) ([]byte, error)
  • MarshalIndent– 以下是MarshalIndent函数的签名。它类似于Marshal函数,但应用了缩进以格式化输出。因此,可以用来美观地打印一个结构体。
MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)

需要注意的是,MarshalMarshalIndent函数只能访问结构体的导出字段,这意味着只有以大写字母开头的字段才能被访问和编码为 JSON 格式。

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type employee struct {
    Name   string
    Age    int
    salary int
}

func main() {
    emp := employee{Name: "Sam", Age: 31, salary: 2000}
    //Marshal
    empJSON, err := json.Marshal(emp)
    if err != nil {
        log.Fatalf(err.Error())
    }
    fmt.Printf("Marshal funnction output %s\n", string(empJSON))

    //MarshalIndent
    empJSON, err = json.MarshalIndent(emp, "", "  ")
    if err != nil {
        log.Fatalf(err.Error())
    }
    fmt.Printf("MarshalIndent funnction output %s\n", string(empJSON))
}

输出:

Marshal funnction output {"Name":"Sam","Age":31}

MarshalIndent funnction output {
  "Name": "Sam",
  "Age": 31
}

工资字段未在输出中打印,因为它以小写字母开头且未导出。Marshal函数的输出未经过格式化,而MarshalIndent函数的输出是格式化的。

golang 还允许通过使用结构体元字段使 JSON 编码结构体的键名不同。我们先来理解一下什么是结构体元字段。在 Go 中,结构体也允许为其字段添加元数据。这些元字段可以用于编码解码成不同形式,对结构体字段进行某些形式的验证等。因此,基本上任何元信息都可以与结构体的字段存储,并可以被任何包或库用于不同的目的。

以下是附加元数据的格式。元数据是一个字符串文字,即它被反引号包围。

type strutName struct{
   fieldName type `key:value key2:value2`
}

现在针对我们的用例,我们将为员工结构体添加 JSON 标签,如下所示。Marshal 函数将使用标签中指定的键名。

type employee struct {
    Name   string `json:"n"`
    Age    int    `json:"a"`
    Salary int    `json:"s"`
}

让我们看看完整的程序

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type employee struct {
    Name   string `json:"n"`
    Age    int    `json:"a"`
    Salary int    `json:"s"`
}

func main() {
    emp := employee{Name: "Sam", Age: 31, Salary: 2000}
    //Converting to jsonn
    empJSON, err := json.MarshalIndent(emp, '', '  ')
    if err != nil {
        log.Fatalf(err.Error())
    }
    fmt.Println(string(empJSON))
}

输出:

{
  "n": "Sam",
  "a": 31,
  "s": 2000
}

输出中的键名与 JSON 元标签中指定的相同。

打印嵌套结构体

即使结构体包含另一个结构体,也可以使用上述讨论的方法打印相同内容。

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type address struct {
    City    string `json:"city"`
    Country string `json:"country"`
}

type employee struct {
    Name    string  `json:"name"`
    Age     int     `json:"age"`
    Salary  int     `json:"salary"`
    Address address `json:"address"`
}

func main() {
    address := address{City: "some_city", Country: "some_country"}
    emp := employee{Name: "Sam", Age: 31, Salary: 2000, Address: address}
    //Converting to json
    empJSON, err := json.MarshalIndent(emp, "", "  ")
    if err != nil {
        log.Fatalf(err.Error())
    }
    fmt.Printf("MarshalIndent funnction output\n %s\n", string(empJSON))
}

输出

MarshalIndent function output
 {
  "name": "Sam",
  "age": 31,
  "salary": 2000,
  "address": {
    "city": "some_city",
    "country": "some_country"
  }
}

这就是关于打印结构体的全部内容。希望你喜欢这篇文章。请在评论中分享反馈。

在 Go (Golang)中打印所有二叉树路径

来源:golangbyexample.com/print-all-binary-tree-paths-go/

目录

  • 概述

  • 程序

概述

给定一棵树的根节点。目标是打印所有从根节点开始的树路径。

示例

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

输出:

1->2->4
1->3->5
1->3->6

程序

下面是相同程序的代码

package main

import (
	"fmt"
	"strconv"
)

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func binaryTreePaths(root *TreeNode) []string {
	output := make([]string, 0)

	binaryTreePathsUtil(root, "", &output)
	return output
}

func binaryTreePathsUtil(root *TreeNode, curr string, output *[]string) {
	if root == nil {
		return
	}

	valString := strconv.Itoa(root.Val)
	if curr == "" {
		curr = valString
	} else {
		curr = curr + "->" + valString
	}
	if root.Left == nil && root.Right == nil {
		*output = append(*output, curr)
		return
	}

	binaryTreePathsUtil(root.Left, curr, output)
	binaryTreePathsUtil(root.Right, curr, output)

}

func main() {
	root := TreeNode{Val: 1}
	root.Left = &TreeNode{Val: 2}
	root.Left.Left = &TreeNode{Val: 4}
	root.Right = &TreeNode{Val: 3}
	root.Right.Left = &TreeNode{Val: 5}
	root.Right.Right = &TreeNode{Val: 6}

	output := binaryTreePaths(&root)
	fmt.Println(output)
}

输出:

[1->2->4 1->3->5 1->3->6]

注意: 请查看我们的 Golang 高级教程。本系列的教程内容详尽,我们尝试用示例覆盖所有概念。这个教程是为了那些希望获得 Golang 专业知识和扎实理解的人准备的 – Golang 高级教程

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

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

在 Go(Golang)中打印数组或切片元素

来源:golangbyexample.com/print-an-array-or-slice-elements-golang/

目录

概述

  • 打印切片

  • 打印数组

概述

打印数组或切片的方式是相同的。让我们逐个查看

打印切片

一起打印切片元素

使用

  • fmt.Println 和

  • fmt.Printf

package main
import "fmt"
func main() {
    numbers := []int{1, 2, 3}
    fmt.Println(numbers)
    fmt.Printf("Numbers: %v", numbers)
}

输出

[1 2 3]
Numbers: [1 2 3]

打印单个切片元素

使用

  • for-range 循环
package main
import "fmt"
func main() {
    numbers := []int{1, 2, 3}
    for _, num := range numbers {
        fmt.Println(num)
    }
}

输出

1
2
3

打印数组

一起打印数组元素

使用

  • fmt.Println 和

  • fmt.Printf

package main
import "fmt"
func main() {
    numbers := [3]int{1, 2, 3}
    fmt.Println(numbers)
    fmt.Printf("Numbers: %v", numbers)
}

输出

[1 2 3]
Numbers: [1 2 3]

打印单个数组元素

使用

  • for-range 循环
package main
import "fmt"
func main() {
    numbers := [3]int{1, 2, 3}
    for _, num := range numbers {
        fmt.Println(num)
    }
}

输出

1
2
3
  • 数组* 切片*

在 Go(Golang)中打印/输出删除线文本

来源:golangbyexample.com/print-text-crossout-golang/

目录

  • 概述

  • 程序

概述

我们可以使用 faith 包来实现相同的功能

github.com/fatih/color

程序

package main

import (
	"github.com/fatih/color"
)

func main() {
	whilte := color.New(color.FgWhite)
	boldWhite := whilte.Add(color.CrossedOut)
	boldWhite.Println("This will print text in crossout")
}

输出

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

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

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

在 Go(Golang)中以粗体打印/输出文本

来源:golangbyexample.com/print-text-bold-go/

目录

  • 概述

  • 程序

概述

我们可以使用 faith 包来实现相同的功能

github.com/fatih/color

程序

package main

import "github.com/fatih/color"

func main() {
    whilte := color.New(color.FgWhite)
    boldWhite := whilte.Add(color.Bold)
    boldWhite.Println("This will print text in bold white.")
}

输出

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

在控制台中以颜色输出文本

来源:golangbyexample.com/print-output-text-color-console/

注意: 如果你有兴趣学习 Golang,我们有一个全面的 Golang 教程系列。请查看一下 – Golang 全面教程系列。现在让我们看看当前的教程。下面是目录。

目录

  • 概述

  • 代码

概述

ANSI 转义码可以用来在控制台中输出彩色文本。请注意

  • 在 MAC/Linux 系统终端支持 ANSI 转义码

  • Windows 命令提示符不支持它。在 Windows 上,你可以安装 Cygwin。ANSI 转义码在那个环境下可以工作。

另外需要提到的是,在下面的代码中,我们在打印后使用了 colorReset。如果不使用它,颜色效果将保持,不会被清除。去掉下面代码中的 colorReset,它将以青色显示文本“next”

下面是用 Golang 实现相同功能的代码。

代码

package main

import (
    "fmt"
)

func main() {
    colorReset := "\0330m"

    colorRed := "\033[31m"
    colorGreen := "\033[32m"
    colorYellow := "\033[33m"
    colorBlue := "\033[34m"
    colorPurple := "\033[35m"
    colorCyan := "\033[36m"
    colorWhite := "\033[37m"

    fmt.Println(string(colorRed), "test")
    fmt.Println(string(colorGreen), "test")
    fmt.Println(string(colorYellow), "test")
    fmt.Println(string(colorBlue), "test")
    fmt.Println(string(colorPurple), "test")
    fmt.Println(string(colorWhite), "test")
    fmt.Println(string(colorCyan), "test", string(colorReset))
    fmt.Println("next")
}

输出:

在我的 Mac 机器上

![* ansi* color* console* go* output* print* terminal* text*

在 Go (Golang) 中以斜体打印/输出文本

来源:golangbyexample.com/print-italic-text-golang/

目录

  • 概述

  • 程序

概述

我们可以使用 faith 包来实现相同的功能。

github.com/fatih/color

程序

package main

import (
	"fmt"

	"github.com/fatih/color"
)

func main() {
	whilte := color.New(color.FgWhite)
	boldWhite := whilte.Add(color.Italic)
	boldWhite.Println("This will print text in italic white.")
}

输出

![](https://gitee.com/OpenDocCN/geekdoc-golang-zh/raw/master/docs/go-exam-2023/img/57e8598f752454129e75169d77836aff.png)* 前往* Golang*

在 Go(Golang)中打印/输出下划线文本

来源:golangbyexample.com/print-text-underline-golang/

目录

  • 概述

  • 程序

概述

我们可以使用 faith 包来实现相同的功能。

github.com/fatih/color

程序

package main

import (
	"github.com/fatih/color"
)

func main() {
	whilte := color.New(color.FgWhite)
	boldWhite := whilte.Add(color.Underline)
	boldWhite.Println("This will print text in underline")
}

输出

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

注意: 请查看我们的 Golang 高级教程。本系列教程详细阐述了所有概念,并提供了示例。本教程适合那些希望获得专业知识和对 golang 有深入理解的人——Golang 高级教程

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

在 Go (Golang) 中打印/输出带背景的文本

来源:golangbyexample.com/print-text-background-golang/

目录

  • 概述

  • 程序

概述

我们可以使用 faith 包来实现相同的功能

github.com/fatih/color

程序

在下面的程序中,我们将文本以白色字体打印在红色背景上

package main

import (
	"fmt"

	"github.com/fatih/color"
)

func main() {
	whilte := color.New(color.FgWhite)
	boldWhite := whilte.Add(color.BgRed)
	boldWhite.Print("This will print white text with red background")
	fmt.Println()

	boldWhite = whilte.Add(color.BgGreen)
	boldWhite.Print("This will print white text with green background")
	fmt.Println()

}

输出

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

其他背景颜色选项有

github.com/fatih/color/blob/master/color.go

// Background text colors
const (
    BgBlack Attribute = iota + 40
    BgRed
    BgGreen
    BgYellow
    BgBlue
    BgMagenta
    BgCyan
    BgWhite
)
// Background Hi-Intensity text colors
const (
    BgHiBlack Attribute = iota + 100
    BgHiRed
    BgHiGreen
    BgHiYellow
    BgHiBlue
    BgHiMagenta
    BgHiCyan
    BgHiWhite
)

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

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

在 Go (Golang) 中用双引号打印字符串

来源:golangbyexample.com/print-double-quotes-string-golang/

目录

  • 概述**

  • 双引号字符串的程序

概述

反斜杠是转义字符。要打印包含字面双引号的任何字符串,我们需要在字符串用双引号括起来时转义这两个引号。然而,用反引号括起来的字符串是原始字面字符串,不会遵循任何转义。因此,反引号也可以用来打印包含字面双引号的字符串。

双引号字符串的程序

package main

import "fmt"

func main() {
	fmt.Println("\"test\"")
}

输出

"test"

注意到我们在那个字符串中转义了单引号和双引号

反引号的程序

package main
import "fmt"
func main() {
    fmt.Println(`"test"`)
}

输出

"test"

在 Go 语言中打印给定字符的下一个或上一个字符。

来源:golangbyexample.com/next-previous-char-golang/

目录

  • 概述

  • 程序

概述

通过对当前字符进行加 1 和减 1,我们可以得到下一个和上一个字符。

程序

这里是相应的程序。

package main

import "fmt"

func main() {
	var curr byte
	curr = 'b'

	fmt.Println(string(curr + 1))
	fmt.Println(string(curr - 1))
} 

输出:

c
a

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

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

在 Go(Golang)中打印接口的基础类型和值

来源:golangbyexample.com/print-type-value-interface-golang/

目录

  • 概述

  • 代码

概述

Golang 提供格式标识符来打印接口值所表示的基础类型和基础值。

  • %T 可以用来打印接口值的具体类型

  • %v 可以用来打印接口值的具体值。

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

type animal interface {
    breathe()
    walk()
}

我们还有一个狮子结构体实现了这个动物接口。

type lion struct {
    age int
}

代码

package main

import "fmt"

type animal interface {
    breathe()
    walk()
}

type lion struct {
    age int
}

func (l lion) breathe() {
    fmt.Println("Lion breathes")
}

func (l lion) walk() {
    fmt.Println("Lion walk")
}

func main() {
    var a animal
    a = lion{age: 10}
    fmt.Printf("Underlying Type: %T\n", a)
    fmt.Printf("Underlying Value: %v\n", a)
}

输出

Concrete Type: main.lion
Concrete Value: {10}

Go (Golang)中的 Println vs Print vs Printf

来源:golangbyexample.com/println-printf-print-golang/

目录

  • 概述

  • 关于 Println 函数

  • 关于 Print 函数

  • 关于 Printf 函数

    • 打印字符串变量

    • 打印整数

    • 打印结构体

概述

PrintlnPrintPrintffmt包中定义,用于格式化字符串并写入标准输出

golang.org/pkg/fmt/

它们之间的基本区别是

  • Println使用默认格式说明符格式化字符串,并在字符串后添加新行

  • Print使用默认格式说明符格式化字符串,但在字符串后不添加新行

  • Printf使用自定义说明符格式化字符串。它也不会添加新行

让我们详细了解每个例子

关于 Println 函数

以下是Println的函数原型

func Println(a ...interface{}) (n int, err error)

Println接受可变数量的参数,每个参数都是一个空接口。它返回打印的字符数和任何发生的错误。由于参数类型是空接口,我们可以传递任何数据类型。我们可以传递字符串、整型、浮点型、结构体或任何其他数据类型。每个传递给Println函数的参数都根据该参数类型的默认格式说明符进行格式化。例如,结构体将根据以下说明符进行格式化

%v

此格式说明符仅打印结构体中的值部分。让我们看一个例子

package main
import "fmt"
type employee struct {
    Name string
    Age  int
}
func main() {
    name := "John"
    age := 21
    fmt.Println("Name is: ", name)
    fmt.Println("Age is: ", age)
    e := employee{
        Name: name,
        Age:  age,
    }
    fmt.Println(e)
    fmt.Println("a", 12, "b", 12.0)

    bytesPrinted, err := fmt.Println("Name is: ", name)
    if err != nil {
	log.Fatalln("Error occured", err)
    }
    fmt.Println(bytesPrinted)
}

输出

Name is: John
Age is: 21
{John 21}
a 12 b 12
Name is: John
14

关于Println函数的一些重要注意事项

  • 它在末尾附加新行。这就是为什么每个输出都在不同的行上

  • 输出中的每个参数将用空格分隔。这就是为什么

fmt.Println("Name is: ", name)

打印

Name is: John

两个参数之间会自动引入空格。

  • 它返回打印的字符数或任何发生的错误
bytesPrinted, err := fmt.Println("Name is: ", name)
if err != nil {
    log.Fatalln("Error occured", err)
}
fmt.Println(bytesPrinted)

将输出如下

Name is: John
14

bytesPrinted的数量为 14,因为输出了 14 个字符

关于 Print 函数

Print的函数原型

func Print(a ...interface{}) (n int, err error)

Print函数与Println函数完全相同,除了两个区别

  • 它在末尾不附加新行。我们需要使用新行标识符来添加新行“\n”。

  • 只有当两个操作数都不是字符串时,才会在参数之间添加空格

让我们看一个相同的例子

package main

import "fmt"

type employee struct {
	Name string
	Age  int
}

func main() {
	name := "John"
	age := 21
	fmt.Print("Name is:", name, "\n")
	fmt.Print("Age is:", age, "\n")

	e := employee{
		Name: name,
		Age:  age,
	}

	fmt.Print(e, "\n")

	fmt.Print("a", 12, "b", 12.0, "\n")

	fmt.Print(12, 12.0, "\n")

        bytesPrinted, err := fmt.Print("Name is: ", name, "\n")
	if err != nil {
		log.Fatalln("Error occured", err)
	}
	fmt.Print(bytesPrinted)
}

输出

Name is:John
Age is:21
{John 21}
a12b12
12 12
Name is: John
14

关于Print函数的一些重要注意事项

  • 它在末尾不会附加新行。这就是为什么需要使用“\n”来添加新行。

  • 只有在每个参数都是非字符串时,它才会在两个参数之间添加空格。这就是原因

fmt.Print(12, 12.0, "\n")

打印

12 12

fmt.Print("a", 12, "b", 12.0, "\n")

打印

a12b12
  • 它还会返回打印的字符数以及任何可能发生的错误

关于 Printf 函数

Printf 的函数原型

func Printf(format string, a ...interface{}) (n int, err error)

Printf 也是一个可变参数函数,这意味着它可以有多个参数。关于它的参数列表有两个重要点

  • 请注意,第一个参数是 格式模板 字符串。

  • 下一个是可变数量的参数。这些参数可以是字符串、整数、结构体或其他任何类型。出于上述相同的原因,这就是为什么它是一个空接口

格式模板 字符串包含需要格式化的实际字符串加上一些格式化动词。这些格式化动词告诉后续参数在最终字符串中将如何格式化。因此,基本上格式字符串参数包含某些符号,这些符号会被后续参数替换。

例如

打印字符串变量

  • %s 符号被使用

  • 示例

name := "John"
fmt.Printf("Name is: %s\n", name)

打印整数

  • %d 符号被使用

  • 示例

age := 21
fmt.Printf("Age is: %d\n", age)

打印结构体

例如,打印结构体有三种格式说明符。

  • %v – 它将只打印值。字段名称将不会被打印。这是使用 Println 打印结构体的默认方式

  • %+v – 它将打印字段和值。

  • %#v – 它将打印结构体,同时打印字段名称和对应的值

这就是为什么

fmt.Printf("Employee is %v\n", e)
fmt.Printf("Employee is %+v\n", e)
fmt.Printf("Employee is %#v\n", e)

分别打印如下

Employee is {John 21}
Employee is {Name:John Age:21}
Employee is main.employee{Name:"John", Age:21}

这与上述解释一致。

此外,请注意,此函数返回打印的字符数以及任何可能发生的错误。与 Println 不同,它不会自动添加新行。你需要显式添加 “\n”

下面是相应的工作程序

package main

import (
	"fmt"
	"log"
)

type employee struct {
	Name string
	Age  int
}

func main() {
	name := "John"
	age := 21

	fmt.Printf("Name is: %s\n", name)
	fmt.Printf("Age is: %d\n", age)

	fmt.Printf("Name: %s Age: %d\n", name, age)

	e := employee{
		Name: name,
		Age:  age,
	}

	fmt.Printf("Employee is %v\n", e)
	fmt.Printf("Employee is %+v\n", e)
	fmt.Printf("Employee is %#v\n", e)

	bytesPrinted, err := fmt.Printf("Name is: %s\n", name)
	if err != nil {
		log.Fatalln("Error occured", err)
	}
	fmt.Println(bytesPrinted)
}

输出

Name is: John
Age is: 21
Name: John Age: 21
Employee is {John 21}
Employee is {Name:John Age:21}
Employee is main.employee{Name:"John", Age:21}
Name is: John
14

请注意下面的 Printf

fmt.Printf("Name: %s Age: %d\n", name, age)
  • %s 被替换为名称。

  • %d 被替换为年龄。

所以基本上,格式字符串参数中的符号或动词会按顺序被后续参数替换如果格式字符串中的格式说明符数量与后续可变参数的数量不匹配,则格式说明符将按原样打印。例如,在下面的代码中,我们有两个格式说明符

*** %d

  • %s

而下一个可变数量的参数只有一个。因此,当我们打印时,它将按照原样打印第二个格式说明符,并显示“MISSING”作为警告

package main
import "fmt"
type employee struct {
    Name string
    Age  int
}
func main() {
    name := "John"
    fmt.Printf("Name is: %s %d\n", name)
}

输出

Name is: John %!d(MISSING)

另外,查看我们的 Golang 高级教程系列 – Golang 高级教程

Go(Golang)中在已排序数组中进行二分查找的程序

来源:golangbyexample.com/binary-search-sorted-array-golang/

目录

  • 概述

  • 程序

概述

思路是在输入数组中对给定的目标元素进行二分查找。如果目标元素存在,则输出其索引。如果输出元素不存在,则输出 -1。

预期时间复杂度为 O(logn)

示例 1

Input: [1, 4, 5, 6]
Target Element: 4
Output: 1

Target element 4 is present at index 1

示例 2

Input: [1, 2, 3]
Target Element: 4
Output: -1

Target element 4 is present at index 1

程序

package main

import "fmt"

func search(nums []int, target int) int {
	return binarySearch(nums, 0, len(nums)-1, target)
}

func binarySearch(nums []int, start, end, target int) int {
	if start > end {
		return -1
	}
	mid := (start + end) / 2

	if nums[mid] == target {
		return mid
	}

	if target < nums[mid] {
		return binarySearch(nums, start, mid-1, target)
	} else {
		return binarySearch(nums, mid+1, end, target)
	}
}

func main() {
	index := search([]int{1, 4, 5, 6}, 4)
	fmt.Println(index)

	index = search([]int{1, 2, 3, 6}, 4)
	fmt.Println(index)
}

输出

1
-1

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

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

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

用 Go (Golang) 编写的房屋抢劫问题程序

来源:golangbyexample.com/house-robber-golang/

目录

概述

  • 程序 * * ## 概述

邻里中有几所房子。每所房子里都有一些钱。这些房子被表示为一个数组,数组中的每个条目表示该房子里的钱数。

例如,如果我们有以下数组

[2, 3, 4, 2]

然后

  • 第一所房子里有 2 元钱。

  • 第二所房子里有 3 元钱。

  • 第三所房子里有 4 元钱。

  • 第四所房子里有 2 元钱。

抢劫者可以抢劫任意数量的房子,但他不能抢劫两个相邻的房子。例如,他可以在上述数组的以下组合中进行抢劫。

  • 1 和 3

  • 1 和 4

  • 2 和 4

上述组合中没有相邻的房子。问题是确定哪个组合能为抢劫者带来最大的收益。

例如,在上述情况下,第一个组合(1 和 3)将给他带来最大的抢劫金额,即 2+4=6。因此,抢劫者可以在第一和第三所房子中抢劫,得到 2+4=6。

另一个例子

Input: [1, 6, 8, 2, 3, 4]
Output: 13

抢劫者可以在第一、第三和第六所房子中抢劫,得到 1+8+4=13。

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

  • dp[0] = money[0]

  • dp[1] = max(money[0], money[1])

  • dp[2] = max(money[0] + money[1], money[2])

  • dp[i] = dp[i] + max(dp[i-1], dp[i-2])

其中 dp[i] 表示如果包括第 i 所房子,抢劫者可以抢劫的金额。最后,我们返回 dp 数组中的最大值。

程序

这里是相同程序的代码。

package main

import "fmt"

func rob(nums []int) int {
	lenNums := len(nums)

	if lenNums == 0 {
		return 0
	}

	maxMoney := make([]int, lenNums)

	maxMoney[0] = nums[0]

	if lenNums > 1 {
		maxMoney[1] = nums[1]
	}

	if lenNums > 2 {
		maxMoney[2] = nums[2] + nums[0]
	}

	for i := 3; i < lenNums; i++ {
		if maxMoney[i-2] > maxMoney[i-3] {
			maxMoney[i] = nums[i] + maxMoney[i-2]
		} else {
			maxMoney[i] = nums[i] + maxMoney[i-3]
		}

	}

	max := 0
	for i := lenNums; i < lenNums; i++ {
		if maxMoney[i] > max {
			max = maxMoney[i]
		}
	}

	return max
}

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

	output = rob([]int{1, 6, 8, 2, 3, 4})
	fmt.Println(output)

}

输出

6
13

注意: 查看我们的 Golang 高级教程。本系列教程内容详尽,我们试图用例子涵盖所有概念。这个教程是为那些希望获得专业知识和对 Golang 有深入理解的人准备的 – Golang 高级教程。

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