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

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

验证 Go(Golang)中结构体中整数的范围

来源:golangbyexample.com/range-int-struct-validate-golang/

目录

  • 概述

  • 示例

概述

下面的库可用于验证 Golang 中结构体中整数的范围

在本教程中,我们将使用以下员工结构体

type employee struct {
    Age int
}

示例

让我们看一个相同的例子。以下是代码

go.mod

module sample.com/validate
go 1.14
require (
    github.com/go-playground/universal-translator v0.17.0 // indirect
    github.com/leodido/go-urn v1.2.1 // indirect
    gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
    gopkg.in/go-playground/validator.v9 v9.31.0
)

main.go

package main
import (
    "fmt"
    "gopkg.in/go-playground/validator.v9"
)
var validate *validator.Validate
type employee struct {
    Age int `validate:"required,gte=10,lte=20"`
}
func main() {
    e := employee{}
    err := validateStruct(e)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
    }
    e = employee{Age: 5}
    err = validateStruct(e)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
    }
    e = employee{Age: 25}
    err = validateStruct(e)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
    }
}
func validateStruct(e employee) error {
    validate = validator.New()
    err := validate.Struct(e)
    if err != nil {
        return err
    }
    return nil
}

输出

Error: Key: 'employee.Age' Error:Field validation for 'Age' failed on the 'required' tag
Error: Key: 'employee.Age' Error:Field validation for 'Age' failed on the 'gte' tag
Error: Key: 'employee.Age' Error:Field validation for 'Age' failed on the 'lte' tag

首先,我们需要声明 Validate 的实例

var validate *validator.Validate

请注意,我们需要将元标签与结构体的字段关联,以让验证器知道您想要验证此字段。在上面的示例中,我们为年龄字段添加了标签。该标签由 playground validate 库解析。请注意,我们为年龄字段添加了三种验证

  • required – 验证字段是否存在

  • gte – 验证字段值是否大于等于特定值

  • lte – 验证字段值是否小于等于特定值

type employee struct {
	Age int `validate:"required,gte=10,lte=20"`
}

然后调用 Struct 方法来验证结构体

validate.Struct(e)

对于

e := employee{}

输出如下,因为年龄字段为空

Error: Key: 'employee.Age' Error:Field validation for 'Age' failed on the 'required' tag

对于

e := employee{Age: 5}

输出如下,因为年龄字段的值是 5,小于 10

Error: Key: 'employee.Age' Error:Field validation for 'Age' failed on the 'gte' tag

对于

e := employee{Age: 25}

输出如下,因为年龄字段的值是 25,超过了 20

Error: Key: 'employee.Age' Error:Field validation for 'Age' failed on the 'lte' tag

在 Go (Golang) 中验证 HTTP 请求体中整数的范围。

来源:golangbyexample.com/validate-range-http-body-golang/

目录

  • 概述

  • 程序

概述

以下库可用于验证传入 JSON HTTP 请求体中整数的范围。

在本教程中,我们将尝试将传入的 JSON 请求体解析为以下员工结构。

type employee struct {
    Age int `validate:"required,gte=10,lte=20"`
}

请注意,我们需要将元标签与结构的字段关联,以便让验证器知道您希望验证此字段。在上述示例中,我们为年龄字段添加了标签。这个标签由 playground validate 库解释。请注意,我们为 年龄 字段添加了三个验证。

  • 必需 – 验证字段是否存在。

  • gte – 验证字段值是否大于等于特定值。

  • lte – 验证字段值是否小于等于特定值。

程序

package main
import (
    "encoding/json"
    "net/http"
    "github.com/go-playground/validator"
)
var validate *validator.Validate
type employee struct {
    Age int `validate:"required,gte=10,lte=20"`
}
func main() {
    createEmployeeHanlder := http.HandlerFunc(createEmployee)
    http.Handle("/employee", createEmployeeHanlder)
    http.ListenAndServe(":8080", nil)
}
func createEmployee(w http.ResponseWriter, r *http.Request) {
    headerContentTtype := r.Header.Get("Content-Type")
    if headerContentTtype != "application/json" {
        errorResponse(w, "Content Type is not application/json", http.StatusUnsupportedMediaType)
        return
    }
    var e employee
    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&e)
    if err != nil {
        errorResponse(w, "Bad Request "+err.Error(), http.StatusBadRequest)
    }
    err = validateStruct(e)
    if err != nil {
        errorResponse(w, "Bad Request "+err.Error(), http.StatusBadRequest)
    }
    errorResponse(w, "Success", http.StatusOK)
    return
}
func errorResponse(w http.ResponseWriter, message string, httpStatusCode int) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(httpStatusCode)
    resp := make(map[string]string)
    resp["message"] = message
    jsonResp, _ := json.Marshal(resp)
    w.Write(jsonResp)
}
func validateStruct(e employee) error {
    validate = validator.New()
    err := validate.Struct(e)
    if err != nil {
        return err
    }
    return nil
}

运行程序。

go run main.go

它将启动一个监听在 8080 端口的 HTTP 服务器。现在进行一些 curl API 调用。

  • 以下 curl 调用
curl  -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Age": 5}'

返回以下响应,因为 5 小于最小值 10。

{"message":"Bad Request Key: 'employee.Age' Error:Field validation for 'Age' failed on the 'gte' tag"}
  • 以下 curl 调用
curl  -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Age": 10}'

返回以下响应,因为 10 大于最大值 20。

{"message":"Bad Request Key: 'employee.Age' Error:Field validation for 'Age' failed on the 'lte' tag"}
  • 以下 curl 调用
curl  -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Age": 15}'

返回成功,因为 15 大于 10 且小于 20。

