learn Go with tests学习笔记(7)——Maps

Maps

声明Map变量的语法如下:

var dictionary = map[keyType]valueType

keyType比较特殊,它的类型必须是comparable的,如果无法判断两个key是否相等,我们就无法确保得到的是正确的value。The value type, on the other hand, can be any type you want. It can even be another map.以下是官方文档对comparable type的详细说明:

  • Boolean values are comparable. Two boolean values are equal if they are either both true or both false.

  • Integer values are comparable and ordered, in the usual way.

  • Floating-point values are comparable and ordered, as defined by the IEEE-754 standard.

  • Complex values are comparable. Two complex values u and v are equal if both real(u) == real(v) and imag(u) == imag(v).

  • String values are comparable and ordered, lexically byte-wise.

  • Pointer values are comparable. Two pointer values are equal if they point to the same variable or if both have value nil. Pointers to distinct zero-size variables may or may not be equal.

  • Channel values are comparable. Two channel values are equal if they were created by the same call to make or if both have value nil.

  • Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.

  • A value x of non-interface type X and a value t of interface type T are comparable when values of type X are comparable and X implements T. They are equal if t’s dynamic type is identical to X and t’s dynamic value is equal to x.

  • Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.

  • Array values are comparable if values of the array element type are comparable. Two array values are equal if their corresponding elements are equal.

    A comparison of two interface values with identical dynamic types causes a run-time panic if values of that type are not comparable. This behavior applies not only to direct interface value comparisons but also when comparing arrays of interface values or structs with interface-valued fields.

    Slice, map, and function values are not comparable. However, as a special case, a slice, map, or function value may be compared to the predeclared identifier nil. Comparison of pointer, channel, and interface values to nil is also allowed and follows from the general rules above.

从map中查询值:

func Search(dictionary map[string]string, word string) string {
	return dictionary[word]
}

自定义类型

type Dictionary map[string]string

func (d Dictionary) Search(word string) string {
	return d[word]
}

这样做有两个直观的好处,重定义了map[string]string类型之后代码的意图就很明显,另外Search从函数变成了方法,要传进去的参数也减少了一个。

完善功能

Search方法在输入一个字典里没有的key时,就会什么也得不到。我们想要在这种情况下添加一个提示,让方法报告在字典里找不到这个key。这样使用者就不会纠结到底是没有这个key还是这个key没有定义了。虽然听起来很牵强但是这个功能在特定的应用场景还是有必要的。下面是为了实现这个功能写的测试。

func TestSearch(t *testing.T) {
	dictionary := Dictionary{"test": "this is just a test"}

	t.Run("known word", func(t *testing.T) {
		got, _ := dictionary.Search("test")
		want := "this is just a test"

		assertStrings(t, got, want)
	})

	t.Run("unknown word", func(t *testing.T) {
		_, err := dictionary.Search("unknown")
		want := "could not find the word you were looking for"
		//protecting assertStrings with if to ensure we don't call .Error() on nil.
		if err == nil {
			t.Fatal("expected to get an error.")
		}
		//Error类型可以使用`.Error()`方法转换成string
		assertStrings(t, err.Error(), want)
	})
}

为了完成这个功能,只需要让Search方法返回第二个参数那就是一个Error类型。

func (d Dictionary) Search(word string) (string, error) {
	definition, ok := d[word]
	if !ok {
		return "", errors.New("could not find the word you were looking for")
	}

	return definition, nil
}

In order to make this pass, we are using an interesting property of the map lookup. It can return 2 values. The second value is a boolean which indicates if the key was found successfully.

This property allows us to differentiate between a word that doesn’t exist and a word that just doesn’t have a definition.

添加元素

为了完成往字典里添加新的字的能力,我们需要对map进行修改。根据上一章对于pointers的学习,直觉告诉我们想要利用函数或者方法来完成对map的修改我们需要传入类似于&myMap这样的指针。但是实际上并不需要,而且这并不意味着map属于引用类型( as Dave Cheney describes they are not)。

A map value is a pointer to a runtime.hmap structure.

所以当你把一个map传入到函数或者方法中的时候,你实际上copy了它,但是只copy了pointer部分,而不包括里面的数据。

map里面有一个小坑那就是map可以是nil值。虽然你读取一个值为nil的map时,它表现得就和一个empty的map一样,但是你一旦想要写入东西到里面,就会引起runtime panic。所以你永远不应该声明一个空的map。

var m map[string]string

相反地,应该使用make来创建map,或者加上花括号来初始化空map。这两种方法都可以得到一个空的hash map,并且确保不会发生runtime panic。

var dictionary = map[string]string{}

// OR

var dictionary = make(map[string]string)

重构,打包错误

const (
	ErrNotFound   = DictionaryErr("could not find the word you were looking for")
	ErrWordExists = DictionaryErr("cannot add word because it already exists")
)

type DictionaryErr string

func (e DictionaryErr) Error() string {
	return string(e)
}

We made the errors constant; this required us to create our own DictionaryErr type which implements the error interface. You can read more about the details in this excellent article by Dave Cheney. Simply put, it makes the errors more reusable and immutable.

删除元素

func (d Dictionary) Delete(word string) {
	delete(d, word)
}

Go has a built-in function delete that works on maps. It takes two arguments. The first is the map and the second is the key to be removed.

The delete function returns nothing, and we based our Delete method on the same notion. Since deleting a value that’s not there has no effect, unlike our Update and Add methods, we don’t need to complicate the API with errors.

总结

In this section, we covered a lot. We made a full CRUD (Create, Read, Update and Delete) API for our dictionary. Throughout the process we learned how to:

  • Create maps

    创建空的map时要防止创建值为nil的map,可以使用make构造map或者在声明后面添加{}来初始化,否则就会发生runtime panic。

  • Search for items in maps

    我们通过返回error来区分key是已存在还是没有定义

  • Add new items to maps

    在这里我们通过返回error来区分key是否已存在,如果已存在就报错

  • Update items in maps

    我们通过返回error来区分key是否不存在,不存在就报错

  • Delete items from a map

    delete不需要区分情况,因为delete不存在的键值对不会有影响

  • Learned more about errors

    1. How to create errors that are constants
    2. Writing error wrappers
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洞爷湖dyh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值