第6章,部分字词修订。

pull/26/head
zhliner 2017-08-24 22:28:33 +08:00
parent ff3c5b0a70
commit 9a9b9a0594
5 changed files with 13 additions and 13 deletions

View File

@ -85,4 +85,4 @@ fmt.Println(geometry.PathDistance(perim)) // "12", standalone function
fmt.Println(perim.Distance()) // "12", method of geometry.Path
```
**译注:** 如果我们要用方法去计算perim的distance还需要去写全geometry的包名和其函数名但是因为Path这个变量定义了一个可以直接用的Distance方法所以我们可以直接写perim.Distance()。相当于可以少打很多字作者应该是这个意思。因为在Go里包外调用函数需要带上包名还是挺麻烦的。
**译注:** 如果我们要用方法去计算perim的distance还需要去写全geometry的包名和其函数名但是因为Path这个类型定义了一个可以直接用的Distance方法所以我们可以直接写perim.Distance()。相当于可以少打很多字作者应该是这个意思。因为在Go里包外调用函数需要带上包名还是挺麻烦的。

View File

@ -43,7 +43,7 @@ func (v Values) Add(key, value string) {
}
```
这个定义向外部暴露了一个map的类型的变量并且提供了一些能够简单操作这个map的方法。这个map的value字段是一个string的slice所以这个Values是一个多维map。客户端使用这个变量的时候可以使用map固有的一些操作(make切片m[key]等等),也可以使用这里提供的操作方法,或者两者并用,都是可以的:
这个定义向外部暴露了一个map的命名类型并且提供了一些能够简单操作这个map的方法。这个map的value字段是一个string的slice所以这个Values是一个多维map。客户端使用这个变量的时候可以使用map固有的一些操作(make切片m[key]等等),也可以使用这里提供的操作方法,或者两者并用,都是可以的:
<u><i>gopl.io/ch6/urlvalues</i></u>
```go
@ -61,6 +61,6 @@ fmt.Println(m.Get("item")) // ""
m.Add("item", "3") // panic: assignment to entry in nil map
```
对Get的最后一次调用中nil接收器的行为即是一个空map的行为。我们可以等价地将这个操作写成Value(nil).Get("item")但是如果你直接写nil.Get("item")的话是无法通过编译的因为nil的字面量编译器无法判断其准类型。所以相比之下最后的那行m.Add的调用就会产生一个panic因为他尝试更新一个空map。
对Get的最后一次调用中nil接收器的行为即是一个空map的行为。我们可以等价地将这个操作写成Value(nil).Get("item")但是如果你直接写nil.Get("item")的话是无法通过编译的因为nil的字面量编译器无法判断其准类型。所以相比之下最后的那行m.Add的调用就会产生一个panic因为他尝试更新一个空map。
由于url.Values是一个map类型并且间接引用了其key/value对因此url.Values.Add对这个map里的元素做任何的更新、删除操作对调用方都是可见的。实际上就像在普通函数中一样虽然可以通过引用来操作内部值但在方法想要修改引用本身是不会影响原始值的比如把他置为nil或者让这个引用指向了其它的对象调用方都不会受影响。译注因为传入的是存储了内存地址的变量你改变这个变量是影响不了原始的变量的想想C语言是差不多的
由于url.Values是一个map类型并且间接引用了其key/value对因此url.Values.Add对这个map里的元素做任何的更新、删除操作对调用方都是可见的。实际上就像在普通函数中一样虽然可以通过引用来操作内部值但在方法想要修改引用本身是不会影响原始值的,比如把他置为nil或者让这个引用指向了其它的对象调用方都不会受影响。译注因为传入的是存储了内存地址的变量你改变这个变量本身是影响不了原始的变量的想想C语言是差不多的

View File

