回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,8 +1,8 @@
## 6.1. 方法
## 6.1. 方法
在函數聲明時,在其名字之前放上一個變量,是一方法。這個附加的參數會將該函數附加到這種類型上,卽相當於爲這種類型定了一個獨占的方法。
在函数声明时,在其名字之前放上一个变量,是一方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定了一个独占的方法。
下面來寫我們第一方法的例子,這個例子在package geometry下
下面来写我们第一方法的例子,这个例子在package geometry下
<u><i>gopl.io/ch6/geometry</i></u>
```go
@@ -23,11 +23,11 @@ func (p Point) Distance(q Point) float64 {
}
```
上面的代里那附加的參數p叫做方法的接收器(receiver),早期的面向對象語言留下的遺産將調用一方法稱爲“向一個對象發送消息”。
上面的代里那附加的参数p叫做方法的接收器(receiver),早期的面向对象语言留下的遗产将调用一方法称为“向一个对象发送消息”。
在Go言中,我們併不會像其它言那用this或者self作接收器;我可以任意的選擇接收器的名字。由接收器的名字經常會被使用到,所以保持其在方法間傳遞時的一致性和短性是不的主意。里的建是可以使用其型的第一字母,比如里使用了Point的首字母p。
在Go言中,我们并不会像其它言那用this或者self作接收器;我可以任意的选择接收器的名字。由接收器的名字经常会被使用到,所以保持其在方法间传递时的一致性和短性是不的主意。里的建是可以使用其型的第一字母,比如里使用了Point的首字母p。
在方法調用過程中,接收器參數一般在方法名之前出現。這和方法明是一的,都是接收器參數在方法名字之前。下面是例子:
在方法调用过程中,接收器参数一般在方法名之前出现。这和方法明是一的,都是接收器参数在方法名字之前。下面是例子:
```Go
p := Point{1, 2}
@@ -36,11 +36,11 @@ fmt.Println(Distance(p, q)) // "5", function call
fmt.Println(p.Distance(q)) // "5", method call
```
可以看到,上面的兩個函數調用都是Distance但是卻沒有發生衝突。第一Distance的調用實際上用的是包别的函geometry.Distance而第二個則是使用剛剛聲明的Point調用的是Point類下聲明的Point.Distance方法。
可以看到,上面的两个函数调用都是Distance但是却没有发生冲突。第一Distance的调用实际上用的是包别的函geometry.Distance而第二个则是使用刚刚声明的Point用的是Point类下声明的Point.Distance方法。
這種p.Distance的表式叫做選擇器,因爲他會選擇合適的對應p這個對象的Distance方法來執行。選擇器也被用來選擇一個struct型的字段比如p.X。由方法和字段都是在同一命名空,所以如果我們在這里聲明一X方法的編譯器會報錯因爲在調用p.X時會有歧義(譯註:這里確實挺奇怪的)。
这种p.Distance的表式叫做选择器,因为他会选择合适的对应p这个对象的Distance方法来执行。选择器也被用来选择一个struct型的字段比如p.X。由方法和字段都是在同一命名空,所以如果我们在这里声明一X方法的编译器会报错因为在调用p.X时会有歧义(译注:这里确实挺奇怪的)。
爲每種類型都有其方法的命名空,我在用Distance這個名字的不同的Distance調用指向了不同型里的Distance方法。讓我們來定義一個Path型,這個Path代表一個線段的集合,且也給這個Path定義一個叫Distance的方法。
为每种类型都有其方法的命名空,我在用Distance这个名字的不同的Distance用指向了不同型里的Distance方法。让我们来定义一个Path型,这个Path代表一个线段的集合,且也给这个Path定义一个叫Distance的方法。
```Go
// A Path is a journey connecting the points with straight lines.
@@ -57,11 +57,11 @@ func (path Path) Distance() float64 {
}
```
Path是一命名的slice而不是Point那的struct型,然而我依然可以它定方法。在能夠給任意型定方法這一點Go和很多其它的面向象的言不太一。因此在Go言里,我們爲一些簡單的數值、字符串、slice、map來定義一些附加行很方便。方法可以被明到任意型,要不是一個指針或者一interface。
Path是一命名的slice而不是Point那的struct型,然而我依然可以它定方法。在能够给任意型定方法这一点Go和很多其它的面向象的言不太一。因此在Go言里,我们为一些简单的数值、字符串、slice、map来定义一些附加行很方便。方法可以被明到任意型,要不是一个指针或者一interface。
兩個Distance方法有不同的型。他們兩個方法之間沒有任何關繫,盡管Path的Distance方法會在內部調用Point.Distance方法來計算每個連接鄰接點的線段的度。
两个Distance方法有不同的型。他们两个方法之间没有任何关系,尽管Path的Distance方法会在内部调用Point.Distance方法来计算每个连接邻接点的线段的度。
讓我們來調用一新方法,算三角形的週長
让我们来调用一新方法,算三角形的周长
```Go
perim := Path{
@@ -73,9 +73,9 @@ perim := Path{
fmt.Println(perim.Distance()) // "12"
```
在上面兩個對Distance名字的方法的調用中,編譯器會根據方法的名字以及接收器來決定具體調用的是哪一個函數。第一例子中path[i-1]數組中的型是Point因此Point.Distance這個方法被調用;在第二例子中perim的型是Path因此Distance調用的是Path.Distance。
在上面两个对Distance名字的方法的用中,编译器会根据方法的名字以及接收器来决定具体调用的是哪一个函数。第一例子中path[i-1]数组中的型是Point因此Point.Distance这个方法被用;在第二例子中perim的型是Path因此Distance用的是Path.Distance。
對於一個給定的型,其部的方法都必有唯一的方法名,但是不同的類型卻可以有同的方法名,比如我們這里Point和Path就都有Distance這個名字的方法;所以我們沒有必要非在方法名之前加型名消除歧比如PathDistance。里我們已經看到了方法比之函的一些好:方法名可以短。當我們在包外調用的時候這種好處就會被放大,因爲我們可以使用這個短名字,而可以省略掉包的名字,下面是例子:
对于一个给定的型,其部的方法都必有唯一的方法名,但是不同的类型却可以有同的方法名,比如我们这里Point和Path就都有Distance这个名字的方法;所以我们没有必要非在方法名之前加型名消除歧比如PathDistance。里我们已经看到了方法比之函的一些好:方法名可以短。当我们在包外用的时候这种好处就会被放大,因为我们可以使用这个短名字,而可以省略掉包的名字,下面是例子:
```Go
import "gopl.io/ch6/geometry"
@@ -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

@@ -1,6 +1,6 @@
### 6.2.1. Nil也是一合法的接收器
### 6.2.1. Nil也是一合法的接收器
就像一些函數允許nil指針作爲參數一樣,方法理上也可以用nil指針作爲其接收器,尤其nil對於對象來説是合法的零值比如map或者slice。在下面的簡單int表的例子里nil代表的是空表:
就像一些函数允许nil指针作为参数一样,方法理上也可以用nil指针作为其接收器,尤其nil对于对象来说是合法的零值比如map或者slice。在下面的简单int表的例子里nil代表的是空表:
```go
// An IntList is a linked list of integers.
@@ -18,9 +18,9 @@ func (list *IntList) Sum() int {
}
```
你定義一個允許nil作接收器值的方法的類型時,在型前面的註釋中指出nil量代表的意是很有必要的,就像我上面例子里做的這樣
你定义一个允许nil作接收器值的方法的类型时,在型前面的注释中指出nil量代表的意是很有必要的,就像我上面例子里做的这样
下面是net/url包里Values型定的一部分。
下面是net/url包里Values型定的一部分。
<u><i>net/url</i></u>
```go
@@ -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

