Skip to content

Latest commit

 

History

History
1477 lines (1126 loc) · 45.5 KB

File metadata and controls

1477 lines (1126 loc) · 45.5 KB

三、数据转换与组合

理解围棋的打字系统是掌握围棋开发各个层次的关键步骤。本章将展示一些在数据类型之间转换、使用非常大的数字、使用货币、使用不同类型的编码和解码(包括 Base64 和gob)以及使用闭包创建自定义集合的示例。本章将介绍以下配方:

  • 转换数据类型和接口转换
  • 使用 math 和 math/big 处理数字数据类型
  • 货币兑换和浮动 64 注意事项
  • 使用指针和 SQL 空类型进行编码和解码
  • Go 数据的编码和解码
  • Go 中的结构标记与基本反射
  • 使用闭包实现集合

技术要求

为了继续本章中的所有配方,请按照以下步骤配置您的环境:

  1. 在您的操作系统上下载并安装 Go 1.12.6 或更高版本 https://golang.org/doc/install

  2. 打开终端/控制台应用,创建并导航到项目目录,如~/projects/go-programming-cookbook,所有代码将从此目录运行和修改。

  3. 将最新代码克隆到~/projects/go-programming-cookbook-original。如果愿意,您可以从此目录进行操作,而不是手动键入示例:

$ git clone git@github.com:PacktPublishing/Go-Programming-Cookbook-Second-Edition.git go-programming-cookbook-original

转换数据类型和接口转换

当用于将数据从一种类型转换为另一种类型时,Go 通常非常灵活。一个类型可以继承另一个类型,如下所示:

type A int

我们始终可以回溯到继承的类型,如下所示:

var a A = 1
fmt.Println(int(a))

还有一些方便的函数,可以使用强制转换在数字之间进行转换,使用fmt.Sprintstrconv在字符串和其他类型之间进行转换,以及使用反射在接口和类型之间进行转换。本食谱将探讨一些基本的转换,这些转换将贯穿本书。

怎么做。。。

以下步骤介绍了如何编写和运行应用:

  1. 从终端/控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter3/dataconv的新目录。
  2. 导航到此目录。
  3. 运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/dataconv 

您应该会看到一个名为go.mod的文件,其中包含以下内容:

module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/dataconv    
  1. 复制~/projects/go-programming-cookbook-original/chapter3/dataconv中的测试,或者将其用作编写自己代码的练习!
  2. 创建一个名为dataconv.go的文件,其内容如下:
        package dataconv

        import "fmt"

        // ShowConv demonstrates some type conversion
        func ShowConv() {
            // int
            var a = 24

            // float 64
            var b = 2.0

            // convert the int to a float64 for this calculation
            c := float64(a) * b
            fmt.Println(c)

            // fmt.Sprintf is a good way to convert to strings
            precision := fmt.Sprintf("%.2f", b)

            // print the value and the type
            fmt.Printf("%s - %T\n", precision, precision)
        }
  1. 创建一个名为strconv.go的文件,其内容如下:
        package dataconv

        import (
            "fmt"
            "strconv"
        )

        // Strconv demonstrates some strconv
        // functions
        func Strconv() error {
            //strconv is a good way to convert to and from strings
            s := "1234"
            // we can specify the base (10) and precision
            // 64 bit
            res, err := strconv.ParseInt(s, 10, 64)
            if err != nil {
                return err
          }

          fmt.Println(res)

          // lets try hex
          res, err = strconv.ParseInt("FF", 16, 64)
          if err != nil {
              return err
          }

          fmt.Println(res)

          // we can do other useful things like:
          val, err := strconv.ParseBool("true")
          if err != nil {
              return err
          }

          fmt.Println(val)

          return nil
        }
  1. 创建一个名为interfaces.go的文件,其内容如下:
        package dataconv

        import "fmt"

        // CheckType will print based on the
        // interface type
        func CheckType(s interface{}) {
            switch s.(type) {
            case string:
                fmt.Println("It's a string!")
            case int:
                fmt.Println("It's an int!")
            default:
                fmt.Println("not sure what it is...")
            }
        }

        // Interfaces demonstrates casting
        // from anonymous interfaces to types
        func Interfaces() {
            CheckType("test")
            CheckType(1)
            CheckType(false)

            var i interface{}
            i = "test"

            // manually check an interface
            if val, ok := i.(string); ok {
                fmt.Println("val is", val)
            }

            // this one should fail
            if _, ok := i.(int); !ok {
                fmt.Println("uh oh! glad we handled this")
            }
        }
  1. 创建一个名为example的新目录并导航到它。
  2. 创建一个名为main.go的文件,其内容如下:
        package main

        import "github.com/PacktPublishing/
                Go-Programming-Cookbook-Second-Edition/
                chapter3/dataconv"

        func main() {
            dataconv.ShowConv()
            if err := dataconv.Strconv(); err != nil {
                panic(err)
            }
            dataconv.Interfaces()
        }
  1. 运行go run main.go,也可以运行以下命令:
$ go build $ ./example

您应该看到以下输出:

$ go run main.go
48
2.00 - string
1234
255
true
It's a string!
It's an int!
not sure what it is...
val is test
uh oh! glad we handled this
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

此配方演示了如何通过使用strconv包和接口反射将类型包装成新类型来在类型之间转换。这些方法允许 Go 开发人员在各种抽象 Go 类型之间快速转换。前两种方法在编译期间会返回错误,但是,在运行时之前,可能无法找到接口反射中的错误。如果您对不受支持的类型进行了错误的反射,则您的程序将死机。在类型之间切换是一种泛化的方法,在本配方中也进行了演示。

转换对于像math这样只在float64上运行的包来说非常重要。

使用 math 和 math/big 处理数字数据类型

mathmath/big包侧重于向 Go 语言公开更复杂的数学运算,例如PowSqrtCos。除非函数另有说明,否则math包本身主要在float64上运行。math/big包适用于太大而无法用 64 位值表示的数字。本食谱将展示math包的一些基本用法,并演示如何将math/big用于斐波那契序列。

怎么做。。。

以下步骤介绍了如何编写和运行应用:

  1. 从终端/控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter3/math的新目录。

  2. 导航到此目录。

  3. 运行以下命令:

$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/math 

您应该看到一个名为go.mod的文件,其中包含以下内容:

module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/math    
  1. 复制~/projects/go-programming-cookbook-original/chapter3/math中的测试,或者将其用作编写自己代码的练习!
  2. 创建一个名为fib.go的文件,其内容如下:
        package math

        import "math/big"

        // global to memoize fib
        var memoize map[int]*big.Int

        func init() {
            // initialize the map
            memoize = make(map[int]*big.Int)
        }

        // Fib prints the nth digit of the fibonacci sequence
        // it will return 1 for anything < 0 as well...
        // it's calculated recursively and use big.Int since
        // int64 will quickly overflow
        func Fib(n int) *big.Int {
            if n < 0 {
                return big.NewInt(1)
            }

            // base case
            if n < 2 {
                memoize[n] = big.NewInt(1)
            }

            // check if we stored it before
            // if so return with no calculation
            if val, ok := memoize[n]; ok {
                return val
            }

            // initialize map then add previous 2 fib values
            memoize[n] = big.NewInt(0)
            memoize[n].Add(memoize[n], Fib(n-1))
            memoize[n].Add(memoize[n], Fib(n-2))

            // return result
            return memoize[n]
        }
  1. 创建一个名为math.go的文件,其内容如下:
package math

import (
  "fmt"
  "math"
)

// Examples demonstrates some of the functions
// in the math package
func Examples() {
  //sqrt Examples
  i := 25

  // i is an int, so convert
  result := math.Sqrt(float64(i))

  // sqrt of 25 == 5
  fmt.Println(result)

  // ceil rounds up
  result = math.Ceil(9.5)
  fmt.Println(result)

  // floor rounds down
  result = math.Floor(9.5)
  fmt.Println(result)

  // math also stores some consts:
  fmt.Println("Pi:", math.Pi, "E:", math.E)
}
  1. 创建一个名为example的新目录并导航到它。
  2. 创建一个名为main.go的文件,其内容如下:
        package main

        import (
            "fmt"

            "github.com/PacktPublishing/
             Go-Programming-Cookbook-Second-Edition/
             chapter3/math"
        )

        func main() {
            math.Examples()

            for i := 0; i < 10; i++ {
                fmt.Printf("%v ", math.Fib(i))
            }
            fmt.Println()
        }
  1. 运行go run main.go,您还可以运行以下操作:
$ go build $ ./example

您应该看到以下输出:

$ go run main.go
5
10
9
Pi: 3.141592653589793 E: 2.718281828459045
1 1 2 3 5 8 13 21 34 55
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

math软件包使得在 Go 中执行复杂的数学运算成为可能。此配方应与此软件包结合使用,以执行复杂的浮点运算,并根据需要在类型之间进行转换。值得注意的是,即使使用float64,某些浮点数仍可能存在舍入错误;下面的配方演示了一些处理此问题的技术。

math/big部分展示了递归斐波那契序列。如果将main.go修改为循环远远超过 10,则如果使用int64而不是big.Int,则会很快溢出int64big.Int包还具有帮助器方法,可以在大类型之间转换为其他类型。

货币兑换和浮动 64 注意事项

使用货币总是一个棘手的过程。将货币表示为float64很有诱惑力,但这可能会导致在计算时出现一些相当棘手(错误)的舍入错误。出于这个原因,最好用美分来表示货币,并将数字存储为一个int64实例。