{"message":"Success"}
```*


<!--yml

分类:未分类

日期:2024-10-13 06:18:02

-->

# Go 语言中的变量

> 来源:[`golangbyexample.com/variables-in-golang-complete-guide/`](https://golangbyexample.com/variables-in-golang-complete-guide/)

这是 Go 语言综合教程系列的第六章。请参考此链接了解该系列的其他章节 – [Go 语言综合教程系列](https://golangbyexample.com/golang-comprehensive-tutorial/)

**下一个教程** – [所有基本数据类型](https://golangbyexample.com/all-basic-data-types-golang/)

**上一个教程** – [包和模块 – 第二部分](https://golangbyexample.com/packages-modules-go-second/)

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

目录

**   什么是变量

+   命名约定

+   声明变量

    +   无初始值的单变量声明

    +   带初始值的单变量声明

    +   无初始值的多变量声明

    +   带初始值的多变量声明

    +   声明不同类型的变量

    +   无类型声明或类型推断

    +   短变量声明

+   重要点

+   变量的作用域(局部变量和全局变量)

    +   局部变量

    +   全局变量

+   结论

# **什么是变量**

变量是内存位置的名称。该内存位置可以存储任何类型的值。因此,每个变量都有一个与之相关的类型,这决定了该变量的大小和范围,以及在该变量上定义的操作。

# **命名约定**

+   变量名只能以字母或下划线开头。

+   变量名后可以跟任意数量的字母、数字或下划线。

+   Go 语言区分大小写,因此大写字母和小写字母被视为不同的字符。

+   变量名不能是 Go 语言中的任何关键字。

+   变量名的长度没有限制。

+   但建议变量名应保持在最佳长度。

# **声明变量**

在 GO 中,变量是使用**var**关键字声明的,但还有其他声明变量的方法,稍后在本教程中我们将看到。让我们深入探索声明变量的不同方式。

## **单变量声明,无初始值**

以下是没有分配初始值的单变量声明格式。第一是**var**关键字,第二是**变量名**,第三是**变量类型**。还要注意,当未提供值时,变量会被初始化为该类型的默认值,这也被称为该类型的零值。在 Go 中,**int**的默认值或零值是零。

**var <变量名> <类型>**

见下面的示例,它声明了一个名为**aaa**的**int**类型的变量。

```go
package main

import "fmt"

func main() {
    var aaa int
    fmt.Println(aaa)
}

输出: 将打印 int 的默认值,即零。

0

带初始值的单变量声明

以下是分配初始值的单变量声明格式。与上面相同,唯一不同的是在末尾为变量分配值。

var <变量名> <类型> = <值>

见下面的示例,它声明了一个名为aaaint类型的变量,并给它赋值为8

package main

import "fmt"

func main() {
    var aaa int = 8
    fmt.Println(aaa)
}

输出:

8

没有初始值的多个变量声明

以下是没有分配初始值的多个变量声明的格式。请注意,只有相同类型的变量可以一起声明,类型位于末尾。

var <名称 1>, <名称 2>,….<名称 N> <类型>

见下面的示例

package main

import "fmt"

func main() {
    var aaa, bbb int
    fmt.Println(aaa)
    fmt.Println(bbb)
}

输出: 将打印int的默认值或零值,对aaabbb都是零。

0
0

带初始值的多个变量声明

以下是分配初始值的多个变量声明的格式。请注意,只有相同类型的变量可以一起声明,类型位于末尾。

var <名称 1>, <名称 2>, …..,<名称 N> <类型> = <值 1>, <值 2>, …..,<值 N>

见下面的代码示例。变量aaabbb在一次声明中分别被赋值为 8 和 9。

package main

import "fmt"

func main() {
    var aaa, bbb int = 8, 9
    fmt.Println(aaa)
    fmt.Println(bbb)
}

输出:

8
9

声明不同类型的变量

以下是声明不同类型多个变量的格式。可以在那时为变量分配值,也可以不分配。没有分配值的变量将获得该类型的默认值。在下面的示例中,我们看到三个声明。

package main

import "fmt"

func main() {
    var (
        aaa int
        bbb int    = 8
        ccc string = "a"
    )

    fmt.Println(aaa)
    fmt.Println(bbb)
    fmt.Println(ccc)
}

输出:

0
8
a

没有类型或类型推断的变量声明

变量也可以在不指定类型的情况下声明。

GO 编译器会根据分配给变量的值确定类型。因此,如果变量有初始值,则可以省略类型。这也称为类型推断。以下是这种声明的格式。

var <变量名> = <值>

下面是基本类型 intfloat复数stringboolean字符的类型推断表。这基本上意味着,如果值是整数,则推断出的变量类型将是 int;如果值是浮点数,则推断出的变量类型将是 float64,依此类推。

整数 int
浮点数 float64
复数 complex128
字符串 string
布尔值 bool
字符 int32 或 rune

对于 数组指针结构体 等其他类型,类型推断将根据值进行。让我们看看上述观点的一个工作示例。注意,变量 t 的类型被正确推断为 int,因为赋值给它的是 123,这个值是 int。同样,变量 u 的类型也被正确推断为 string,因为赋值给它的是一个 string

还注意到变量 z 的类型被正确推断为结构体 main.sample

package main

import "fmt"

func main() {
    var t = 123      //Type Inferred will be int
    var u = "circle" //Type Inferred will be string
    var v = 5.6      //Type Inferred will be float64
    var w = true     //Type Inferred will be bool
    var x = 'a'      //Type Inferred will be rune
    var y = 3 + 5i   //Type Inferred will be complex128
    var z = sample{name: "test"}  //Type Inferred will be main.Sample

    fmt.Printf("Type: %T Value: %v\n", t, t)
    fmt.Printf("Type: %T Value: %v\n", u, u)
    fmt.Printf("Type: %T Value: %v\n", v, v)
    fmt.Printf("Type: %T Value: %v\n", w, w)
    fmt.Printf("Type: %T Value: %v\n", x, x)
    fmt.Printf("Type: %T Value: %v\n", y, y)
    fmt.Printf("Type: %T Value: %v\n", z, z)
}

type sample struct {
    name string
}

输出:

Type: int Value: 123
Type: string Value: circle
Type: float64 Value: 5.6
Type: bool Value: true
Type: int32 Value: 97
Type: complex128 Value: (3+5i)
Type: main.sample Value: &{test}

短变量声明

Go 还提供了另一种声明变量的方法,即使用 := 运算符。当使用 := 运算符时,可以省略 var 关键字和类型信息。下面是这种声明的格式。

 <variable_name>:= <value></value></variable_name>

类型推断将如上所述进行。让我们看看一个工作示例。

package main

import "fmt"

