Go 编程的三个常见问题
via:
https://medium.com/higher-order-functions/golang-three-common-programming-problems-3ef8baf006af
作者:Saurabh Nayar
四哥水平有限,如有翻译或理解错误,烦请帮忙指出,感谢!
作者在文章中列举了使用 Go 语言过程碰到的三个常见问题,并且都给出了解决方法,一起来看下作者是怎么解决的!
原文如下:
每种语言都是独一无二的。这些常见编程问题的解决方案在 Java 中是非常不同的 -- Java 是我以前最喜欢的编程语言。我敢这么说,这些问题的解决方法如果使用 Java 来解决会更加直观。
Go 语言有解决这些问题的独特方法。我下面列出的解决方案最初对我来说不是很直观,但是现在已经成为我的下意识的反映。我不确定这些解决方法是否是地道的 Go 语言解决方式,老实说,我也不知道地道的方式是怎么样的。
也许会有更好的、不同的方式来解决这些问题 -- 我想听听你的想法。
现在,我们一起来研究下这些问题。
问题一
问题:我需要维护一个集合,但是 Go 语言里面没有集合这种数据结构。
解决办法之一:可以用 Go 语言里面的 map 代替集合,map 中的 key 都是唯一的。
package main
import "fmt"
type Set struct {
m map[string]bool
}
func NewSet() Set {
m := make(map[string]bool)
return Set{m: m}
}
func (s *Set) Contains(val string) bool {
_, ok := s.m[val]
return ok
}
func (s *Set) Add(val string) {
s.m[val] = true
}
func (s *Set) Remove(val string) {
delete(s.m, val)
}
func main() {
s := NewSet()
s.Add("foo")
fmt.Printf("s has foo: %t. s has bar: %t\n", s.Contains("foo"), s.Contains("bar"))
s.Remove("foo")
fmt.Printf("s has foo: %t. s has bar: %t\n", s.Contains("foo"), s.Contains("bar"))
}
使用 map 作为集合的底层数据结构的好处在于,map 基于 hash 表实现,减值查找效率高。使用这种方法可以少写很多代码。
问题二
问题:我想比较两个值大小,但是 == 操作符有时会失效。
解决办法之一:我们需要理解 == 操作符的适用场景。
包含 map 或者 slice 的结构体
type ABC struct {
a int
b string
c []int
}
Error:
invalid operation: a == b (struct containing []int cannot be compared)
包含指针的结构体
从实际意义上讲,指针可以进行比较,但事实总是出乎意料。
a, b := 1, 1
fmt.Println(&a == &b) // False
使用 reflect.DeepEqual
//ABC - A simple type
type ABC struct {
a int
b string
c []int
}
var a = ABC{a: 1, b: "10", c: []int{1, 2}}
var b = ABC{a: 1, b: "10", c: []int{1, 2}}
reflect.DeepEqual(a, b)
Example #2
a, b := 1, 1
fmt.Println(&a == &b) // False
fmt.Println(reflect.DeepEqual(&a, &b)) // True
reflect.DeepEqual 可以实现更好的效果。但是如果结构体中 float 或者 时间字段想要忽略,则需要自己编写比较函数。
//ABC - A simple type
type ABC struct {
a int
b string
t time.Time // Ignore time while comparing to structs
}
var a = ABC{a: 1, b: "10", t: time.Now()}
var b = ABC{a: 1, b: "10", t: time.Now()}
fmt.Println(a == b, equals(a, b))
func equals(val1, val2 ABC) bool {
return val1.a == val2.a && val1.b == val2.b
}
除非别无选择,否则一般都不会自己编写比较函数。但是与 == 操作符相比,是否要倾向于使用 reflect.DeepEqual。本质上,如果 == 比较的结果为 true,则 reflect.DeepEqual 可以保证比较的结果为 true,反之为 false。所以你可以默认使用 reflect.DeepEqual,除非程序上有性能上的限制:
func BenchmarkOperator(t *testing.B) {
for i := 0; i < t.N; i++ {
if a == b {
}
}
}
func BenchmarkReflectDeep(t *testing.B) {
for i := 0; i < t.N; i++ {
if reflect.DeepEqual(a, b) {
}
}
}
BenchmarkOperator-8 44614131 24.8 ns/op 0 B/op 0 allocs/op
BenchmarkReflectDeep-8 823174 1558 ns/op 96 B/op 2 allocs/op
从结果看出,== 比 reflect.DeepEqual 快多了!
问题三
问题:我需要使用一个 struct 作为 map 的键 -- 但 struct 有想要忽略的 slice、指针或别的字段。
解决办法之一:Go 语言里面使用 == 操作符比较 map 的键,而不要使用 reflect.DeepEqual。
解决问题的方法之一就是自定义 key 的创建逻辑。
//Obvious solution that will not work
type A struct {
i *int
}
i, j := 1, 1
a, b := A{i: &i}, A{i: &j}
m := map[A]bool{}
m[a] = true
_, ok := m[b]
fmt.Println(ok) // False key b doesn't exist in map m
//Custom keys- solution
func customKey(a A) int {
return *a.i
}
i, j := 1, 1
a, b := A{i: &i}, A{i: &j}
m := map[int]bool{}
m[customKey(a)] = true
_, ok := m[customKey(b)]
fmt.Println(ok)// This will return true
有奖问答
问题:从上面问题 2 和问题 3 衍生出一个问题,如何比较两个 map?
key, val := "key", "val"
key1, val1 := "key", "val"
abc := map[*string]string{&key: val}
abc2 := map[*string]string{&key1: val1}
def := map[string]*string{key: &val}
def2 := map[string]*string{key1: &val1}
fmt.Println(reflect.DeepEqual(abc, abc2)) //false
fmt.Println(reflect.DeepEqual(def, def2)) //true
首先需要注意的是,不能使用 == 操作符比较 map,需要使用 reflect.DeepEqual。根据 reflect.DeepEqual 的比较规则,map 的键使用 == 操作符比较而值会使用 reflect.DeepEqual 递归比较。
推荐阅读
站长 polarisxu
自己的原创文章
不限于 Go 技术
职场和创业经验
Go语言中文网
每天为你
分享 Go 知识
Go爱好者值得关注