在Go语言中的静态初始化?

18

我正在学习 Go Lang,但在完成一个练习时遇到了问题:

https://tour.golang.org/methods/23

这个练习要求我实现 ROT13 加密。我决定使用从字节到旋转值的映射来实现加密,但我不确定初始化该映射的最佳方法。我不想使用文本初始化这个映射,而是希望通过循环整个字母表并在循环内设置 (键,值) 对的方式以编程方式初始化它。我还希望该映射仅可从 Rot13Reader 结构或对象中访问,并且所有实例都共享同一映射(而不是每个 Rot13Reader 对应一个副本)。

下面是我当前的 Go 程序:

package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

var rot13Map = map[byte]byte{}

func (rotr *rot13Reader) Read(p []byte) (int, error) {
    n, err := rotr.r.Read(p)
    for i := 0; i < n; i++ {
        if sub := rot13Map[p[i]]; sub != byte(0) {
            p[i] = sub
        }
    }
    return n, err
}

func main() {
    func() {
        var uppers = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
        var lowers = []byte("abcdefghijklmnopqrstuvwxyz")

        var init = func (alphabet []byte) {
            for i, char := range alphabet {
                rot13_i := (i + 13) % 26
                rot13Map[char] = alphabet[rot13_i]
            }
        }

        init(uppers)
        init(lowers)
    }()

    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

我对这个方案有以下问题:

  • 我不想在main()中准备rot13Map
  • 我不想让rot13Map处于全局作用域。
  • 我不想让每个rot13Reader副本都有单独的rot13Map

在Go语言中有什么办法可以实现我的要求吗?


在某种程度上相关的问题是,为什么我必须将嵌套函数定义为 var init = func (...) {...} 而不是 func init(...) {...}?(后者会导致编译器错误) - jlhawn
我猜init不允许像main一样带参数。 - zk82
http://golang.org/ref/spec 指明,init 函数(位于包级别的 func init())不能在程序的任何地方被引用。 - zk82
我意识到我将这个函数命名为init,但当时我并不知道这个名称的重要性以及包级别的init是如何工作的,因为Go Lang教程从未提过它。 - jlhawn
这个教程并不是一个完整的参考,但它鼓励你去查看规范。它们非常易读 ;) 你认为在这个教程中加入对 "init" 的参考会有价值吗(还有其他列出要添加的主题,如数组)? - zk82
1
我知道这个教程是为初学者准备的,而init似乎更适合于初中级话题。如果有第二个教程涵盖更高级的主题,比如init、制作包(以及它们与$GOPATH的关系,这一点我一开始觉得有些困惑)、结构体中的匿名字段和组合(我现在才刚刚开始学习),以及使用go命令行工具,那就太好了。我在网络和golang.org上找到了分别涉及这些主题的地方,但不像入门教程那样集中在一个地方。 - jlhawn
3个回答

17

为了实现这个,我会创建一个 rot13 的包。你可以在 init() 函数中以编程方式创建映射,并将其作为包级全局变量提供给所有 rot13 解码器使用。当导入您的包时,init 函数会运行。

因为 Rot13Reader 是包中唯一的类型,所以它是唯一能够访问您的映射的类型。

警告:所有代码未经测试。

package rot13

import (
    "io"
)

var rot13Map = map[byte]byte{}

func init() {
    var uppers = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    var lowers = []byte("abcdefghijklmnopqrstuvwxyz")

    var init = func(alphabet []byte) {
        for i, char := range alphabet {
            rot13_i := (i + 13) % 26
            rot13Map[char] = alphabet[rot13_i]
        }
    }

    init(uppers)
    init(lowers)
}

type Reader struct {
    r io.Reader
}

func (rotr Reader) Read(p []byte) (int, error) {
    n, err := rotr.r.Read(p)
    for i := 0; i < n; i++ {
        if sub := rot13Map[p[i]]; sub != byte(0) {
            p[i] = sub
        }
    }
    return n, err
}

显然,您不能在go tour中创建另一个包。您只能使用rot13Map作为main函数的一部分。如果您想要所需的隔离性,您需要在本地运行Go。


6
为了完整起见:在一个包中,除了 init 函数之外,还有一个 sync.Once 可以用于初始化工作。它只会运行一次提供的函数。
创建一个 Once 对象并在其上调用 Do 函数,只要 Once 对象的状态没有改变,提供的函数就只会被调用一次。
例如:
import "sync"

var readerInitOnce sync.Once

func (rotr *rot13Reader) Read(p []byte) (int, error) {
    readerInitOnce.Do(initRot13Map)
    ...
}

4
我会简化您的代码并使用一个init函数。例如,
package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

func newRot13Map() map[byte]byte {
    n := byte('Z' - 'A' + 1)
    rot13 := make(map[byte]byte, 2*n)
    for ltr := byte(0); ltr < n; ltr++ {
        sub := (ltr + 13) % n
        rot13[ltr+'A'] = sub + 'A'
        rot13[ltr+'a'] = sub + 'a'
    }
    return rot13
}

var rot13Map map[byte]byte

func init() {
    rot13Map = newRot13Map()
}

func (rotr *rot13Reader) Read(p []byte) (int, error) {
    n, err := rotr.r.Read(p)
    for i, ltr := range p[:n] {
        if sub, ok := rot13Map[ltr]; ok {
            p[i] = sub
        }
    }
    return n, err
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

输出:

You cracked the code!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接