func main() {
    t := 123      //Type Inferred will be int
    u := "circle" //Type Inferred will be string
    v := 5.6      //Type Inferred will be float64
    w := true     //Type Inferred will be bool
    x := 'a'      //Type Inferred will be rune
    y := 3 + 5i   //Type Inferred will be complex128
    z := sample{name: "test"}  //Type Inferred will be main.Sample

    fmt.Printf("Type: %T Value: %v\n", t, t)
    fmt.Printf("Type: %T Value: %v\n", u, u)
    fmt.Printf("Type: %T Value: %v\n", v, v)
    fmt.Printf("Type: %T Value: %v\n", w, w)
    fmt.Printf("Type: %T Value: %v\n", x, x)
    fmt.Printf("Type: %T Value: %v\n", y, y)
    fmt.Printf("Type: %T Value: %v\n", z, z)
}

type sample struct {
    name string
}

输出

Type: int Value: 123
Type: string Value: circle
Type: float64 Value: 5.6
Type: bool Value: true
Type: int32 Value: 97
Type: complex128 Value: (3+5i)
Type: main.sample Value: &{test}

关于 := 运算符的一些注意事项。

  • := 运算符仅在函数内部可用,函数外部不允许使用。

  • 一旦使用 := 声明的变量,不能再次使用 := 运算符重新声明。因此,下面的语句将引发编译器错误“左侧没有新变量”。

a := 8
a := 16
  • := 运算符还可以用来在一行中声明多个变量。见下面的例子。
a,b := 1, 2
  • 在多重声明的情况下,如果左侧至少有一个变量是新的,则可以再次使用 := 进行特定变量的声明。见下面的例子。注意 b 又是通过 := 声明的。这只有在至少有一个变量是新的情况下才能实现,这里是变量 c。在这种情况下,它作为变量 b 的赋值。
package main

import "fmt"

func main() {
    a, b := 1, 2
    b, c := 3, 4
    fmt.Println(a, b, c)
}

输出:

1, 3, 4

重要事项

  • 未使用的变量将被报告为编译器错误。Go 编译器不允许任何未使用的变量。这是 Go 的一种优化。同样的规则适用于常量,稍后我们将看到。例如,下面的程序将引发编译器错误。
a declared but not used
package main

func main() {
    var a = 1
}
  • 在内层作用域中声明的变量如果与外层作用域中声明的变量同名,将会遮蔽外层作用域的变量。
package main

import "fmt"

var a = 123

func main() {
    var a = 456
    fmt.Println(a)
}

输出:

456
  • 变量表达式 – 在声明变量时,也可以将表达式或函数调用赋值给它。见下面的例子。

    • 变量 a 是通过表达式 5 + 3 声明的。

    • 变量 b 是通过函数调用 math.Max(4, 5) 声明的,其结果将在运行时赋值给 b。

package main
import (
    "fmt"
    "math"
)
func main() {
    a := 5 + 3
    b := math.Max(4, 5)
    fmt.Println(a)
    fmt.Println(b)
}

输出:

8
5
  • 一旦初始化为特定类型的变量,不能再赋值为不同类型的值。这对于简写声明同样适用。见下面的例子。
package main

func main() {
    var aaa int = 1
    aaa = "atest"

    bbb := 1
    bbb = "btest"
}

输出:

cannot use "atest" (type untyped string) as type int in assignment
cannot use "btest" (type untyped string) as type int in assignment

变量 aaa 被赋予了 int 类型,因此在将字符串类型的值赋给它时,编译器会引发错误。对于变量 bbb,推断的类型是 int,因此在将字符串类型的值赋给它时也会引发编译器错误。

变量的作用域(局部和全局变量)

变量声明可以在包级、函数级或块级进行。变量的作用域定义了该变量可以访问的位置以及变量的生命周期。Golang 变量可以根据作用域分为两类。

  • 局部变量

  • 全局变量

局部变量

  • 局部变量是在块或函数级别定义的变量。

  • 块的一个例子是 for 循环或范围循环等。

  • 这些变量只能在它们的块或函数内部访问。

  • 这些变量仅在声明它们的块或函数结束时存在。之后,它们会被垃圾回收。

见下面的例子。

  • 在 for 循环之后,i 不可用。

  • 同样,aaa 在声明它的函数外部不可用。

因此,下面的程序将引发编译器错误。

undefined: i
undefined: aaa

代码:

package main

import "fmt"

func main() {
    var aaa = "test"
    fmt.Println(aaa)
    for i := 0; i < 3; i++ {
        fmt.Println(i)
    }
    fmt.Println(i)
}

func testLocal() {
    fmt.Println(aaa)
}

全局变量

  • 如果变量在文件顶部声明,并且在任何函数或块的作用域之外,则它将在包内成为全局变量。

  • 如果这个变量名以小写字母开头,则可以在包含该变量定义的包内访问。

  • 如果变量名以大写字母开头,则可以从不同于声明所在包的外部访问。

  • 全局变量在程序的整个生命周期内可用。

例如,在下面的程序中,变量 aaa 将是一个全局变量,在 main 包中可用。它将在 main 包内的任何函数中可用。请注意,由于变量名以小写字母开头,因此该变量名在 main 包之外不可用。

package main

import "fmt"

var aaa = "test"

func main() {
    testGlobal()
}

func testGlobal() {
    fmt.Println(aaa)
}

输出:

test

结论

这就是关于 Golang 中变量的全部内容。希望你喜欢这篇文章。请在评论中分享反馈、改进建议或错误。

**下一教程 – 所有基本数据类型

上一教程 – 包和模块 – 第二部分**

Go(Golang)中的模块供应商依赖

来源:golangbyexample.com/vendor-dependency-go/

目录

  • 概述

  • 示例

概述

如果您想供应您的依赖,可以使用下面的命令来实现。

go mod vendor

示例

让我们看一个示例以更好地理解。首先让我们创建一个模块

go mod init learn

该命令将在同一目录中创建一个go.mod文件。

module learn

go 1.14

由于这是一个空模块,尚未指定任何直接依赖。让我们在go.mod文件中指定一个依赖。

module learn

go 1.14

require github.com/pborman/uuid v1.2.1

在同一目录中创建一个名为uuid.go的文件,内容如下,并使用我们之前在go.mod文件中添加的依赖。

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)
}

让我们运行下面的命令

go mod vendor

这将创建一个 vendor 目录在您的项目目录中。vendor 目录将包含所有直接和间接下载的依赖。您还可以将 vendor 目录检查到您的版本控制系统(VCS)中。这在运行时不需要下载任何依赖,因为它们已经存在于检查到 VCS 的 vendor 文件夹中。

-v标志也可以与 go mod vendor 一起使用。当提供该标志时,命令将打印所有被引入的模块和包。