@@ -1,6 +1,6 @@
## 6.2. 基於指針對象的方法
## 6.2. 基于指针对象的方法
當調用一個函數時,會對其每一個參數值進行拷,如果一個函數需要更新一個變量,或者函的其中一個參數實在太大我希望能避免進行這種默認的拷貝,這種情況下我就需要用到指了。對應到我們這里用更新接收器的象的方法,當這個接受者量本身比較大時,我就可以用其指而不是對象來聲明方法,如下:
当调用一个函数时,会对其每一个参数值进行拷,如果一个函数需要更新一个变量,或者函的其中一个参数实在太大我希望能避免进行这种默认的拷贝,这种情况下我就需要用到指了。对应到我们这里用更新接收器的象的方法,当这个接受者量本身比较大时,我就可以用其指而不是对象来声明方法,如下:
```go
func (p *Point) ScaleBy(factor float64) {
@@ -9,18 +9,18 @@ func (p *Point) ScaleBy(factor float64) {
}
```
這個方法的名字是`(*Point).ScaleBy`里的括是必的;有括號的話這個表達式可能被理解`*(Point.ScaleBy)`
这个方法的名字是`(*Point).ScaleBy`里的括是必的;有括号的话这个表达式可能被理解`*(Point.ScaleBy)`
現實的程序里,一般會約定如果Point這個類有一個指針作爲接收器的方法,那所有Point的方法都必有一個指針接收器,使是那些不需要這個指針接收器的函。我們在這里打破了這個約定隻是爲了展示一下兩種方法的同而已。
现实的程序里,一般会约定如果Point这个类有一个指针作为接收器的方法,那所有Point的方法都必有一个指针接收器,使是那些不需要这个指针接收器的函。我们在这里打破了这个约定只是为了展示一下两种方法的同而已。
隻有類型(Point)和指向他的指(*Point),才是可能會出現在接收器明里的兩種接收器。此外,了避免歧,在明方法,如果一個類型名本身是一個指針的話,是不允其出在接收器中的,比如下面這個例子:
只有类型(Point)和指向他的指(*Point),才是可能会出现在接收器明里的两种接收器。此外,了避免歧,在明方法,如果一个类型名本身是一个指针的话,是不允其出在接收器中的,比如下面这个例子:
```go
type P *int
func (P) f() { /* ... */ } // compile error: invalid receiver type
```
想要調用指針類型方法`(*Point).ScaleBy`要提供一Point型的指針卽可,像下面這樣
想要用指针类型方法`(*Point).ScaleBy`要提供一Point型的指针即可,像下面这样
```go
r := &Point{1, 2}
@@ -28,7 +28,7 @@ r.ScaleBy(2)
fmt.Println(*r) // "{2, 4}"
```
或者這樣
或者这样
```go
p := Point{1, 2}
@@ -36,7 +36,7 @@ pptr := &p
pptr.ScaleBy(2)
fmt.Println(p) // "{2, 4}"
```
或者這樣:
或者这样:
```go
p := Point{1, 2}
@@ -44,51 +44,51 @@ p := Point{1, 2}
fmt.Println(p) // "{2, 4}"
```
過後面兩種方法有些笨拙。幸的是go言本身在這種地方會幫到我。如果接收器p是一Point型的量,且其方法需要一Point指針作爲接收器,我可以用下面這種簡短的法:
过后面两种方法有些笨拙。幸的是go言本身在这种地方会帮到我。如果接收器p是一Point型的量,且其方法需要一Point指针作为接收器,我可以用下面这种简短的法:
```go
p.ScaleBy(2)
```
編譯器會隱式地幫我們&p去調用ScaleBy這個方法。這種簡寫方法隻適用於“變量”包括struct里的字段比如p.X以及array和slice的元素比如perim[0]。我不能通過一個無法取到地址的接收器來調用指方法,比如臨時變量的存地址就無法獲取得到:
编译器会隐式地帮我们&p去用ScaleBy这个方法。这种简写方法只适用于“变量”包括struct里的字段比如p.X以及array和slice的元素比如perim[0]。我不能通过一个无法取到地址的接收器来调用指方法,比如临时变量的存地址就无法获取得到:
```go
Point{1, 2}.ScaleBy(2) // compile error: can't take address of Point literal
```
但是我可以用一`*Point`這樣的接收器來調用Point的方法爲我們可以通地址找到這個變量,要用解引用符`*`取到該變量卽可。編譯器在里也會給我們隱式地插入`*`這個操作符,所以下面這兩種寫法等的:
但是我可以用一`*Point`这样的接收器来调用Point的方法为我们可以通地址找到这个变量,要用解引用符`*`取到该变量即可。编译器在里也会给我们隐式地插入`*`这个操作符,所以下面这两种写法等的:
```Go
pptr.Distance(q)
(*pptr).Distance(q)
```
里的幾個例子可能你有些惑,所以我們總結一下:在每一合法的方法調用表式中,也就是下面三種情況里的任意一種情況都是可以的:
里的几个例子可能你有些惑,所以我们总结一下:在每一合法的方法用表式中,也就是下面三种情况里的任意一种情况都是可以的:
是接收器的實際參數和其接收器的形式參數相同,比如者都是型T或者都是`*T`
是接收器的实际参数和其接收器的形式参数相同,比如者都是型T或者都是`*T`
```go
Point{1, 2}.Distance(q) // Point
pptr.ScaleBy(2) // *Point
```
或者接收器形參是類型T但接收器實參是類`*T`這種情況下編譯器會隱式地爲我們取變量的地址:
或者接收器形参是类型T但接收器实参是类`*T`这种情况下编译器会隐式地为我们取变量的地址:
```go
p.ScaleBy(2) // implicit (&p)
```
或者接收器形參是類`*T`實參是類型T。編譯器會隱式地爲我們解引用,取到指指向的實際變量:
或者接收器形参是类`*T`实参是类型T。编译器会隐式地为我们解引用,取到指指向的实际变量:
```go
pptr.Distance(q) // implicit (*pptr)
```
如果型T的所有方法都是用T型自己做接收器(而不是`*T`),那麽拷貝這種類型的例就是安全的;調用他的任何一方法也就會産生一值的拷。比如time.Duration的這個類型,在調用其方法時就會被全部拷一份,包括在作爲參數傳入函數的時候。但是如果一方法使用指針作爲接收器,你需要避免對其進行拷,因爲這樣可能會破壞掉該類型內部的不性。比如你bytes.Buffer對象進行了拷,那可能引起原始象和拷貝對象隻是别名而已,但實際上其指向的象是一致的。接着對拷貝後的變量進行脩改可能會有讓你意外的果。
如果型T的所有方法都是用T型自己做接收器(而不是`*T`),那么拷贝这种类型的例就是安全的;用他的任何一方法也就会产生一值的拷。比如time.Duration的这个类型,在用其方法时就会被全部拷一份,包括在作为参数传入函数的时候。但是如果一方法使用指针作为接收器,你需要避免对其进行拷,因为这样可能会破坏掉该类型内部的不性。比如你bytes.Buffer对象进行了拷,那可能引起原始象和拷贝对象只是别名而已,但实际上其指向的象是一致的。接着对拷贝后的变量进行修改可能会有让你意外的果。
**譯註** 作者這里説的比較繞,其實有兩點
**译注** 作者这里说的比较绕,其实有两点
1. 不管你的method的receiver是指針類型還是非指針類型,都是可以通過指針/非指針類型進行調用的,編譯器會幫你做類型轉換
2.明一method的receiver是指針還是非指針類型時,你需要考慮兩方面的部,第一方面是這個對象本身是不是特别大,如果聲明爲非指針變量時,調用會産生一次拷;第二方面是如果你用指針類型作receiver你一定要意,這種指針類型指向的始是一塊內存地址,就算你對其進行了拷。熟悉C或者C艹的人這里應該很快能明白。
1. 不管你的method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型进行调用的,编译器会帮你做类型转换
2.明一method的receiver是指针还是非指针类型时,你需要考虑两方面的部,第一方面是这个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷;第二方面是如果你用指针类型作receiver你一定要意,这种指针类型指向的始是一块内存地址,就算你对其进行了拷。熟悉C或者C艹的人这里应该很快能明白。
{% include "./ch6-02-1.md" %}