从表单、命令行或其他来源收集用户输入时,货币通常以美元形式表示。因此,最好将其视为字符串,并将该字符串直接转换为美分,而无需进行浮点转换。此配方将介绍如何将货币的字符串表示形式转换为int64(美分)实例,然后再转换回来。

怎么做。。。

以下步骤介绍了如何编写和运行应用:

  1. 从终端/控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter3/currency的新目录。

  2. 导航到此目录。

  3. 运行以下命令:

$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/currency 

您应该看到一个名为go.mod的文件,其中包含以下内容:

module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/currency    
  1. 复制~/projects/go-programming-cookbook-original/chapter3/currency中的测试,或者将其用作编写自己代码的练习!
  2. 创建一个名为dollars.go的文件,其内容如下:
        package currency

        import (
            "errors"
            "strconv"
            "strings"
        )

        // ConvertStringDollarsToPennies takes a dollar amount
        // as a string, i.e. 1.00, 55.12 etc and converts it
        // into an int64
        func ConvertStringDollarsToPennies(amount string) (int64, 
        error) {
            // check if amount can convert to a valid float
            _, err := strconv.ParseFloat(amount, 64)
            if err != nil {
                return 0, err
            }

            // split the value on "."
            groups := strings.Split(amount, ".")

            // if there is no . result will still be
            // captured here
            result := groups[0]

            // base string
            r := ""

            // handle the data after the "."
            if len(groups) == 2 {
                if len(groups[1]) != 2 {
                    return 0, errors.New("invalid cents")
                }
                r = groups[1]
            }

            // pad with 0, this will be
            // 2 0's if there was no .
            for len(r) < 2 {
                r += "0"
            }

            result += r

            // convert it to an int
            return strconv.ParseInt(result, 10, 64)
        }
  1. 创建一个名为pennies.go的文件,其内容如下:
        package currency

        import (
            "strconv"
        )

        // ConvertPenniesToDollarString takes a penny amount as 
        // an int64 and returns a dollar string representation
        func ConvertPenniesToDollarString(amount int64) string {
            // parse the pennies as a base 10 int
            result := strconv.FormatInt(amount, 10)

            // check if negative, will set it back later
            negative := false
            if result[0] == '-' {
                result = result[1:]
                negative = true
            }

            // left pad with 0 if we're passed in value < 100
            for len(result) < 3 {
                result = "0" + result
            }
            length := len(result)

            // add in the decimal
            result = result[0:length-2] + "." + result[length-2:]

            // from the negative we stored earlier!
            if negative {
                result = "-" + result
            }

            return result
        }
  1. 创建一个名为example的新目录并导航到它。
  2. 创建一个名为main.go的文件,其内容如下:
        package main

        import (
            "fmt"

            "github.com/PacktPublishing/
             Go-Programming-Cookbook-Second-Edition/
             chapter3/currency"
        )

        func main() {
            // start with our user input
            // of fifteen dollars and 93 cents
            userInput := "15.93"

            pennies, err := 
            currency.ConvertStringDollarsToPennies(userInput)
            if err != nil {
                panic(err)
            }

            fmt.Printf("User input converted to %d pennies\n", pennies)

            // adding 15 cents
            pennies += 15

            dollars := currency.ConvertPenniesToDollarString(pennies)

            fmt.Printf("Added 15 cents, new values is %s dollars\n", 
            dollars)
        }
  1. 运行go run main.go,您还可以运行以下操作:
$ go build $ ./example

您应该看到以下输出:

$ go run main.go
User input converted to 1593 pennies
Added 15 cents, new values is 16.08 dollars
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

此方法使用strconvstrings包在字符串格式的美元和int64中的便士之间转换货币。它没有转换为float64类型,这可能会导致舍入错误,并且只在验证时这样做。

strconv.ParseIntstrconv.FormatInt函数对于在int64和字符串之间进行转换非常有用。我们还利用了这样一个事实,即 Go 字符串可以根据需要很容易地追加和切片。

使用指针和 SQL 空类型进行编码和解码

在 Go 中对对象进行编码或解码时,未显式设置的类型将设置为其默认值。例如,字符串将默认为空字符串(""),整数将默认为0。通常情况下,这很好,除非0对正在使用用户输入或返回用户输入的 API 或服务意味着什么。

此外,如果您使用struct标记,如json omitempty,则0值将被忽略,即使它们有效。另一个例子是Null,它从 SQL 返回。对于Int,什么值最能代表Null?本食谱将探讨开发人员处理此问题的一些方法。

怎么做。。。

以下步骤介绍了如何编写和运行应用:

  1. 从终端/控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter3/nulls的新目录。
  2. 导航到此目录。
  3. 运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/nulls 

您应该看到一个名为go.mod的文件,其中包含以下内容:

module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/nulls    
  1. 复制~/projects/go-programming-cookbook-original/chapter3/nulls中的测试,或者将其用作编写自己代码的练习!
  2. 创建一个名为base.go的文件,其内容如下:
        package nulls

        import (
            "encoding/json"
            "fmt"
        )

        // json that has name but not age
        const (
            jsonBlob = `{"name": "Aaron"}`
            fulljsonBlob = `{"name":"Aaron", "age":0}`
        )

        // Example is a basic struct with age
        // and name fields
        type Example struct {
            Age int `json:"age,omitempty"`
            Name string `json:"name"`
        }

        // BaseEncoding shows encoding and
        // decoding with normal types
        func BaseEncoding() error {
            e := Example{}

            // note that no age = 0 age
            if err := json.Unmarshal([]byte(jsonBlob), &e); err != nil 
            {
                return err
            }
            fmt.Printf("Regular Unmarshal, no age: %+v\n", e)

            value, err := json.Marshal(&e)
            if err != nil {
                return err
            }
            fmt.Println("Regular Marshal, with no age:", string(value))

            if err := json.Unmarshal([]byte(fulljsonBlob), &e);
            err != nil {
                return err
            }
            fmt.Printf("Regular Unmarshal, with age = 0: %+v\n", e)

            value, err = json.Marshal(&e)
            if err != nil {
                return err
            }
            fmt.Println("Regular Marshal, with age = 0:", 
            string(value))

            return nil
        }
  1. 创建一个名为pointer.go的文件,其内容如下:
        package nulls

        import (
            "encoding/json"
            "fmt"
        )

        // ExamplePointer is the same, but
        // uses a *Int
        type ExamplePointer struct {
            Age *int `json:"age,omitempty"`
            Name string `json:"name"`
        }

        // PointerEncoding shows methods for
        // dealing with nil/omitted values
        func PointerEncoding() error {

            // note that no age = nil age
            e := ExamplePointer{}
            if err := json.Unmarshal([]byte(jsonBlob), &e); err != nil 
            {
                return err
            }
            fmt.Printf("Pointer Unmarshal, no age: %+v\n", e)

            value, err := json.Marshal(&e)
            if err != nil {
                return err
            }
            fmt.Println("Pointer Marshal, with no age:", string(value))

            if err := json.Unmarshal([]byte(fulljsonBlob), &e);
            err != nil {
                return err
            }
            fmt.Printf("Pointer Unmarshal, with age = 0: %+v\n", e)

            value, err = json.Marshal(&e)
            if err != nil {
                return err
            }
            fmt.Println("Pointer Marshal, with age = 0:",
            string(value))

            return nil
        }
  1. 创建一个名为nullencoding.go的文件,其内容如下:
        package nulls

        import (
            "database/sql"
            "encoding/json"
            "fmt"
        )

        type nullInt64 sql.NullInt64

        // ExampleNullInt is the same, but
        // uses a sql.NullInt64
        type ExampleNullInt struct {
            Age *nullInt64 `json:"age,omitempty"`
            Name string `json:"name"`
        }

        func (v *nullInt64) MarshalJSON() ([]byte, error) {
            if v.Valid {
                return json.Marshal(v.Int64)
            }
            return json.Marshal(nil)
        }

        func (v *nullInt64) UnmarshalJSON(b []byte) error {
            v.Valid = false
            if b != nil {
                v.Valid = true
                return json.Unmarshal(b, &v.Int64)
            }
            return nil
        }

        // NullEncoding shows an alternative method
        // for dealing with nil/omitted values
        func NullEncoding() error {
            e := ExampleNullInt{}

            // note that no means an invalid value
            if err := json.Unmarshal([]byte(jsonBlob), &e); err != nil 
            {
                return err
            }
            fmt.Printf("nullInt64 Unmarshal, no age: %+v\n", e)

            value, err := json.Marshal(&e)
            if err != nil {
                return err
            }
            fmt.Println("nullInt64 Marshal, with no age:",
            string(value))

            if err := json.Unmarshal([]byte(fulljsonBlob), &e);
            err != nil {
                return err
            }
            fmt.Printf("nullInt64 Unmarshal, with age = 0: %+v\n", e)

            value, err = json.Marshal(&e)
            if err != nil {
                return err
            }
            fmt.Println("nullInt64 Marshal, with age = 0:",
            string(value))

            return nil
        }
  1. 创建一个名为example的新目录并导航到它。
  2. 创建一个名为main.go的文件,其内容如下:
        package main

        import (
            "fmt"

            "github.com/PacktPublishing/
             Go-Programming-Cookbook-Second-Edition/
             chapter3/nulls"
        )

        func main() {
            if err := nulls.BaseEncoding(); err != nil {
                panic(err)
            }
            fmt.Println()

            if err := nulls.PointerEncoding(); err != nil {
                panic(err)
            }
            fmt.Println()

            if err := nulls.NullEncoding(); err != nil {
                panic(err)
            }
        }
  1. 运行go run main.go,您还可以运行以下操作:
$ go build $ ./example

您应该看到以下输出:

$ go run main.go
Regular Unmarshal, no age: {Age:0 Name:Aaron}
Regular Marshal, with no age: {"name":"Aaron"}
Regular Unmarshal, with age = 0: {Age:0 Name:Aaron}
Regular Marshal, with age = 0: {"name":"Aaron"}

Pointer Unmarshal, no age: {Age:<nil> Name:Aaron}
Pointer Marshal, with no age: {"name":"Aaron"}
Pointer Unmarshal, with age = 0: {Age:0xc42000a610 Name:Aaron}
Pointer Marshal, with age = 0: {"age":0,"name":"Aaron"}

nullInt64 Unmarshal, no age: {Age:<nil> Name:Aaron}
nullInt64 Marshal, with no age: {"name":"Aaron"}
nullInt64 Unmarshal, with age = 0: {Age:0xc42000a750 
Name:Aaron}
nullInt64 Marshal, with age = 0: {"age":0,"name":"Aaron"}
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

在封送和解封送时,从值切换到指针是表示空值的一种快速方法。设置这些值可能有点棘手,因为您无法将它们直接分配给指针-- *a := 1,但除此之外,这是一种灵活的处理方法。

该配方还展示了使用sql.NullInt64类型的替代方法。这通常与 SQL 一起使用,如果返回除Null之外的任何内容,则设置valid;否则设置Null。我们添加了一个MarshalJSON方法和一个UnmarshallJSON方法,以允许此类型与JSON包交互,并且我们选择使用指针,以便omitempty可以继续按预期工作。

Go 数据的编码和解码

除了 JSON、TOML 和 YAML 之外,Go 还提供了许多其他编码类型。它们主要用于在 Go 进程之间传输数据,例如有线协议和 RPC,或者在某些字符格式受到限制的情况下。

本食谱将探索如何对gob格式和base64进行编码和解码。后面的章节将探讨 GRPC 等协议。

怎么做。。。

以下步骤介绍了如何编写和运行应用:

  1. 从终端/控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter3/encoding的新目录。

  2. 导航到此目录。

  3. 运行以下命令:

$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/encoding 

您应该看到一个名为go.mod的文件,其中包含以下内容:

module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/encoding    
  1. 复制~/projects/go-programming-cookbook-original/chapter3/encoding中的测试,或者将其用作编写自己代码的练习!
  2. 创建一个名为gob.go的文件,其内容如下:
        package encoding

        import (
            "bytes"
            "encoding/gob"
            "fmt"
        )

        // pos stores the x, y position
        // for Object
        type pos struct {
            X      int
            Y      int
            Object string
        }

        // GobExample demonstrates using
        // the gob package
        func GobExample() error {
            buffer := bytes.Buffer{}

            p := pos{
                X:      10,
                Y:      15,
                Object: "wrench",
            }

            // note that if p was an interface
            // we'd have to call gob.Register first

            e := gob.NewEncoder(&buffer)
            if err := e.Encode(&p); err != nil {
                return err
            }

            // note this is a binary format so it wont print well
            fmt.Println("Gob Encoded valued length: ", 
            len(buffer.Bytes()))

            p2 := pos{}
            d := gob.NewDecoder(&buffer)
            if err := d.Decode(&p2); err != nil {
                return err
            }

            fmt.Println("Gob Decode value: ", p2)

            return nil
        }
  1. 创建一个名为base64.go的文件,其内容如下:
        package encoding

        import (
            "bytes"
            "encoding/base64"
            "fmt"
            "io/ioutil"
        )

        // Base64Example demonstrates using
        // the base64 package
        func Base64Example() error {
            // base64 is useful for cases where
            // you can't support binary formats
            // it operates on bytes/strings

            // using helper functions and URL encoding
            value := base64.URLEncoding.EncodeToString([]byte("encoding 
            some data!"))
            fmt.Println("With EncodeToString and URLEncoding: ", value)

            // decode the first value
            decoded, err := base64.URLEncoding.DecodeString(value)
            if err != nil {
                return err
            }
            fmt.Println("With DecodeToString and URLEncoding: ", 
            string(decoded))

            return nil
        }

        // Base64ExampleEncoder shows similar examples
        // with encoders/decoders
        func Base64ExampleEncoder() error {
            // using encoder/ decoder
            buffer := bytes.Buffer{}

            // encode into the buffer
            encoder := base64.NewEncoder(base64.StdEncoding, &buffer)

            if _, err := encoder.Write([]byte("encoding some other 
            data")); err != nil {
                return err
            }

            // be sure to close
            if err := encoder.Close(); err != nil {
                return err
            }

            fmt.Println("Using encoder and StdEncoding: ", 
            buffer.String())

            decoder := base64.NewDecoder(base64.StdEncoding, &buffer)
            results, err := ioutil.ReadAll(decoder)
            if err != nil {
                return err
            }

            fmt.Println("Using decoder and StdEncoding: ", 
            string(results))

            return nil
        }
  1. 创建一个名为example的新目录并导航到它。
  2. 创建一个名为main.go的文件,其内容如下:
        package main

        import (
            "github.com/PacktPublishing/
             Go-Programming-Cookbook-Second-Edition/
             chapter3/encoding"
        )

        func main() {
            if err := encoding.Base64Example(); err != nil {
                panic(err)
            }

            if err := encoding.Base64ExampleEncoder(); err != nil {
                panic(err)
            }

            if err := encoding.GobExample(); err != nil {
                panic(err)
            }
        }
  1. 运行go run main.go,您还可以运行以下操作:
$ go build $ ./example

您应该看到以下输出:

$ go run main.go
With EncodeToString and URLEncoding: 
ZW5jb2Rpbmcgc29tZSBkYXRhIQ==
With DecodeToString and URLEncoding: encoding some data!
Using encoder and StdEncoding: ZW5jb2Rpbmcgc29tZSBvdGhlciBkYXRh
Using decoder and StdEncoding: encoding some other data
Gob Encoded valued length: 57
Gob Decode value: {10 15 wrench}
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

Gob 编码是一种基于 Go 数据类型构建的流式格式。它在发送和编码多个连续项目时最为有效。对于单个项目,其他编码格式(如 JSON)可能更高效、更可移植。尽管如此,gob编码使得编组大型复杂结构并在单独的过程中重建它们变得简单。尽管这里没有显示,gob也可以使用自定义MarshalBinaryUnmarshalBinary方法对自定义类型或未报告的类型进行操作。

Base64 编码对于通过GET请求中的 URL 进行通信或生成二进制数据的字符串表示编码非常有用。大多数语言都可以支持这种格式,并在另一端解组数据。因此,在不支持 JSON 格式的情况下,通常会对 JSON 有效负载等内容进行编码。

Go 中的结构标记与基本反射

反思是一个复杂的话题,不能用一个单一的方法来解决;然而,反射的一个实际应用是处理结构标记。在其核心,struct标记只是键-值字符串:您查找键,然后处理值。可以想象,对于 JSON 封送和解封送之类的东西,处理这些值有很多复杂性。

reflect包用于询问和理解接口对象。它有助手方法来查看不同类型的结构、值、struct标记等等。如果您需要基本接口转换之外的东西,例如本章开头的转换,那么您应该看看这个包。

怎么做。。。

以下步骤介绍了如何编写和运行应用:

  1. 从终端/控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter3/tags的新目录。
  2. 导航到此目录。
  3. 运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/tags 

您应该看到一个名为go.mod的文件,其中包含以下内容:

module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/tags    
  1. 复制~/projects/go-programming-cookbook-original/chapter3/tags中的测试,或者将其用作编写自己代码的练习!

  2. 创建一个名为serialize.go的文件,其内容如下:

        package tags

        import "reflect"

        // SerializeStructStrings converts a struct
        // to our custom serialization format
        // it honors serialize struct tags for string types
        func SerializeStructStrings(s interface{}) (string, error) {
            result := ""

            // reflect the interface into
            // a type
            r := reflect.TypeOf(s)
            value := reflect.ValueOf(s)

            // if a pointer to a struct is passed
            // in, handle it appropriately
            if r.Kind() == reflect.Ptr {
                r = r.Elem()
                value = value.Elem()
            }

            // loop over all of the fields
            for i := 0; i < r.NumField(); i++ {
                field := r.Field(i)
                // struct tag found
                key := field.Name
                if serialize, ok := field.Tag.Lookup("serialize"); ok {
                    // ignore "-" otherwise that whole value
                    // becomes the serialize 'key'
                    if serialize == "-" {
                        continue
                    }
                    key = serialize
                }

                switch value.Field(i).Kind() {
                // this recipe only supports strings!
                case reflect.String:
                    result += key + ":" + value.Field(i).String() + ";"
                    // by default skip it
                default:
                    continue
               }
            }
            return result, nil
        }
  1. 创建一个名为deserialize.go的文件,其内容如下:
        package tags

        import (
            "errors"
            "reflect"
            "strings"
        )

        // DeSerializeStructStrings converts a serialized
        // string using our custom serialization format
        // to a struct
        func DeSerializeStructStrings(s string, res interface{}) error          
        {
            r := reflect.TypeOf(res)

            // we're setting using a pointer so
            // it must always be a pointer passed
            // in
            if r.Kind() != reflect.Ptr {
                return errors.New("res must be a pointer")
            }

            // dereference the pointer
            r = r.Elem()
            value := reflect.ValueOf(res).Elem()

            // split our serialization string into
            // a map
            vals := strings.Split(s, ";")
            valMap := make(map[string]string)
            for _, v := range vals {
                keyval := strings.Split(v, ":")
                if len(keyval) != 2 {
                    continue
                }
                valMap[keyval[0]] = keyval[1]
            }

            // iterate over fields
            for i := 0; i < r.NumField(); i++ {
                field := r.Field(i)

               // check if in the serialize set
               if serialize, ok := field.Tag.Lookup("serialize"); ok {
                   // ignore "-" otherwise that whole value
                   // becomes the serialize 'key'
                   if serialize == "-" {
                       continue
                   }
                   // is it in the map
                   if val, ok := valMap[serialize]; ok {
                       value.Field(i).SetString(val)
                   }
               } else if val, ok := valMap[field.Name]; ok {
                   // is our field name in the map instead?
                   value.Field(i).SetString(val)
               }
            }
            return nil
        }
  1. 创建一个名为tags.go的文件,其内容如下:
        package tags

        import "fmt"

        // Person is a struct that stores a persons
        // name, city, state, and a misc attribute
        type Person struct {
            Name string `serialize:"name"`
            City string `serialize:"city"`
            State string
             Misc string `serialize:"-"`
             Year int `serialize:"year"`
        }

        // EmptyStruct demonstrates serialize
        // and deserialize for an Empty struct
        // with tags
        func EmptyStruct() error {
            p := Person{}

            res, err := SerializeStructStrings(&p)
            if err != nil {
                return err
            }
            fmt.Printf("Empty struct: %#v\n", p)
            fmt.Println("Serialize Results:", res)

            newP := Person{}
            if err := DeSerializeStructStrings(res, &newP); err != nil 
            {
                return err
            }
            fmt.Printf("Deserialize results: %#v\n", newP)
                return nil
            }

           // FullStruct demonstrates serialize
           // and deserialize for an Full struct
           // with tags
           func FullStruct() error {
               p := Person{
                   Name: "Aaron",
                   City: "Seattle",
                   State: "WA",
                   Misc: "some fact",
                   Year: 2017,
               }
               res, err := SerializeStructStrings(&p)
               if err != nil {
                   return err
               }
               fmt.Printf("Full struct: %#v\n", p)
               fmt.Println("Serialize Results:", res)

               newP := Person{}
               if err := DeSerializeStructStrings(res, &newP);
               err != nil {
                   return err
               }
               fmt.Printf("Deserialize results: %#v\n", newP)
               return nil
        }
  1. 创建一个名为example的新目录并导航到它。
  2. 创建一个名为main.go的文件,其内容如下:
        package main

        import (
            "fmt"

            "github.com/PacktPublishing/
             Go-Programming-Cookbook-Second-Edition/
             chapter3/tags"
        )

        func main() {

            if err := tags.EmptyStruct(); err != nil {
                panic(err)
            }

            fmt.Println()

            if err := tags.FullStruct(); err != nil {
                panic(err)
            }
        }
  1. 运行go run main.go,您还可以运行以下操作:
$ go build $ ./example

您应该看到以下输出:

$ go run main.go
Empty struct: tags.Person{Name:"", City:"", State:"", Misc:"", 
Year:0}
Serialize Results: name:;city:;State:;
Deserialize results: tags.Person{Name:"", City:"", State:"", 
Misc:"", Year:0}

Full struct: tags.Person{Name:"Aaron", City:"Seattle", 
State:"WA", Misc:"some fact", Year:2017}
Serialize Results: name:Aaron;city:Seattle;State:WA;
Deserialize results: tags.Person{Name:"Aaron", City:"Seattle",         
State:"WA", Misc:"", Year:0}
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

此方法生成一个字符串序列化格式,该格式接受一个struct值,并将所有字符串字段序列化为可解析格式。此配方不处理某些边缘情况;特别是,字符串不能包含冒号(:或分号;字符。以下是其行为的摘要:

  • 如果字段是字符串,则将对其进行序列化/反序列化。

  • 如果字段不是字符串,则将忽略它。

  • 如果字段的struct标记包含序列化“key”,则该 key 将是返回的序列化/反序列化环境。

  • 不处理重复项。

  • 如果未指定struct标记,则使用字段名。

  • 如果struct标记值是连字符(-),则忽略该字段,即使它是字符串。

另外需要注意的是,反射并不完全适用于非导出的值。

通过闭包实现集合

如果您一直在使用函数式或动态编程语言,您可能会觉得for循环和if语句会产生冗长的代码。使用mapfilter等功能结构处理列表非常有用,可以使代码看起来更可读;但是,在 Go 中,这些类型不在标准库中,如果没有泛型或非常复杂的反射以及使用空接口,则很难进行泛化。本食谱将为您提供一些使用 Go 闭包实现集合的基本示例。

怎么做。。。

以下步骤介绍了如何编写和运行应用:

  1. 从终端/控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter3/collections的新目录。
  2. 导航到此目录。
  3. 运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/collections 

您应该看到一个名为go.mod的文件,其中包含以下内容:

module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter3/collections    
  1. ~/projects/go-programming-cookbook-original/chapter3/collections复制测试,或者将其用作编写自己代码的练习!

  2. 创建一个名为collections.go的文件,其内容如下:

        package collections

        // WorkWith is the struct we'll
        // be implementing collections for
        type WorkWith struct {
            Data    string
            Version int
        }

        // Filter is a functional filter. It takes a list of
        // WorkWith and a WorkWith Function that returns a bool
        // for each "true" element we return it to the resultant
        // list
        func Filter(ws []WorkWith, f func(w WorkWith) bool) []WorkWith 
        {
            // depending on results, smalles size for result
            // is len == 0
            result := make([]WorkWith, 0)
            for _, w := range ws {
                if f(w) {
                    result = append(result, w)
                }
            }
            return result
        }

        // Map is a functional map. It takes a list of
        // WorkWith and a WorkWith Function that takes a WorkWith
        // and returns a modified WorkWith. The end result is
        // a list of modified WorkWiths
        func Map(ws []WorkWith, f func(w WorkWith) WorkWith) []WorkWith 
        {
            // the result should always be the same
            // length
            result := make([]WorkWith, len(ws))

            for pos, w := range ws {
                newW := f(w)
                result[pos] = newW
            }
            return result
        }
  1. 创建一个名为functions.go的文件,其内容如下:
        package collections

        import "strings"

        // LowerCaseData does a ToLower to the
        // Data string of a WorkWith
        func LowerCaseData(w WorkWith) WorkWith {
            w.Data = strings.ToLower(w.Data)
            return w
        }

        // IncrementVersion increments a WorkWiths
        // Version
        func IncrementVersion(w WorkWith) WorkWith {
            w.Version++
            return w
        }

        // OldVersion returns a closures
        // that validates the version is greater than
        // the specified amount
        func OldVersion(v int) func(w WorkWith) bool {
            return func(w WorkWith) bool {
                return w.Version >= v
            }
        }
  1. 创建一个名为example的新目录并导航到它。
  2. 创建一个名为main.go的文件,其内容如下:
        package main

        import (
            "fmt"

            "github.com/PacktPublishing/
             Go-Programming-Cookbook-Second-Edition/
             chapter3/collections"
        )

        func main() {
            ws := []collections.WorkWith{
                collections.WorkWith{"Example", 1},
                collections.WorkWith{"Example 2", 2},
            }

            fmt.Printf("Initial list: %#v\n", ws)

            // first lower case the list
            ws = collections.Map(ws, collections.LowerCaseData)
            fmt.Printf("After LowerCaseData Map: %#v\n", ws)

            // next increment all versions
            ws = collections.Map(ws, collections.IncrementVersion)
            fmt.Printf("After IncrementVersion Map: %#v\n", ws)

            // lastly remove all versions older than 3
            ws = collections.Filter(ws, collections.OldVersion(3))
            fmt.Printf("After OldVersion Filter: %#v\n", ws)
        }
  1. 运行go run main.go,您还可以运行以下操作:
$ go build $ ./example

您应该看到以下输出:

$ go run main.go
Initial list:         
[]collections.WorkWith{collections.WorkWith{Data:"Example", 
Version:1}, collections.WorkWith{Data:"Example 2", Version:2}}
After LowerCaseData Map:         
[]collections.WorkWith{collections.WorkWith{Data:"example", 
Version:1}, collections.WorkWith{Data:"example 2", Version:2}}
After IncrementVersion Map: 
[]collections.WorkWith{collections.WorkWith{Data:"example", 
Version:2}, collections.WorkWith{Data:"example 2", Version:3}}
After OldVersion Filter: 
[]collections.WorkWith{collections.WorkWith{Data:"example 2",        
Version:3}}
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

Go 中的闭包非常强大。虽然我们的collections函数不是泛型函数,但它们相对较小,可以很容易地应用于WorkWith结构,只需使用各种函数添加最少的代码。您可能会注意到,从这里看,我们没有返回任何错误。这些函数的思想是它们是纯的:除了我们选择在每次调用后重写它之外,原始列表没有任何副作用。

如果您需要对列表或列表结构应用修改层,那么这种模式可以为您节省大量的混乱,并使测试变得非常简单。还可以将映射和过滤器链接在一起,以获得非常有表现力的编码风格。