go mod vendor -v

Go 语言中的访问者设计模式

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

注:如果你有兴趣了解如何在 GO 中实现所有其他设计模式,请参阅此完整参考 – Go 语言中的所有设计模式

目录

** 介绍:

  • UML 图:

  • 映射

  • 示例

  • 完整工作代码:

介绍:

访问者设计模式是一种行为型设计模式,它允许你在不实际修改结构体的情况下,为结构体添加行为。

让我们通过一个例子理解访问者模式。假设你是一个库的维护者,该库有不同形状的结构体,例如

  1. 正方形

  2. 圆形

  3. 三角形

上述每个形状结构体都实现了一个共同的接口shape。你公司中有很多团队在使用你的库。现在假设其中一个团队希望你为形状结构体添加一个新的行为(getArea())。

解决这个问题有很多选项

第一种选择

第一个想到的选项是在形状接口中添加getArea()方法,然后每个形状结构体都可以实现 getArea()方法。这看起来很简单,但有一些问题:

  • 作为库的维护者,你不想通过添加额外的行为来更改库的高测试代码。

  • 使用你的库的团队可能会有更多请求,想要更多行为,比如getNumSides()getMiddleCoordinates()。在这种情况下,你不想不断修改你的库。但你希望其他团队在不实际修改代码的情况下扩展你的库。

第二种选择

第二种选择是请求特性的团队可以自己编写行为逻辑。因此,根据他们喜欢的形状结构体类型,可以使用以下代码

if shape.type == square {
   //Calculate area for squre
} elseif shape.type == circle {
    //Calculate area of triangle 
} elseif shape.type == "triangle" {
    //Calculate area of triangle
} else {
   //Raise error
} 

上面的代码也存在问题,因为你无法充分利用接口,反而进行脆弱的显式类型检查。其次,运行时获取类型可能会影响性能,甚至在某些语言中可能不可行。

第三种选择

第三种选择是使用访问者模式来解决上述问题。我们可以定义一个访问者接口,如下所示

type visitor interface {
   visitForSquare(square)
   visitForCircle(circle)
   visitForTriangle(triangle)
}

函数visitforSquare(square)、visitForCircle(circle)、visitForTriangle(三角形)允许我们分别为正方形、圆形和三角形添加功能。

现在脑海中浮现的问题是,为什么我们不能在访问者接口中使用单一方法 visit(shape)。原因在于 GO 及其他一些语言支持方法重载。因此每个结构体都有不同的方法。

我们在形状接口中添加一个 accept 方法,其签名如下,每个形状结构需要定义此方法。

func accept(v visitor)

但等一下,我们刚提到我们不想修改现有的形状结构。但是使用访客模式时,我们确实需要修改形状结构,但这种修改只需进行一次。在添加任何额外行为(如 getNumSides()getMiddleCoordinates())时,将使用上述相同的 accept(v visitor) 函数,而无需进一步更改形状结构。基本上,形状结构只需修改一次,所有未来对额外行为的请求将通过相同的 accept 函数处理。让我们看看。

square 结构将实现如下的 accept 方法:

func (obj *squre) accept(v visitor){
    v.visitForSquare(obj)
}

同样,circle 和 triangle 也会定义一个如上所述的 accept 函数。

现在请求 getArea() 行为的团队可以简单定义访客接口的具体实现,并在该具体实现中编写面积计算逻辑。

areaCalculator.go

type areaCalculator struct{
    area int
}

func (a *areaCalculator) visitForSquare(s *square){
    //Calculate are for square
}
func (a *areaCalculator) visitForCircle(s *square){
    //Calculate are for circle
}
func (a *areaCalculator) visitForTriangle(s *square){
    //Calculate are for triangle
}

要计算正方形的面积,我们首先创建一个正方形实例,他们可以简单调用。

sq := &square{}
ac := &areaCalculator{}
sq.accept(ac)

同样,其他团队请求 getMiddleCoordinates() 行为可以定义类似于上述的访客接口的另一个具体实现。

middleCoordinates.go

type middleCoordinates struct {
    x int
    y int
}

func (a *middleCoordinates) visitForSquare(s *square) {
    //Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
}

func (a *middleCoordinates) visitForCircle(c *circle) {
    //Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
}

func (a *middleCoordinates) visitForTriangle(t *triangle) {
    //Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
}

UML 图:

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

以下是与我们上面给出的形状结构和 areaCalculator 的实际示例对应的映射 UML 图

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

映射

下表表示 “示例” 中 UML 图演员到实际实现演员的映射

元素 shape.go
具体元素 A square.go
具体元素 B circle.go
具体元素 C rectangle.go
访客 visitor.go
具体访客 1 areaCalculator.go
具体访客 2 middleCoordinates.go
客户端 main.go

示例

shape.go

package main

type shape interface {
    getType() string
    accept(visitor)
}

square.go

package main

type square struct {
    side int
}

func (s *square) accept(v visitor) {
    v.visitForSquare(s)
}

func (s *square) getType() string {
    return "Square"
}

circle.go

package main

type circle struct {
    radius int
}

func (c *circle) accept(v visitor) {
    v.visitForCircle(c)
}

func (c *circle) getType() string {
    return "Circle"
}

rectangle.go

package main

type rectangle struct {
    l int
    b int
}

func (t *rectangle) accept(v visitor) {
    v.visitForrectangle(t)
}

func (t *rectangle) getType() string {
    return "rectangle"
}

visitor.go

package main

type visitor interface {
    visitForSquare(*square)
    visitForCircle(*circle)
    visitForrectangle(*rectangle)
}

areaCalculator.go

package main

import (
    "fmt"
)

type areaCalculator struct {
    area int
}

func (a *areaCalculator) visitForSquare(s *square) {
    //Calculate area for square. After calculating the area assign in to the area instance variable
    fmt.Println("Calculating area for square")
}

func (a *areaCalculator) visitForCircle(s *circle) {
    //Calculate are for circle. After calculating the area assign in to the area instance variable
    fmt.Println("Calculating area for circle")
}

func (a *areaCalculator) visitForrectangle(s *rectangle) {
    //Calculate are for rectangle. After calculating the area assign in to the area instance variable
    fmt.Println("Calculating area for rectangle")
}

middleCoordinates.go

package main

import "fmt"

type middleCoordinates struct {
    x int
    y int
}

