引言:为什么 Python 程序员在 Go 中怀念 "in"?
\n\n如果您从 Python 世界来到 Go,您一定记得这个神奇的代码行:if "banana" in fruits:。简短、优雅、可读性强。然后您打开 Go 并问自己:"Go 是否有类似 Python 的 'if x in' 结构?" —— 这正是 Stack Overflow 上最流行的问题,获得了数千次浏览。
剧透:Go 中没有像 in 这样的直接语法糖。但这并不意味着问题无法解决或解决起来很复杂。相反,Go 有几种惯用的方法来检查成员关系,每种方法都高效且各有特色。
在本文中,我们将讨论:
\n- \n
- 为什么 Go 没有
in,以及语言哲学如何影响这一点; \n - 如何检查切片中元素的存在(包括使用新函数
slices.Contains); \n - 如何在映射中搜索(剧透:这是最快的方法); \n
- 通过
strings.Contains在字符串中搜索子串; \n - 编写我们自己的通用包装函数以实现“Python 风格”; \n
- 比较各种方法的性能。 \n
为什么 Go 不想要 "if x in"?
\n\nGo 是一种极简语言。它的创建者(Rob Pike、Ken Thompson、Rob Griesemer)有意识地避免使语法过载。他们没有添加关键字 in,而是建议使用显式循环和内置结构。
这不是缺点,而是一个特点:您总是能看到正在发生的事情。例如,对于映射,语法 value, ok := myMap[key] 不仅为您提供了存在性检查,还提供了值本身——这比简单的布尔值 True/False 更强大。
方法 1:在切片中搜索(经典方法)
\n\n最常见的场景。假设您有一个字符串切片,需要检查其中是否有 "Go"。
\n\n在 Go 1.21 版本之前,您必须手动编写循环:
\n\npackage main\n\nimport "fmt"\n\nfunc contains(slice []string, target string) bool {\n for _, v := range slice {\n if v == target {\n return true\n }\n }\n return false\n}\n\nfunc main() {\n languages := []string{"Python", "Java", "Go", "Rust"}\n if contains(languages, "Go") {\n fmt.Println("Go 已找到!")\n }\n}\n\n\n这段代码是地道的 Go 代码。它显式、快速且易于理解。对于小型切片(最多 1000 个元素),这种线性搜索效果很好。
\n\n方法 1.1:使用 slices.Contains(Go 1.21+)
\n\n从 Go 1.21 版本(于 2023 年 8 月发布)开始,标准库中引入了 slices 包,其中包含现成的 Contains 函数。现在您的代码几乎和 Python 一样简洁:
package main\n\nimport (\n "fmt"\n "slices"\n)\n\nfunc main() {\n languages := []string{"Python", "Java", "Go", "Rust"}\n if slices.Contains(languages, "Go") {\n fmt.Println("Go 已找到!(通过 slices.Contains)")\n }\n}\n\n\n重要提示: slices.Contains 是一个泛型函数。它适用于任何支持 == 运算符的类型(字符串、数字、布尔值、不包含切片/映射的结构体)。
方法 2:在映射中搜索——性能之王
\n\n在 Python 中,您对字典使用 in,这是常数时间 O(1)。在 Go 中也是如此,但语法不同:
package main\n\nimport "fmt"\n\nfunc main() {\n // 创建映射作为集合\n fruits := map[string]bool{\n "apple": true,\n "banana": true,\n "cherry": true,\n }\n\n if fruits["banana"] {\n fmt.Println("banana 存在!")\n }\n\n // 更正确的方法——使用 ok 检查\n if _, ok := fruits["orange"]; ok