@ -13,7 +13,7 @@ func (p *Point) ScaleBy(factor float64) {
在现实的程序里一般会约定如果Point这个类有一个指针作为接收器的方法那么所有Point的方法都必须有一个指针接收器即使是那些并不需要这个指针接收器的函数。我们在这里打破了这个约定只是为了展示一下两种方法的异同而已。
只有类型(Point)和指向他们的指针`(*Point)`,才是可能会出现在接收器声明里的两种接收器。此外,为了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的,比如下面这个例子:
只有类型(Point)和指向他们的指针`(*Point)`,才可能是出现在接收器声明里的两种接收器。此外,为了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的,比如下面这个例子:
```go
type P *int
@ -65,7 +65,7 @@ pptr.Distance(q)
这里的几个例子可能让你有些困惑,所以我们总结一下:在每一个合法的方法调用表达式中,也就是下面三种情况里的任意一种情况都是可以的:
不论接收器的实际参数和其接收器的形式参数相同比如两者都是类型T或者都是类型`*T`
不论接收器的实际参数和其形式参数相同比如两者都是类型T或者都是类型`*T`
```go
Point{1, 2}.Distance(q) // Point
@ -84,11 +84,11 @@ p.ScaleBy(2) // implicit (&p)
pptr.Distance(q) // implicit (*pptr)
```
如果命名类型T(译注用type xxx定义的类型)的所有方法都是用T类型自己来做接收器(而不是`*T`)那么拷贝这种类型的实例就是安全的调用他的任何一个方法也就会产生一个值的拷贝。比如time.Duration的这个类型在调用其方法时就会被全部拷贝一份包括在作为参数传入函数的时候。但是如果一个方法使用指针作为接收器你需要避免对其进行拷贝因为这样可能会破坏掉该类型内部的不变性。比如你对bytes.Buffer对象进行了拷贝那么可能会引起原始对象和拷贝对象只是别名而已但实际上其指向的对象是一致的。紧接着对拷贝后的变量进行修改可能会有让你意外的结果。
如果命名类型T译注用type xxx定义的类型的所有方法都是用T类型自己来做接收器而不是`*T`那么拷贝这种类型的实例就是安全的调用他的任何一个方法也就会产生一个值的拷贝。比如time.Duration的这个类型在调用其方法时就会被全部拷贝一份包括在作为参数传入函数的时候。但是如果一个方法使用指针作为接收器你需要避免对其进行拷贝因为这样可能会破坏掉该类型内部的不变性。比如你对bytes.Buffer对象进行了拷贝那么可能会引起原始对象和拷贝对象只是别名而已实际上它们指向的对象是一样的。紧接着对拷贝后的变量进行修改可能会有让你意外的结果。
**译注:** 作者这里说的比较绕,其实有两点:
1. 不管你的method的receiver是指针类型还是非指针类型都是可以通过指针/非指针类型进行调用的,编译器会帮你做类型转换。
2. 在声明一个method的receiver该是指针还是非指针类型时你需要考虑两方面的内部第一方面是这个对象本身是不是特别大如果声明为非指针变量时调用会产生一次拷贝第二方面是如果你用指针类型作为receiver那么你一定要注意这种指针类型指向的始终是一块内存地址就算你对其进行了拷贝。熟悉C或者C的人这里应该很快能明白。
2. 在声明一个method的receiver该是指针还是非指针类型时你需要考虑两方面的因素第一方面是这个对象本身是不是特别大如果声明为非指针变量时调用会产生一次拷贝第二方面是如果你用指针类型作为receiver那么你一定要注意这种指针类型指向的始终是一块内存地址就算你对其进行了拷贝。熟悉C或者C++的人这里应该很快能明白。
{% include "./ch6-02-1.md" %}

View File

@ -39,7 +39,7 @@ func (s *IntSet) UnionWith(t *IntSet) {
}
```
因为每一个字都有64个二进制位所以为了定位x的bit位我们用了x/64的商作为字的下标并且用x%64得到的值作为这个字内的bit的所在位置。UnionWith这个方法里用到了bit位的“或”逻辑操作符号|来一次完成64个元素的或计算。(在练习6.5中我们还会程序用到这个64位字的例子。)
因为每一个字都有64个二进制位所以为了定位x的bit位我们用了x/64的商作为字的下标并且用x%64得到的值作为这个字内的bit的所在位置。UnionWith这个方法里用到了bit位的“或”逻辑操作符号|来一次完成64个元素的或计算。(在练习6.5中我们还会程序用到这个64位字的例子。)
当前这个实现还缺少了很多必要的特性我们把其中一些作为练习题列在本小节之后。但是有一个方法如果缺失的话我们的bit数组可能会比较难混将IntSet作为一个字符串来打印。这里我们来实现它让我们来给上面的例子添加一个String方法类似2.5节中做的那样:
@ -94,7 +94,7 @@ fmt.Println(x.String()) // "{1 9 42 144}"
fmt.Println(x) // "{[4398046511618 0 65536]}"
```
在第一个Println中我们打印一个`*IntSet`的指针这个类型的指针确实有自定义的String方法。第二Println我们直接调用了x变量的String()方法这种情况下编译器会隐式地在x前插入&操作符,这样相当我们还是调用的IntSet指针的String方法。在第三个Println中因为IntSet类型没有String方法所以Println方法会直接以原始的方式理解并打印。所以在这种情况下&符号是不能忘的。在我们这种场景下你把String方法绑定到IntSet对象上而不是IntSet指针上可能会更合适一些不过这也需要具体问题具体分析。
在第一个Println中我们打印一个`*IntSet`的指针这个类型的指针确实有自定义的String方法。第二Println我们直接调用了x变量的String()方法这种情况下编译器会隐式地在x前插入&操作符,这样相当我们还是调用的IntSet指针的String方法。在第三个Println中因为IntSet类型没有String方法所以Println方法会直接以原始的方式理解并打印。所以在这种情况下&符号是不能忘的。在我们这种场景下你把String方法绑定到IntSet对象上而不是IntSet指针上可能会更合适一些不过这也需要具体问题具体分析。
**练习6.1:** 为bit数组实现下面这些方法
@ -107,7 +107,7 @@ func (*IntSet) Copy() *IntSet // return a copy of the set
**练习 6.2** 定义一个变参方法(*IntSet).AddAll(...int)这个方法可以添加一组IntSet比如s.AddAll(1,2,3)。
**练习 6.3** (*IntSet).UnionWith会用|操作符计算两个集合的交集我们再为IntSet实现另外的几个函数IntersectWith(交集元素在A集合B集合均出现),DifferenceWith(差集元素出现在A集合未出现在B集合),SymmetricDifference(并差集元素出现在A但没有出现在B或者出现在B没有出现在A)
**练习 6.3** (*IntSet).UnionWith会用`|`操作符计算两个集合的并集我们再为IntSet实现另外的几个函数IntersectWith交集元素在A集合B集合均出现,DifferenceWith差集元素出现在A集合未出现在B集合SymmetricDifference并差集元素出现在A但没有出现在B或者出现在B没有出现在A
***练习6.4: ** 实现一个Elems方法返回集合中的所有元素用于做一些range之类的遍历操作。

View File

@ -12,13 +12,13 @@ type IntSet struct {
}
```
当然我们也可以把IntSet定义为一个slice类型尽管这样我们就需要把代码中所有方法里用到的s.words用`*s`替换掉了:
当然我们也可以把IntSet定义为一个slice类型这样我们就需要把代码中所有方法里用到的s.words用`*s`替换掉了:
```go
type IntSet []uint64
```
尽管这个版本的IntSet在本质上是一样的他也可以允许其它包中可以直接读取并编辑这个slice。换句话说相对`*s`这个表达式会出现在所有的包中s.words只需要在定义IntSet的包中出现(译注:所以还是推荐后者吧的意思)。
尽管这个版本的IntSet在本质上是一样的但它也允许其它包中可以直接读取并编辑这个slice。换句话说相对`*s`这个表达式会出现在所有的包中s.words只需要在定义IntSet的包中出现(译注:所以还是推荐后者吧的意思)。
这种基于名字的手段使得在语言中最小的封装单元是package而不是像其它语言一样的类型。一个struct类型的字段对同一个包的所有代码都有可见性无论你的代码是写在一个函数还是一个方法里。