func (a *middleCoordinates) visitForSquare(s *square) {
    //Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
    fmt.Println("Calculating middle point coordinates for square")
}

func (a *middleCoordinates) visitForCircle(c *circle) {
    //Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
    fmt.Println("Calculating middle point coordinates for circle")
}

func (a *middleCoordinates) visitForrectangle(t *rectangle) {
    //Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
    fmt.Println("Calculating middle point coordinates for rectangle")
}

main.go

package main

import "fmt"

func main() {
    square := &square{side: 2}
    circle := &circle{radius: 3}
    rectangle := &rectangle{l: 2, b: 3}

    areaCalculator := &areaCalculator{}
    square.accept(areaCalculator)
    circle.accept(areaCalculator)
    rectangle.accept(areaCalculator)

    fmt.Println()
    middleCoordinates := &middleCoordinates{}
    square.accept(middleCoordinates)
    circle.accept(middleCoordinates)
    rectangle.accept(middleCoordinates)
}

输出:

Calculating area for square
Calculating area for circle
Calculating area for rectangle

Calculating middle point coordinates for square
Calculating middle point coordinates for circle
Calculating middle point coordinates for rectangle

完整工作代码:

package main

import "fmt"

type shape interface {
    getType() string
    accept(visitor)
}

type square struct {
    side int
}

func (s *square) accept(v visitor) {
    v.visitForSquare(s)
}

func (s *square) getType() string {
    return "Square"
}

type circle struct {
    radius int
}

func (c *circle) accept(v visitor) {
    v.visitForCircle(c)
}

func (c *circle) getType() string {
    return "Circle"
}

type rectangle struct {
    l int
    b int
}

func (t *rectangle) accept(v visitor) {
    v.visitForrectangle(t)
}

func (t *rectangle) getType() string {
    return "rectangle"
}

type visitor interface {
    visitForSquare(*square)
    visitForCircle(*circle)
    visitForrectangle(*rectangle)
}

type areaCalculator struct {
    area int
}

func (a *areaCalculator) visitForSquare(s *square) {
    //Calculate area for square. After calculating the area assign in to the area instance variable
    fmt.Println("Calculating area for square")
}

func (a *areaCalculator) visitForCircle(s *circle) {
    //Calculate are for circle. After calculating the area assign in to the area instance variable
    fmt.Println("Calculating area for circle")
}

func (a *areaCalculator) visitForrectangle(s *rectangle) {
    //Calculate are for rectangle. After calculating the area assign in to the area instance variable
    fmt.Println("Calculating area for rectangle")
}

type middleCoordinates struct {
    x int
    y int
}

func (a *middleCoordinates) visitForSquare(s *square) {
    //Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
    fmt.Println("Calculating middle point coordinates for square")
}

func (a *middleCoordinates) visitForCircle(c *circle) {
    //Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
    fmt.Println("Calculating middle point coordinates for circle")
}

func (a *middleCoordinates) visitForrectangle(t *rectangle) {
    //Calculate middle point coordinates for square. After calculating the area assign in to the x and y instance variable.
    fmt.Println("Calculating middle point coordinates for rectangle")
}

func main() {
    square := &square{side: 2}
    circle := &circle{radius: 3}
    rectangle := &rectangle{l: 2, b: 3}
    areaCalculator := &areaCalculator{}
    square.accept(areaCalculator)
    circle.accept(areaCalculator)
    rectangle.accept(areaCalculator)

    fmt.Println()
    middleCoordinates := &middleCoordinates{}
    square.accept(middleCoordinates)
    circle.accept(middleCoordinates)
    rectangle.accept(middleCoordinates)
}

输出:

Calculating area for square
Calculating area for circle
Calculating area for rectangle

Calculating middle point coordinates for square
Calculating middle point coordinates for circle
Calculating middle point coordinates for rectangle

等待所有 Go 程序完成在 Golang 中的执行

来源:golangbyexample.com/wait-all-goroutines-go/

golang 的 sync 包提供 WaitGroup 结构,可以用来等待一组 goroutine 完成执行。WaitGroup 提供:

  • 添加方法以设置要等待的 goroutine 数量。

  • 完成方法在每个 goroutine 完成时被调用。

  • 等待方法用于阻塞直到所有 goroutine 完成并调用 完成 方法

让我们看看一个有效的代码

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    go sleep(&wg, time.Second*1)
    go sleep(&wg, time.Second*2)

    wg.Wait()
    fmt.Println("All goroutines finished")
}

func sleep(wg *sync.WaitGroup, t time.Duration) {
    defer wg.Done()
    time.Sleep(t)
    fmt.Println("Finished Execution")
}

输出:

Finished Execution
Finished Execution
All goroutines finished

chmod o-w 命令在 bash 或终端中是什么意思?

来源:golangbyexample.com/chmod-ow-command/

目录

  • 概述

  • 权限组

  • 权限类型

  • 操作定义

  • 示例

概述

在管理文件权限时,图中有三个组件。

权限组

  • 用户 – 简写为 ‘u’

  • – 简写为 ‘g’

  • 其他 – 简写为 ‘o’

  • 所有 – 简写为 ‘a’

权限类型

  • 读取权限 – 简写为 ‘r’

  • 写入权限 – 简写为 ‘w’

  • 执行权限 – 简写为 ‘x’

操作定义

*** + 用于添加权限。

  • 用于移除权限。

  • = 用于设置权限。

所以 o-w 意味着从 其他 用户那里取消 写入 权限。

在我们查看示例之前,让我们先看看当你运行 ls 命令时,文件权限是如何表示的。

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

下面是关于上图的一些要点。

  • 第一个字符表示文件类型。‘-’表示常规文件,‘d’表示目录。

  • 第二到第四个字符表示拥有者的读取、写入和执行权限。

  • 第四到第七个字符表示组的读取、写入和执行权限。

  • 第八到第十个字符表示其他用户的读取、写入和执行权限。

示例

  • 创建一个文件 temp.txt。检查其权限。
ls -all | grep temp.txt
-rw-r--r--    1 root  root      0 Aug  9 14:50 temp.txt

注意 其他 用户仅有 读取 权限。

  • 现在运行命令以给予其他用户写入权限。
chmod o+w temp.txt
ls -all | grep temp.txt
-rw-r--rw-    1 root  root      0 Aug  9 14:50 temp.txt

参见上面的输出。执行权限也被赋予了其他用户。

  • 现在运行命令以取消其他用户的写入权限。