View File

@@ -1,6 +1,6 @@
## 6.3. 通嵌入結構體來擴展類
## 6.3. 通嵌入结构体来扩展类
看看ColoredPoint這個類型:
看看ColoredPoint这个类型:
<u><i>gopl.io/ch6/coloredpoint</i></u>
```go
@@ -14,7 +14,7 @@ type ColoredPoint struct {
}
```
完全可以ColoredPoint定義爲一個有三字段的struct但是我們卻將Point這個類型嵌入到ColoredPoint提供X和Y這兩個字段。像我在4.4中看到的那樣,內嵌可以使我在定ColoredPoint得到一句法上的簡寫形式,使其包含Point型所具有的一切字段,然再定一些自己的。如果我想要的,我可以直接認爲通過嵌入的字段就是ColoredPoint自身的字段而完全不需要在調用時指出Point比如下面這樣
完全可以ColoredPoint定义为一个有三字段的struct但是我们却将Point这个类型嵌入到ColoredPoint提供X和Y这两个字段。像我在4.4中看到的那样,内嵌可以使我在定ColoredPoint得到一句法上的简写形式,使其包含Point型所具有的一切字段,然再定一些自己的。如果我想要的,我可以直接认为通过嵌入的字段就是ColoredPoint自身的字段而完全不需要在调用时指出Point比如下面这样
```go
var cp ColoredPoint
@@ -24,7 +24,7 @@ cp.Point.Y = 2
fmt.Println(cp.Y) // "2"
```
對於Point中的方法我也有似的用法,我可以把ColoredPoint類型當作接收器來調用Point里的方法使ColoredPoint里沒有聲明這些方法:
对于Point中的方法我也有似的用法,我可以把ColoredPoint类型当作接收器来调用Point里的方法使ColoredPoint里没有声明这些方法:
```go
red := color.RGBA{255, 0, 0, 255}
@@ -37,15 +37,15 @@ q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10"
```
Point的方法也被引入了ColoredPoint。用這種方式,嵌可以使我們定義字段特别多的複雜類型,我可以字段先按小型分,然後定義小類型的方法,之再把它們組合起
Point的方法也被引入了ColoredPoint。用这种方式,嵌可以使我们定义字段特别多的复杂类型,我可以字段先按小型分,然后定义小类型的方法,之再把它们组合起
者如果對基於類來實現面向象的言比熟悉的,可能會傾向於將Point看作一個基類而ColoredPoint看作其子或者繼承類,或者ColoredPoint看作"is a" Point型。但這是錯誤的理解。請註意上面例子中Distance方法的調用。Distance有一個參數是Point但q不是一Point,所以管q有着Point這個內嵌類型,我也必須要顯式地選擇它。嚐試直接q的話你會看到下面這樣的錯誤
者如果对基于类来实现面向象的言比熟悉的,可能会倾向于将Point看作一个基类而ColoredPoint看作其子或者继承类,或者ColoredPoint看作"is a" Point型。但这是错误的理解。请注意上面例子中Distance方法的用。Distance有一个参数是Point但q不是一Point,所以管q有着Point这个内嵌类型,我也必须要显式地选择它。尝试直接q的话你会看到下面这样的错误
```go
p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
```
ColoredPoint不是一Point但他"has a"Point且它有Point里引入的Distance和ScaleBy方法。如果你喜歡從實現的角度來考慮問題,內嵌字段會指導編譯器去生成外的包方法來委託已經聲明好的方法,和下面的形式是等的:
ColoredPoint不是一Point但他"has a"Point且它有Point里引入的Distance和ScaleBy方法。如果你喜欢从实现的角度来考虑问题,内嵌字段会指导编译器去生成外的包方法来委托已经声明好的方法,和下面的形式是等的:
```go
func (p ColoredPoint) Distance(q Point) float64 {
@@ -57,9 +57,9 @@ func (p *ColoredPoint) ScaleBy(factor float64) {
}
```
Point.Distance被第一個包裝方法調用時它的接收器值是p.Point而不是p然了在Point的方法里,你是訪問不到ColoredPoint的任何字段的。
Point.Distance被第一个包装方法调用时它的接收器值是p.Point而不是p然了在Point的方法里,你是访问不到ColoredPoint的任何字段的。
型中嵌的匿名字段也可能是一命名型的指針,這種情況下字段和方法會被間接地引入到前的型中(譯註:訪問需要通過該指針指向的象去取)。添加這一層間接關繫讓我們可以共享通用的結構併動態地改變對象之間的關繫。下面這個ColoredPoint的聲明內嵌了一*Point的指
型中嵌的匿名字段也可能是一命名型的指针,这种情况下字段和方法会被间接地引入到前的型中(译注:访问需要通过该指针指向的象去取)。添加这一层间接关系让我们可以共享通用的结构并动态地改变对象之间的关系。下面这个ColoredPoint的声明内嵌了一*Point的指
```go
type ColoredPoint struct {
@@ -75,7 +75,7 @@ p.ScaleBy(2)
fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"
```
struct型也可能有多匿名字段。我們將ColoredPoint定義爲下面這樣
struct型也可能有多匿名字段。我们将ColoredPoint定义为下面这样
```go
type ColoredPoint struct {
@@ -84,11 +84,11 @@ type ColoredPoint struct {
}
```
後這種類型的值便會擁有Point和RGBA型的所有方法,以及直接定在ColoredPoint中的方法。當編譯器解析一個選擇器到方法比如p.ScaleBy首先去找直接定義在這個類型里的ScaleBy方法找被ColoredPoint的嵌字段引入的方法,然去找Point和RGBA的嵌字段引入的方法,然一直遞歸向下找。如果選擇器有二性的話編譯器會報錯,比如你在同一里有兩個同名的方法。
后这种类型的值便会拥有Point和RGBA型的所有方法,以及直接定在ColoredPoint中的方法。当编译器解析一个选择器到方法比如p.ScaleBy首先去找直接定义在这个类型里的ScaleBy方法找被ColoredPoint的嵌字段引入的方法,然去找Point和RGBA的嵌字段引入的方法,然一直递归向下找。如果选择器有二性的话编译器会报错,比如你在同一里有两个同名的方法。
方法能在命名型(像Point)或者指向型的指上定,但是多虧了內嵌,有些候我們給匿名struct類型來定義方法也有了手段。
方法能在命名型(像Point)或者指向型的指上定,但是多亏了内嵌,有些候我们给匿名struct类型来定义方法也有了手段。
下面是一小trick。這個例子展示了簡單的cache其使用兩個包級别的變量來實現,一mutex互斥量(§9.2)和它所操作的cache
下面是一小trick。这个例子展示了简单的cache其使用两个包级别的变量来实现,一mutex互斥量(§9.2)和它所操作的cache
```go
var (
@@ -104,7 +104,7 @@ func Lookup(key string) string {
}
```
下面這個版本在功能上是一致的,但將兩個包級吧的量放在了cache這個struct一組內
下面这个版本在功能上是一致的,但将两个包级吧的量放在了cache这个struct一组内
```go
var cache = struct {
@@ -123,4 +123,4 @@ func Lookup(key string) string {
}
```
們給新的量起了一更具表性的名字cache。因sync.Mutex字段也被嵌入到了這個struct里其Lock和Unlock方法也就都被引入到了這個匿名結構中了,這讓我們能夠以一個簡單明了的語法來對其進行加鎖解鎖操作。
们给新的量起了一更具表性的名字cache。因sync.Mutex字段也被嵌入到了这个struct里其Lock和Unlock方法也就都被引入到了这个匿名结构中了,这让我们能够以一个简单明了的语法来对其进行加锁解锁操作。

