Go语言中的常量数组:如何声明而不抓狂

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:来自Stack Overflow的永恒问题

\n\n

如果你曾经搜索过“Declare a constant array in Go”,那么你很可能见过Stack Overflow上那个有数千次浏览的帖子。是的,你并不是唯一感到困惑的人。在Go中,你确实不能直接写const arr = [...]int{1, 2, 3}——编译器会礼貌地告诉你“不行”。

\n\n

为什么会这样?那么,如果我们需要一个不可变的数据列表,该怎么办?在本文中,我们将深入探讨Go中常量的本质,了解为什么数组不能是常量,并找到最佳的变通方法。剧透:解决方案是存在的,而且甚至比你想象的更优雅。

\n\n

为什么Go不允许声明常量数组?

\n\n

要理解这一点,我们需要深入语言底层。在Go中,常量是在编译阶段计算的不可变值。它们必须是严格类型化的,并且在程序运行之前就已知。而数组是复合数据类型,占用内存并且可以被修改。

\n\n

下面是一个简单的例子,它无法编译通过:

\n\n
package main\n\nconst arr = [3]int{1, 2, 3} // 错误:常量初始化器 [3]int{1, 2, 3} 不是常量\n\nfunc main() {\n    fmt.Println(arr)\n}
\n\n

编译器说:“嘿,这不是常量!”。它是对的。即使数组被声明为const,Go也无法保证其内容不会通过指针或切片被修改。因此,语言设计者决定:没有常量数组。

\n\n

但别急着沮丧。至少有三种方法可以绕过这个限制,每种方法都有其优点。

\n\n

方法1:使用带有“请勿修改”注释的变量

\n\n

最简单也最天真的方法——直接声明一个变量,并希望没人会修改它。但我们是认真的开发者,对吧?

\n\n
package main\n\nimport "fmt"\n\n// arr - 常量数组。请不要修改它。\nvar arr = [3]int{1, 2, 3}\n\nfunc main() {\n    fmt.Println(arr)\n    // arr[0] = 100 // 取消注释——一切都会崩溃\n}
\n\n

缺点很明显:这不是真正的常量。任何人都可以通过赋值来修改数组。但对于快速原型或小型项目来说,这已经足够了。

\n\n

方法2:Getter函数(正确的方式)

\n\n

这种方法用于生产代码。我们将数组隐藏在函数内部,并返回它的副本。这样,没有人能够修改原始数据。

\n\n
package main\n\nimport "fmt"\n\n// getColors 返回颜色数组的副本。\n// 数组受到保护,免受外部修改。\nfunc getColors() [3]string {\n    return [3]string{"Red", "Green", "Blue"}\n}\n\nfunc main() {\n    colors := getColors()\n    fmt.Println(colors)\n    // colors[0] = "Yellow" // 这会修改副本,而不是原始数据\n}
\n\n

为什么这很好?因为:

\n
    \n
  • 数组无法从外部修改——函数返回一个副本。
  • \n
  • 代码可读性强且易于理解。
  • \n
  • 易于测试和维护。
  • \n
\n\n

但有一个问题:每次调用函数都会创建数组的一个新副本。如果数组很大(例如,1000个元素),这可能会很耗费资源。在这种情况下,请使用以下方法。

\n\n

方法3:具有私有访问权限的包级变量

\n\n

如果你正在编写一个包,并希望保护数组免受修改,请将其设为不可导出的(小写字母),并通过函数提供访问权限。

\n\n
package constants\n\n// 数组仅在包内可访问\nvar defaultPorts = [5]int{80, 443, 8080, 3000, 5000}\n\n// GetDefaultPorts 返回端口数组的副本。\nfunc GetDefaultPorts() [5]int {\n    return defaultPorts\n}
\n\n

现在,你包的用户只能读取数据,而不能修改它们。非常适合库。

\n\n

方法4:使用iota创建常量集合

\n\n

如果你需要的不是值本身,而是它们的索引呢?例如

博客

书籍推荐