chmod o-w temp.txt
ls -all | grep temp.txt
-rw-r--r--    1 root  root      0 Aug  9 14:50 temp.txt

注意其他用户的写入权限是如何被取消的。

在 Go (Golang)中,go mod tidy 的作用是什么

来源:golangbyexample.com/go-mod-tidy/

目录

  • 概述

  • 示例

概述

此命令将基本上将go.mod文件与源文件中所需的依赖项进行匹配。

  • 下载源文件中所需的所有依赖项,并用该依赖项更新go.mod文件。

  • 移除go.mod文件中在源文件中不需要的所有依赖项。

下面是该命令的使用格式

go mod tidy [-v]

使用-v标志,go mod tidy 将打印从go.mod文件中删除的所有未使用模块的信息(如果有)。

示例

让我们看一个例子。创建一个导入路径为“learn”的模块。

go mod init learn

此命令将在同一目录中创建一个go.mod文件。由于这是一个空模块,因此尚未指定任何直接依赖项。让我们在同一目录中创建一个名为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 mod tidy在源文件中不需要,则会从go.mod文件中移除依赖项。为此,让我们删除上面创建的uuid.go文件。现在运行该命令

go mod tidy -v

它将输出以下内容

unused github.com/pborman/uuid

现在检查go.mod文件的内容。它将如下所示

module learn

go 1.14

require github.com/pborman/uuid v1.2.1

行将被删除,因为它在任何源文件中都不需要。此外,所有github.com/pborman/uuid及其依赖项的条目也将从go.sum文件中删除。

在 Go(Golang)中发生恐慌时会发生什么

来源:golangbyexample.com/what-happens-during-panic-go/

目录

  • 概述

  • 程序

概述

让我们了解当程序发生恐慌时会发生什么。想象一个从main函数调用到f1函数再到f2函数的调用

main->f1->f2

现在假设在函数f2中发生了恐慌,以下是将发生的事件顺序

  • 执行f2将会停止。如果f2中存在延迟函数,则会执行这些延迟函数。控制将返回给调用者,也就是函数f1

  • f1函数的行为将类似于在该函数中发生恐慌,之后调用将返回给调用者,即main函数。请注意,如果中间还有更多函数,则该过程将以类似方式向上延续堆栈

  • main函数的行为也将像是在该函数中发生了恐慌,之后程序将崩溃

  • 一旦程序崩溃,将打印恐慌消息以及此堆栈跟踪

程序

让我们看看一个程序

package main
import "fmt"
func main() {
    f1()
}
func f1() {
    defer fmt.Println("Defer in f1")
    f2()
    fmt.Println("After painc in f1")
}
func f2() {
    defer fmt.Println("Defer in f2")
    panic("Panic Demo")
    fmt.Println("After painc in f2")
}

输出

Defer in f2
Defer in f1
panic: Panic Demo

goroutine 1 [running]:
main.f2()
        main.go:17 +0x95
main.f1()
        main.go:11 +0x96
main.main()
        main.go:6 +0x20
exit status 2

在上述程序中,恐慌发生在f2函数中,如下所示

panic("Panic Demo")

f2中的延迟函数在那之后被调用,并打印以下消息

Defer in f2

请注意,恐慌发生在f2函数中时,其执行停止,因此下面的代码行如果f2将不会被执行

fmt.Println("After painc in f2")

控制返回到f1,如果它有一个延迟函数,延迟函数将被执行,并打印以下消息

Defer in f1

之后控制返回到主函数,然后程序崩溃。输出打印恐慌消息以及从主函数到f1再到f2的整个堆栈跟踪。

为什么在 golang 中关闭响应体

来源:golangbyexample.com/resposne-body-closed-golang/

响应体应在响应完全读取后关闭。这是为了防止连接的资源泄漏。如果响应体没有关闭,则连接将不会被释放,因此无法重用。来自http.Client的官方文档。

来自http.Client的官方文档

golang.org/pkg/net/http/#Client

如果 Body 没有被完全读取到 EOF 并关闭,客户端的底层 RoundTripper(通常是 Transport)可能无法为后续的“保持活动”请求重用与服务器的持久 TCP 连接。

因此,基本上,如果 Body 没有被完全读取并关闭,传输可能无法重用 HTTP/1.x 的“保持活动”TCP 连接。response.Body实现了io.ReadCloser接口。

还要提到,关闭响应体的责任在于调用者。

一些重要的指导原则

  • 使用 defer 方法来关闭响应体。这是为了确保即使在读取和解析响应时发生运行时错误,响应体也能被关闭。

  • 始终先检查错误。仅在错误为 nil 时使用 defer 关闭响应体。一个 nil 错误总是表示一个非 nil 的响应体。如果错误为非 nil,则处理该错误并返回。同时,请注意,关闭一个 nil 的响应体会导致恐慌。

一个简单的示例

resp, err := http.Get("http://google.com/")
if err != nil {
    //Handle the error here.
    return
}
defer resp.Body.Close()
//Read and parse response body here

以上是如何处理 HTTP 错误以及何时关闭响应体的简单示例。请注意在示例中我们使用 defer 函数关闭 resp.Body。并且只有在错误为 nil 时响应体才会被关闭。

Go(Golang)中的通配符匹配或正则表达式匹配程序。

来源:golangbyexample.com/wildcard-matching-golang/

目录

  • 概述

  • 递归解决方案

  • 动态规划解决方案

概述

我们给定一个输入正则表达式和一个输入字符串。正则表达式可以包含两个特殊字符。

  • 星号 ‘*’ – 星号匹配零个或多个字符。

  • 问号 ‘?’ – 它匹配任何字符。

目标是找出给定的输入字符串是否与正则表达式匹配。

例如

Input String: aa
Regex Sring: aa
Output: true

Input String: ab
Regex Sring: a?
Output: true

Input String: aaaa
Regex Sring: *
Output: true

Input String: aa
Regex Sring: a
Output: false

下面是相应的递归解决方案

递归解决方案

在递归解决方案中

  • 如果我们遇到星号 *,那么我们有两种情况。我们忽略模式中的 * 字符,继续处理模式中的下一个字符。另一种情况是我们在输入字符串中移动一个字符,假设 * 至少匹配一个字符。基本上检查 (inputIndex, patternIndex+1)(inputIndex+1, patternIndex) 的匹配。如果它们中的任何一个返回真,则输入字符串与正则表达式匹配。

  • 如果我们遇到问号 ?,那么我们简单地继续进行 (inputIndex+1, patternIndex+1)

  • 如果我们遇到一个简单字符,则我们在输入字符串和模式中都简单地继续,即我们继续进行 (inputIndex+1, patternIndex+1)