View File

@@ -1,6 +1,6 @@
## 6.4. 方法值和方法表
## 6.4. 方法值和方法表
們經常選擇一個方法,且在同一個表達式里行,比如常的p.Distance()形式,實際上將其分成兩步來執行也是可能的。p.Distance叫作“選擇器”,選擇器會返迴一個方法"值"->一個將方法(Point.Distance)定到特定接收器量的函數。這個函數可以不通指定其接收器可被調用;卽調用時不需要指定接收器(譯註:因爲已經在前文中指定了)隻要傳入函數的參數卽可:
们经常选择一个方法,且在同一个表达式里行,比如常的p.Distance()形式,实际上将其分成两步来执行也是可能的。p.Distance叫作“选择器”,选择器会返回一个方法"值"->一个将方法(Point.Distance)定到特定接收器量的函数。这个函数可以不通指定其接收器可被用;即调用时不需要指定接收器(译注:因为已经在前文中指定了)只要传入函数的参数即可:
```go
p := Point{1, 2}
@@ -17,7 +17,7 @@ scaleP(3) // then (6, 12)
scaleP(10) // then (60, 120)
```
在一包的API需要一個函數值、且調用方希望操作的是某一個綁定了象的方法的,方法"值"非常用(=_=眞是繞)。舉例來説下面例子中的time.AfterFunc這個函數的功能是在指定的延遲時間之後來執行一個(譯註:另外的)函。且這個函數操作的是一Rocket象r
在一包的API需要一个函数值、且用方希望操作的是某一个绑定了象的方法的,方法"值"非常用(=_=真是绕)。举例来说下面例子中的time.AfterFunc这个函数的功能是在指定的延迟时间之后来执行一个(译注:另外的)函。且这个函数操作的是一Rocket象r
```go
type Rocket struct { /* ... */ }
@@ -26,17 +26,17 @@ r := new(Rocket)
time.AfterFunc(10 * time.Second, func() { r.Launch() })
```
直接用方法"值"入AfterFunc的可以更爲簡短:
直接用方法"值"入AfterFunc的可以更为简短:
```go
time.AfterFunc(10 * time.Second, r.Launch)
```
譯註:省掉了上面那例子里的匿名函
译注:省掉了上面那例子里的匿名函
和方法"值"相關的還有方法表式。當調用一方法時,與調用一普通的函相比,我們必須要用選擇器(p.Distance)語法來指定方法的接收器。
和方法"值"相关的还有方法表式。当调用一方法时,与调用一普通的函相比,我们必须要用选择器(p.Distance)语法来指定方法的接收器。
T是一個類型時,方法表式可能會寫作T.f或者(*T).f會返迴一個函數"值",這種函數會將其第一個參數用作接收器,所以可以用通常(譯註:不寫選擇器)的方式來對其進行調用:
T是一个类型时,方法表式可能会写作T.f或者(*T).f会返回一个函数"值",这种函数会将其第一个参数用作接收器,所以可以用通常(译注:不写选择器)的方式来对其进行调用:
```go
p := Point{1, 2}
@@ -51,13 +51,13 @@ scale(&p, 2)
fmt.Println(p) // "{2 4}"
fmt.Printf("%T\n", scale) // "func(*Point, float64)"
// 譯註:這個Distance實際上是指定了Point對象爲接收器的一方法func (p Point) Distance()
// 但通Point.Distance得到的函需要比實際的Distance方法多一個參數
// 其需要用第一個額外參數指定接收器,面排列Distance方法的參數
// 看起來本書中函和方法的别是指有有接收器,而不像其他言那是指有有返值。
// 译注:这个Distance实际上是指定了Point对象为接收器的一方法func (p Point) Distance()
// 但通Point.Distance得到的函需要比实际的Distance方法多一个参数
// 其需要用第一个额外参数指定接收器,面排列Distance方法的参数
// 看起来本书中函和方法的别是指有有接收器,而不像其他言那是指有有返值。
```
你根據一個變量來決定調用同一個類型的哪個函數時,方法表式就得很有用了。你可以根據選擇來調用接收器各不相同的方法。下面的例子,量op代表Point型的addition或者subtraction方法Path.TranslateBy方法會爲其Path數組中的每一Point來調用對應的方法:
你根据一个变量来决定调用同一个类型的哪个函数时,方法表式就得很有用了。你可以根据选择来调用接收器各不相同的方法。下面的例子,量op代表Point型的addition或者subtraction方法Path.TranslateBy方法会为其Path数组中的每一Point来调用对应的方法:
```go
type Point struct{ X, Y float64 }

View File

@@ -1,8 +1,8 @@
## 6.5. 示例: Bit數組
## 6.5. 示例: Bit数组
Go言里的集合一般用map[T]bool這種形式表示T代表元素型。集合用map類型來表示然非常活,但我可以以一更好的形式表示它。例如在數據流分析域,集合元素通常是一個非負整數,集合包含很多元素,且集合會經常進行併集、交集操作,這種情況bit數組會比map表更加理想。(譯註:這里再充一例子,比如我們執行一http下載任務把文件按照16kb一塊劃分爲很多,需要有一全局變量來標識哪些塊下載完成了,這種時候也需要用到bit數組)
Go言里的集合一般用map[T]bool这种形式表示T代表元素型。集合用map类型来表示然非常活,但我可以以一更好的形式表示它。例如在数据流分析域,集合元素通常是一个非负整数,集合包含很多元素,且集合会经常进行并集、交集操作,这种情况bit数组会比map表更加理想。(译注:这里再充一例子,比如我们执行一http下载任务把文件按照16kb一块划分为很多,需要有一全局变量来标识哪些块下载完成了,这种时候也需要用到bit数组)
bit數組通常用一個無符號數或者稱之爲“字”的slice或者表示,每一元素的每一位都表示集合里的一值。集合的第i位被設置時,我們才説這個集合包含元素i。下面的這個程序展示了一個簡單的bit數組類型併且實現了三個函數來對這個bit數組來進行操作:
bit数组通常用一个无符号数或者称之为“字”的slice或者表示,每一元素的每一位都表示集合里的一值。集合的第i位被设置时,我们才说这个集合包含元素i。下面的这个程序展示了一个简单的bit数组类型并且实现了三个函数来对这个bit数组来进行操作:
<u><i>gopl.io/ch6/intset</i></u>
```go
@@ -39,9 +39,9 @@ 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中做的那
当前这个实现还缺少了很多必要的特性,我把其中一些作为练习题列在本小节之后。但是有一方法如果缺失的话我们的bit数组可能会比较难混:IntSet作为一个字符串打印。里我们来实现它,让我们来给上面的例子添加一String方法似2.5中做的那
```go
// String returns the set as a string of the form "{1 2 3}".
@@ -66,9 +66,9 @@ func (s *IntSet) String() string {
}
```
里留意一下String方法是不是和3.5.4中的intsToString方法很相似bytes.Buffer在String方法里經常這麽用。當你爲一個複雜的類型定了一String方法fmt包就特殊對待這種類型的值,這樣可以讓這些類型在打印的候看起更加友好而不是直接打印其原始的值。fmt直接調用用戶定義的String方法。這種機製依賴於接口和類型斷在第7章中我們會詳細介紹
里留意一下String方法是不是和3.5.4中的intsToString方法很相似bytes.Buffer在String方法里经常这么用。当你为一个复杂的类型定了一String方法fmt包就特殊对待这种类型的值,这样可以让这些类型在打印的候看起更加友好而不是直接打印其原始的值。fmt直接用用户定义的String方法。这种机制依赖于接口和类型断在第7章中我们会详细介绍
在我就可以在實戰中直接用上面定好的IntSet了
在我就可以在实战中直接用上面定好的IntSet了
```go
var x, y IntSet
@@ -86,7 +86,7 @@ fmt.Println(x.String()) // "{1 9 42 144}"
fmt.Println(x.Has(9), x.Has(123)) // "true false"
```
里要意:我們聲明的String和Has兩個方法都是以指針類型*IntSet來作爲接收器的,但實際上對於這兩個類型來説,把接收器聲明爲指針類型也沒什麽必要。不另外兩個函數就不是這樣了,因另外兩個函數操作的是s.words象,如果你不把接收器聲明爲指針對象,那麽實際操作的是拷貝對象,而不是原的那個對象。因此,因爲我們的String方法定在IntSet指上,所以當我們的變量是IntSet型而不是IntSet指針時,可能有下面這樣讓人意外的情
里要意:我们声明的String和Has两个方法都是以指针类型*IntSet来作为接收器的,但实际上对于这两个类型来说,把接收器声明为指针类型也没什么必要。不另外两个函数就不是这样了,因另外两个函数操作的是s.words象,如果你不把接收器声明为指针对象,那么实际操作的是拷贝对象,而不是原的那个对象。因此,因为我们的String方法定在IntSet指上,所以当我们的变量是IntSet型而不是IntSet指针时,可能有下面这样让人意外的情
```go
fmt.Println(&x) // "{1 9 42 144}"
@@ -94,9 +94,9 @@ 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數組實現下面些方法
练习6.1: bit数组实现下面些方法
```go
func (*IntSet) Len() int // return the number of elements
@@ -105,9 +105,9 @@ func (*IntSet) Clear() // remove all elements from the set
func (*IntSet) Copy() *IntSet // return a copy of the set
```
**練習 6.2**義一個變參方法(*IntSet).AddAll(...int)這個方法可以爲一組IntSet值求和比如s.AddAll(1,2,3)。
**练习 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.4: 實現一個Elems方法集合中的所有元素,用做一些range之的遍操作。
**练习 6.3** (*IntSet).UnionWith用|操作符计算两个集合的交集,我们再为IntSet实现另外的几个函数IntersectWith(交集元素在A集合B集合均出),DifferenceWith(差集:元素出在A集合未出在B集合),SymmetricDifference(差集:元素出在A但有出在B或者出在B有出在A)。
练习6.4: 实现一个Elems方法集合中的所有元素,用做一些range之的遍操作。
**練習 6.5**們這章定的IntSet里的每字都是用的uint64但是64位的值可能在32位的平台上不高效。改程序使其使用uint型,這種類型對於32位平台來説更合適。當然了,里我可以不用簡單粗暴地除64可以定義一個常量來決定是用32是64里你可能用到平台的自動判斷的一智能表32 << (^uint(0) >> 63)
**练习 6.5**们这章定的IntSet里的每字都是用的uint64但是64位的值可能在32位的平台上不高效。改程序使其使用uint型,这种类型对于32位平台来说更合适。当然了,里我可以不用简单粗暴地除64可以定义一个常量来决定是用32是64里你可能用到平台的自动判断的一智能表32 << (^uint(0) >> 63)

View File

@@ -1,10 +1,10 @@
## 6.6. 封
## 6.6. 封
個對象的量或者方法如果對調用方是不可見的話,一般就被定義爲“封”。封裝有時候也被叫做信息藏,同也是面向對象編程最關鍵的一方面。
个对象的量或者方法如果对调用方是不可见的话,一般就被定义为“封”。封装有时候也被叫做信息藏,同也是面向对象编程最关键的一方面。
Go語言隻有一種控製可見性的手段:大首字母的標識符會從定義它們的包中被出,小字母的則不會。這種限製包內成員的方式同樣適用於struct或者一個類型的方法。因而如果我想要封裝一個對象,我們必須將其定義爲一個struct。
Go语言只有一种控制可见性的手段:大首字母的标识符会从定义它们的包中被出,小字母的则不会。这种限制包内成员的方式同样适用于struct或者一个类型的方法。因而如果我想要封装一个对象,我们必须将其定义为一个struct。
也就是前面的小中IntSet被定義爲struct型的原因,管它有一字段:
也就是前面的小中IntSet被定义为struct型的原因,管它有一字段:
```go
type IntSet struct {
@@ -12,21 +12,21 @@ 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型的字段同一包的所有代都有可性,無論你的代碼是寫在一個函數還是一方法里。
这种基于名字的手段使得在言中最小的封装单元是package而不是像其它言一样的类型。一struct型的字段同一包的所有代都有可性,无论你的代码是写在一个函数还是一方法里。
提供了三方面的優點。首先,因爲調用方不能直接脩改對象的量值,其需要關註少量的語句併且隻要弄懂少量量的可能的值可。
提供了三方面的优点。首先,因为调用方不能直接修改对象的量值,其需要关注少量的语句并且只要弄懂少量量的可能的值可。
第二,隱藏實現的細節,可以防止調用方依那些可能化的具體實現,這樣使設計包的程序在不破壞對外的api情下能得到更大的自由。
第二,隐藏实现的细节,可以防止用方依那些可能化的具体实现,这样使设计包的程序在不破坏对外的api情下能得到更大的自由。
把bytes.Buffer這個類型作例子來考慮。這個類型在做短字符串加的候很常用,所以在設計的時候可以做一些先的化,比如提前留一部分空間,來避免反複的內存分配。又因Buffer是一struct型,這些額外的空可以用附加的字節數組來保存,且放在一個小寫字母開頭的字段中。這樣在外部的調用方能看到性能的提,但併不會得到這個附加量。Buffer和其增算法我列在里,爲了簡潔性稍微做了一些精
把bytes.Buffer这个类型作例子来考虑。这个类型在做短字符串加的候很常用,所以在设计的时候可以做一些先的化,比如提前留一部分空间,来避免反复的内存分配。又因Buffer是一struct型,这些额外的空可以用附加的字节数组来保存,且放在一个小写字母开头的字段中。这样在外部的用方能看到性能的提,但并不会得到这个附加量。Buffer和其增算法我列在里,为了简洁性稍微做了一些精
```go
type Buffer struct {
@@ -49,7 +49,7 @@ func (b *Buffer) Grow(n int) {
}
```
的第三個優點也是最重要的優點,是阻止了外部調用方對對象內部的值任意地進行脩改。因爲對象內部變量隻可以被同一個包內的函數脩改,所以包的作者可以讓這些函數確保對象內部的一些值的不性。比如下面的Counter型允許調用方增加counter量的值,且允許將這個值reset0但是不允許隨便設置這個值(譯註:因爲壓根就訪問不到)
的第三个优点也是最重要的优点,是阻止了外部用方对对象内部的值任意地进行修改。因为对象内部变量只可以被同一个包内的函数修改,所以包的作者可以让这些函数确保对象内部的一些值的不性。比如下面的Counter型允许调用方增加counter量的值,且允许将这个值reset0但是不允许随便设置这个值(译注:因为压根就访问不到)
```go
type Counter struct { n int }
@@ -58,7 +58,7 @@ func (c *Counter) Increment() { c.n++ }
func (c *Counter) Reset() { c.n = 0 }
```
隻用來訪問或脩改內部變量的函數被稱爲setter或者getter例子如下比如log包里的Logger類型對應的一些函。在命名一getter方法,我通常省略掉前面的Get前綴。這種簡潔上的偏好也可以推到各種類型的前比如FetchFind或者Lookup。
只用来访问或修改内部变量的函数被称为setter或者getter例子如下比如log包里的Logger类型对应的一些函。在命名一getter方法,我通常省略掉前面的Get前缀。这种简洁上的偏好也可以推广到各种类型的前比如FetchFind或者Lookup。
```go
package log
@@ -73,18 +73,18 @@ func (l *Logger) Prefix() string
func (l *Logger) SetPrefix(prefix string)
```
Go的編碼風格不禁止直接出字段。然,一旦行了出,就沒有辦法在保API兼容的情下去除其的出,所以在一始的選擇一定要經過深思熟慮併且要考到包部的一些不量的保,未可能的化,以及調用方的代碼質量是否會因爲包的一點脩改而差。
Go的编码风格不禁止直接出字段。然,一旦行了出,就没有办法在保API兼容的情下去除其的出,所以在一始的选择一定要经过深思熟虑并且要考到包部的一些不量的保,未可能的化,以及用方的代码质量是否会因为包的一点修改而差。
裝併不總是理想的。
然封在有些情是必要的,但有候我也需要暴露一些內部內比如time.Duration其表暴露爲一個int64字的秒,使得我可以用一般的值操作來對時間進行對比,甚至可以定義這種類型的常量:
装并不总是理想的。
然封在有些情是必要的,但有候我也需要暴露一些内部内比如time.Duration其表暴露为一个int64字的秒,使得我可以用一般的值操作来对时间进行对比,甚至可以定义这种类型的常量:
```go
const day = 24 * time.Hour
fmt.Println(day.Seconds()) // "86400"
```
另一例子,IntSet和本章開頭的geometry.Path進行對比。Path被定義爲一個slice型,這允許其調用slice的字面方法來對其內部的points用range行迭代遍;在這一點IntSet是沒有辦法讓你這麽做的。
另一例子,IntSet和本章开头的geometry.Path进行对比。Path被定义为一个slice型,这允许其调用slice的字面方法来对其内部的points用range行迭代遍;在这一点IntSet是没有办法让你这么做的。
這兩種類型決定性的不同geometry.Path的本是一個坐標點的序列,不多也不少,我可以預見到之後也併不會給他增加外的字段所以在geometry包中Path暴露爲一個slice。相比之下IntSet僅僅是在里用了一[]uint64的slice。這個類型還可以用[]uint類型來表示,或者我甚至可以用其它完全不同的占用更小存空間的東西來表示這個集合,所以我可能還會需要外的字段來在這個類型中記録元素的個數。也正是因爲這些原因,我們讓IntSet對調用方透明。
这两种类型决定性的不同geometry.Path的本是一个坐标点的序列,不多也不少,我可以预见到之后也并不会给他增加外的字段所以在geometry包中Path暴露为一个slice。相比之下IntSet仅仅是在里用了一[]uint64的slice。这个类型还可以用[]uint类型来表示,或者我甚至可以用其它完全不同的占用更小存空间的东西来表示这个集合,所以我可能还会需要外的字段来在这个类型中记录元素的个数。也正是因为这些原因,我们让IntSet对调用方透明。
章中,我們學到了如何方法命名類型進行組合,且知道了如何調用這些方法。管方法對於OOP編程來説至關重要,但他們隻是OOP程里的半天。了完成OOP們還需要接口。Go里的接口在下一章中介
章中,我们学到了如何方法命名类型进行组合,且知道了如何调用这些方法。管方法对于OOP编程来说至关重要,但他们只是OOP程里的半天。了完成OOP们还需要接口。Go里的接口在下一章中介

View File

@@ -1,20 +1,20 @@
# 第六章 方法
90年代早期始,面向對象編程(OOP)就成爲了稱霸工程界和育界的程范式,所以之後幾乎所有大模被用的言都包含了OOP的支持go言也不例外。
90年代早期始,面向对象编程(OOP)就成为了称霸工程界和育界的程范式,所以之后几乎所有大模被用的言都包含了OOP的支持go言也不例外。
盡管沒有被大所接受的明的OOP的定義,從我們的理解來講,一個對象其也就是一個簡單的值或者一個變量,在這個對象中包含一些方法,而一方法是一個一個和特殊類型關聯的函。一面向象的程序用方法來表達其屬性和對應的操作,這樣使用這個對象的用就不需要直接去操作象,而是借助方法來做這些事情。
尽管没有被大所接受的明的OOP的定义,从我们的理解来讲,一个对象其也就是一个简单的值或者一个变量,在这个对象中包含一些方法,而一方法是一个一个和特殊类型关联的函。一面向象的程序用方法来表达其属性和对应的操作,这样使用这个对象的用就不需要直接去操作象,而是借助方法来做这些事情。
在早些的章中,我們已經使用了標準庫提供的一些方法比如time.Duration這個類型的Seconds方法
在早些的章中,我们已经使用了标准库提供的一些方法比如time.Duration这个类型的Seconds方法
```Go
const day = 24 * time.Hour
fmt.Println(day.Seconds()) // "86400"
```
且在2.5中,我們定義了一自己的方法Celsius型的String方法:
且在2.5中,我们定义了一自己的方法Celsius型的String方法:
```Go
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
```
在本章中OOP程的第一方面,我們會向你展示如何有效地定和使用方法。我們會覆蓋到OOP程的兩個關鍵點,封裝和組合。
在本章中OOP程的第一方面,我们会向你展示如何有效地定和使用方法。我们会覆盖到OOP程的两个关键点,封装和组合。