这是程序

package main

import "fmt"

func main() {
	output := isMatch("aa", "aa")
	fmt.Println(output)

	output = isMatch("aaaa", "*")
	fmt.Println(output)

	output = isMatch("ab", "a?")
	fmt.Println(output)

	output = isMatch("adceb", "*a*b")
	fmt.Println(output)

	output = isMatch("aa", "a")
	fmt.Println(output)

	output = isMatch("mississippi", "m??*ss*?i*pi")
	fmt.Println(output)

	output = isMatch("acdcb", "a*c?b")
	fmt.Println(output)
}

func isMatch(s string, p string) bool {
	runeInputArray := []rune(s)
	runePatternArray := []rune(p)
	if len(runeInputArray) > 0 && len(runePatternArray) > 0 {
		if runePatternArray[len(runePatternArray)-1] != '*' && runePatternArray[len(runePatternArray)-1] != '?' && runeInputArray[len(runeInputArray)-1] != runePatternArray[len(runePatternArray)-1] {
			return false
		}
	}
	return isMatchUtil([]rune(s), []rune(p), 0, 0, len([]rune(s)), len([]rune(p)))
}

func isMatchUtil(input, pattern []rune, inputIndex, patternIndex int, inputLength, patternLength int) bool {

	if inputIndex == inputLength && patternIndex == patternLength {
		return true
	} else if patternIndex == patternLength {
		return false
	} else if inputIndex == inputLength {
		if pattern[patternIndex] == '*' && restPatternStar(pattern, patternIndex+1, patternLength) {
			return true
		} else {
			return false
		}
	}

	if pattern[patternIndex] == '*' {
		return isMatchUtil(input, pattern, inputIndex, patternIndex+1, inputLength, patternLength) ||
			isMatchUtil(input, pattern, inputIndex+1, patternIndex, inputLength, patternLength)

	}

	if pattern[patternIndex] == '?' {
		return isMatchUtil(input, pattern, inputIndex+1, patternIndex+1, inputLength, patternLength)
	}

	if inputIndex < inputLength {
		if input[inputIndex] == pattern[patternIndex] {
			return isMatchUtil(input, pattern, inputIndex+1, patternIndex+1, inputLength, patternLength)
		} else {
			return false
		}
	}

	return false

}

func restPatternStar(pattern []rune, patternIndex int, patternLength int) bool {
	for patternIndex < patternLength {
		if pattern[patternIndex] != '*' {
			return false
		}
		patternIndex++
	}

	return true

}

输出

true
true
true
true
false
false
false

动态规划解决方案

上述程序不是优化解决方案,因为子问题被重复求解。这个问题也可以用动态规划解决。

创建一个名为 isMatchingMatrix 的二维矩阵,其中

isMatchingMatrix[i][j] 将为真,如果输入字符串的前 i 个字符与模式的前 j 个字符匹配。

If both input and pattern is empty
isMatchingMatrix[0][0] = true

If pattern is empty 
isMatchingMatrix[i][0] = fasle

If the input string is empty 
isMatchingMatrix[0][j] = isMatchingMatrix[0][j - 1] if pattern[j – 1] is '*'

下面是相应的程序。

package main

import "fmt"

func main() {
	output := isMatch("aa", "aa")
	fmt.Println(output)

	output = isMatch("aaaa", "*")
	fmt.Println(output)

	output = isMatch("ab", "a?")
	fmt.Println(output)

	output = isMatch("adceb", "*a*b")
	fmt.Println(output)

	output = isMatch("aa", "a")
	fmt.Println(output)

	output = isMatch("mississippi", "m??*ss*?i*pi")
	fmt.Println(output)

	output = isMatch("acdcb", "a*c?b")
	fmt.Println(output)
}

func isMatch(s string, p string) bool {

	runeInput := []rune(s)
	runePattern := []rune(p)

	lenInput := len(runeInput)
	lenPattern := len(runePattern)

	isMatchingMatrix := make([][]bool, lenInput+1)

	for i := range isMatchingMatrix {
		isMatchingMatrix[i] = make([]bool, lenPattern+1)
	}

	isMatchingMatrix[0][0] = true
	for i := 1; i < lenInput; i++ {
		isMatchingMatrix[i][0] = false
	}

	if lenPattern > 0 {
		if runePattern[0] == '*' {
			isMatchingMatrix[0][1] = true
		}
	}

	for j := 2; j <= lenPattern; j++ {
		if runePattern[j-1] == '*' {
			isMatchingMatrix[0][j] = isMatchingMatrix[0][j-1]
		}

	}

	for i := 1; i <= lenInput; i++ {
		for j := 1; j <= lenPattern; j++ {

			if runePattern[j-1] == '*' {
				isMatchingMatrix[i][j] = isMatchingMatrix[i-1][j] || isMatchingMatrix[i][j-1]
			}

			if runePattern[j-1] == '?' || runeInput[i-1] == runePattern[j-1] {
				isMatchingMatrix[i][j] = isMatchingMatrix[i-1][j-1]
			}
		}
	}

	return isMatchingMatrix[lenInput][lenPattern]
}

输出

true
true
true
true
false
false
false

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

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

在 Go(Golang)中错误的包装和解包

来源:golangbyexample.com/wrapping-and-unwrapping-error-golang/

目录

** 错误的包装

  • 解包错误

错误的包装

在 Go 中,错误可以包装另一个错误。错误的包装意味着什么?它意味着创建一个错误层次结构,其中特定实例的错误包装另一个错误,而该特定实例本身也可以被包装在另一个错误中。下面是包装错误的语法。

e := fmt.Errorf("... %w ...", ..., err, ...)

%w指令用于包装错误。fmt.Errorf应仅调用一个%w 指令。让我们看一个例子。

package main

import (
	"fmt"
)

type errorOne struct{}

func (e errorOne) Error() string {
	return "Error One happended"
}

func main() {

	e1 := errorOne{}

	e2 := fmt.Errorf("E2: %w", e1)

	e3 := fmt.Errorf("E3: %w", e2)

	fmt.Println(e2)

	fmt.Println(e3)

}

输出

E2: Error One happended
E3: E2: Error One happended

在上面的程序中,我们创建了一个结构体errorOne,它有一个Error方法,因此实现了error接口。然后我们创建了一个名为e1errorOne结构实例。接着我们将该实例e1包装成另一个错误e2,如下所示。

e2 := fmt.Errorf("E2: %w", e1)

然后我们将e2包装到e3中,如下所示。

e3 := fmt.Errorf("E3: %w", e2)

因此,我们创建了一个错误层次结构,其中e3包装e2e2又包装e1。因此,e3也间接地包装了e1。当我们打印e2时,它也会打印e1的错误并给出输出。

E2: Error One happended

当我们打印e3时,它会打印e2e1的错误并给出输出。

E3: E2: Error One happended

现在脑海中出现的问题是,包装错误的用例是什么。为了理解这一点,让我们看一个例子。

package main

import (
	"fmt"
)

type notPositive struct {
	num int
}

func (e notPositive) Error() string {
	return fmt.Sprintf("checkPositive: Given number %d is not a positive number", e.num)
}

type notEven struct {
	num int
}

func (e notEven) Error() string {
	return fmt.Sprintf("checkEven: Given number %d is not an even number", e.num)
}

func checkPositive(num int) error {
	if num < 0 {
		return notPositive{num: num}
	}
	return nil
}

func checkEven(num int) error {
	if num%2 == 1 {
		return notEven{num: num}
	}
	return nil
}

func checkPostiveAndEven(num int) error {
	if num > 100 {
		return fmt.Errorf("checkPostiveAndEven: Number %d is greater than 100", num)
	}

	err := checkPositive(num)
	if err != nil {
		return err
	}

	err = checkEven(num)
	if err != nil {
		return err
	}

	return nil
}

func main() {
	num := 3
	err := checkPostiveAndEven(num)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Givennnumber is positive and even")
	}

}

输出

checkEven: Given number 3 is not an even number

在上面的程序中,我们有一个checkPostiveAndEven函数,用于检查一个数字是否为偶数和正数。它依次调用checkEven函数来检查该数字是否为偶数。然后它调用checkPositive函数来检查该数字是否为正数。如果一个数字既不是偶数也不是正数,就会引发错误。

在上面的程序中,不可能知道错误的堆栈跟踪。我们知道这个错误来自checkEven函数的输出。但是调用checkEven函数的具体函数从错误中并不清楚。这就是包装错误的意义所在。当项目变大并且有很多函数相互调用时,这就变得更加有用。让我们通过包装错误来重写程序。

package main

import (
	"fmt"
)

type notPositive struct {
	num int
}

func (e notPositive) Error() string {
	return fmt.Sprintf("checkPositive: Given number %d is not a positive number", e.num)
}

type notEven struct {
	num int
}

func (e notEven) Error() string {
	return fmt.Sprintf("checkEven: Given number %d is not an even number", e.num)
}

func checkPositive(num int) error {
	if num < 0 {
		return notPositive{num: num}
	}
	return nil
}

func checkEven(num int) error {
	if num%2 == 1 {
		return notEven{num: num}
	}
	return nil
}

func checkPostiveAndEven(num int) error {
	if num > 100 {
		return fmt.Errorf("checkPostiveAndEven: Number %d is greater than 100", num)
	}

	err := checkPositive(num)
	if err != nil {
		return fmt.Errorf("checkPostiveAndEven: %w", err)
	}

	err = checkEven(num)
	if err != nil {
		return fmt.Errorf("checkPostiveAndEven: %w", err)
	}

	return nil
}

func main() {
	num := 3
	err := checkPostiveAndEven(num)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Given number is positive and even")
	}

}

输出

checkPostiveAndEven: checkEven: Given number 3 is not an even number

上面的程序与之前的程序相同,只是在checkPostiveAndEven函数中,我们像下面这样包装错误。

fmt.Errorf("checkPostiveAndEven: %w", err)

因此,输出更加清晰,错误信息也更加丰富。输出清楚地提到调用的顺序。

checkPostiveAndEven: checkEven: Given number 3 is not an even number

解包错误

在上面的部分,我们研究了如何包装错误。也可以解包错误。解包函数来自errors包,可以用来解包错误。下面是该函数的语法。

func Unwrap(err error) error

如果err封装了另一个错误,则将返回封装的错误,否则Unwrap函数将返回 nil。

让我们看看一个程序来说明这一点

import (
    "errors"
    "fmt"
)
type errorOne struct{}
func (e errorOne) Error() string {
    return "Error One happended"
}
func main() {
    e1 := errorOne{}
    e2 := fmt.Errorf("E2: %w", e1)
    e3 := fmt.Errorf("E3: %w", e2)
    fmt.Println(errors.Unwrap(e3))
    fmt.Println(errors.Unwrap(e2))
    fmt.Println(errors.Unwrap(e1))
}

输出

E2: Error One happended
Error One happended 

在上面的程序中,我们创建了一个结构体errorOne,它有一个Error方法,因此它实现了error接口。然后我们创建了一个名为e1errorOne结构体实例。接着我们将该实例e1封装成另一个错误e2,如下所示。

e2 := fmt.Errorf("E2: %w", e1)

然后我们将e2封装成e3,如下所示。

e3 := fmt.Errorf("E3: %w", e2)

因此

fmt.Println(errors.Unwrap(e3))

将返回封装的错误e2,因为e3封装了e2,输出将是

E2: Error One happended

另外,

fmt.Println(errors.Unwrap(e2))

将返回封装的错误e1,因为e2进一步封装了e1,输出将是

Error One happened

fmt.Println(errors.Unwrap(e1))

将输出 nil,因为e1并不封装任何错误

{nil}

在 Go (Golang)中编写多行字符串

来源:golangbyexample.com/multiline-string-go/

反引号(`)可以用于在 Golang 中编写多行字符串。请注意,用反引号编码的字符串是原始字面量字符串,不会遵循任何类型的转义。因此,在使用反引号时,\n 和\t 被视为字符串字面量。

工作代码:

package main

import "fmt"

func main() {
    multiline := `This is 
a multiline 
string`

    fmt.Println(multiline)
}

输出

This is 
a multiline 
string
posted @ 2024-10-19 08:37  绝不原创的飞龙  阅读(0)  评论(0编辑  收藏  举报