在 Go 語言中,結(jié)構(gòu)體就像是類的一種簡化形式,那么面向?qū)ο蟪绦騿T可能會(huì)問:類的方法在哪里呢?在 Go 中有一個(gè)概念,它和方法有著同樣的名字,并且大體上意思相同:Go 方法是作用在接收者(receiver)上的一個(gè)函數(shù),接收者是某種類型的變量。因此方法是一種特殊類型的函數(shù)。
接收者類型可以是(幾乎)任何類型,不僅僅是結(jié)構(gòu)體類型:任何類型都可以有方法,甚至可以是函數(shù)類型,可以是 int、bool、string 或數(shù)組的別名類型。但是接收者不能是一個(gè)接口類型(參考 第 11 章),因?yàn)榻涌谑且粋€(gè)抽象定義,但是方法卻是具體實(shí)現(xiàn);如果這樣做會(huì)引發(fā)一個(gè)編譯錯(cuò)誤:invalid receiver type…。
最后接收者不能是一個(gè)指針類型,但是它可以是任何其他允許類型的指針。
一個(gè)類型加上它的方法等價(jià)于面向?qū)ο笾械囊粋€(gè)類。一個(gè)重要的區(qū)別是:在 Go 中,類型的代碼和綁定在它上面的方法的代碼可以不放置在一起,它們可以存在在不同的源文件,唯一的要求是:它們必須是同一個(gè)包的。
類型 T(或 *T)上的所有方法的集合叫做類型 T(或 *T)的方法集。
因?yàn)榉椒ㄊ呛瘮?shù),所以同樣的,不允許方法重載,即對于一個(gè)類型只能有一個(gè)給定名稱的方法。但是如果基于接收者類型,是有重載的:具有同樣名字的方法可以在 2 個(gè)或多個(gè)不同的接收者類型上存在,比如在同一個(gè)包里這么做是允許的:
func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix
別名類型不能有它原始類型上已經(jīng)定義過的方法。
定義方法的一般格式如下:
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
在方法名之前,func
關(guān)鍵字之后的括號(hào)中指定 receiver。
如果 recv
是 receiver 的實(shí)例,Method1 是它的方法名,那么方法調(diào)用遵循傳統(tǒng)的 object.name
選擇器符號(hào):recv.Method1()。
如果 recv
是一個(gè)指針,Go 會(huì)自動(dòng)解引用。
如果方法不需要使用 recv
的值,可以用 _ 替換它,比如:
func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }
recv
就像是面向?qū)ο笳Z言中的 this
或 self
,但是 Go 中并沒有這兩個(gè)關(guān)鍵字。隨個(gè)人喜好,你可以使用 this
或 self
作為 receiver 的名字。下面是一個(gè)結(jié)構(gòu)體上的簡單方法的例子:
示例 10.10 method .go:
package main
import "fmt"
type TwoInts struct {
a int
b int
}
func main() {
two1 := new(TwoInts)
two1.a = 12
two1.b = 10
fmt.Printf("The sum is: %d\n", two1.AddThem())
fmt.Printf("Add them to the param: %d\n", two1.AddToParam(20))
two2 := TwoInts{3, 4}
fmt.Printf("The sum is: %d\n", two2.AddThem())
}
func (tn *TwoInts) AddThem() int {
return tn.a + tn.b
}
func (tn *TwoInts) AddToParam(param int) int {
return tn.a + tn.b + param
}
輸出:
The sum is: 22
Add them to the param: 42
The sum is: 7
下面是非結(jié)構(gòu)體類型上方法的例子:
示例 10.11 method2.go:
package main
import "fmt"
type IntVector []int
func (v IntVector) Sum() (s int) {
for _, x := range v {
s += x
}
return
}
func main() {
fmt.Println(IntVector{1, 2, 3}.Sum()) // 輸出是6
}
練習(xí) 10.6 employee_salary.go
定義結(jié)構(gòu)體 employee
,它有一個(gè) salary
字段,給這個(gè)結(jié)構(gòu)體定義一個(gè)方法 giveRaise
來按照指定的百分比增加薪水。
練習(xí) 10.7 iteration_list.go
下面這段代碼有什么錯(cuò)?
package main
import "container/list"
func (p *list.List) Iter() {
// ...
}
func main() {
lst := new(list.List)
for _= range lst.Iter() {
}
}
類型和作用在它上面定義的方法必須在同一個(gè)包里定義,這就是為什么不能在 int、float 或類似這些的類型上定義方法。試圖在 int 類型上定義方法會(huì)得到一個(gè)編譯錯(cuò)誤:
cannot define new methods on non-local type int
比如想在 time.Time
上定義如下方法:
func (t time.Time) first3Chars() string {
return time.LocalTime().String()[0:3]
}
類型在其他的,或是非本地的包里定義,在它上面定義方法都會(huì)得到和上面同樣的錯(cuò)誤。
但是有一個(gè)間接的方式:可以先定義該類型(比如:int 或 float)的別名類型,然后再為別名類型定義方法?;蛘呦裣旅孢@樣將它作為匿名類型嵌入在一個(gè)新的結(jié)構(gòu)體中。當(dāng)然方法只在這個(gè)別名類型上有效。
示例 10.12 method_on_time.go:
package main
import (
"fmt"
"time"
)
type myTime struct {
time.Time //anonymous field
}
func (t myTime) first3Chars() string {
return t.Time.String()[0:3]
}
func main() {
m := myTime{time.Now()}
// 調(diào)用匿名Time上的String方法
fmt.Println("Full time now:", m.String())
// 調(diào)用myTime.first3Chars
fmt.Println("First 3 chars:", m.first3Chars())
}
/* Output:
Full time now: Mon Oct 24 15:34:54 Romance Daylight Time 2011
First 3 chars: Mon
*/
函數(shù)將變量作為參數(shù):Function1(recv)
方法在變量上被調(diào)用:recv.Method1()
在接收者是指針時(shí),方法可以改變接收者的值(或狀態(tài)),這點(diǎn)函數(shù)也可以做到(當(dāng)參數(shù)作為指針傳遞,即通過引用調(diào)用時(shí),函數(shù)也可以改變參數(shù)的狀態(tài))。
不要忘記 Method1 后邊的括號(hào) (),否則會(huì)引發(fā)編譯器錯(cuò)誤:method recv.Method1 is not an expression, must be called
接收者必須有一個(gè)顯式的名字,這個(gè)名字必須在方法中被使用。
receiver_type 叫做 (接收者)基本類型,這個(gè)類型必須在和方法同樣的包中被聲明。
在 Go 中,(接收者)類型關(guān)聯(lián)的方法不寫在類型結(jié)構(gòu)里面,就像類那樣;耦合更加寬松;類型和方法之間的關(guān)聯(lián)由接收者來建立。
方法沒有和數(shù)據(jù)定義(結(jié)構(gòu)體)混在一起:它們是正交的類型;表示(數(shù)據(jù))和行為(方法)是獨(dú)立的。
鑒于性能的原因,recv
最常見的是一個(gè)指向 receiver_type 的指針(因?yàn)槲覀儾幌胍粋€(gè)實(shí)例的拷貝,如果按值調(diào)用的話就會(huì)是這樣),特別是在 receiver 類型是結(jié)構(gòu)體時(shí),就更是如此了。
如果想要方法改變接收者的數(shù)據(jù),就在接收者的指針類型上定義該方法。否則,就在普通的值類型上定義方法。
下面的例子 pointer_value.go
作了說明:change()
接受一個(gè)指向 B 的指針,并改變它內(nèi)部的成員;write()
通過拷貝接受 B 的值并只輸出B的內(nèi)容。注意 Go 為我們做了探測工作,我們自己并沒有指出是否在指針上調(diào)用方法,Go 替我們做了這些事情。b1 是值而 b2 是指針,方法都支持運(yùn)行了。
示例 10.13 pointer_value.go:
package main
import (
"fmt"
)
type B struct {
thing int
}
func (b *B) change() { b.thing = 1 }
func (b B) write() string { return fmt.Sprint(b) }
func main() {
var b1 B // b1是值
b1.change()
fmt.Println(b1.write())
b2 := new(B) // b2是指針
b2.change()
fmt.Println(b2.write())
}
/* 輸出:
{1}
{1}
*/
試著在 write()
中改變接收者b的值:將會(huì)看到它可以正常編譯,但是開始的 b 沒有被改變。
我們知道方法將指針作為接收者不是必須的,如下面的例子,我們只是需要 Point3
的值來做計(jì)算:
type Point3 struct { x, y, z float64 }
// A method on Point3
func (p Point3) Abs() float64 {
return math.Sqrt(p.x*p.x + p.y*p.y + p.z*p.z)
}
這樣做稍微有點(diǎn)昂貴,因?yàn)?Point3
是作為值傳遞給方法的,因此傳遞的是它的拷貝,這在 Go 中是合法的。也可以在指向這個(gè)類型的指針上調(diào)用此方法(會(huì)自動(dòng)解引用)。
假設(shè) p3
定義為一個(gè)指針:p3 := &Point{ 3, 4, 5}
。
可以使用 p3.Abs()
來替代 (*p3).Abs()
。
像例子 10.10(method1.go)中接收者類型是 *TwoInts
的方法 AddThem()
,它能在類型 TwoInts
的值上被調(diào)用,這是自動(dòng)間接發(fā)生的。
因此 two2.AddThem
可以替代 (&two2).AddThem()
。
在值和指針上調(diào)用方法:
可以有連接到類型的方法,也可以有連接到類型指針的方法。
但是這沒關(guān)系:對于類型 T,如果在 *T 上存在方法 Meth()
,并且 t
是這個(gè)類型的變量,那么 t.Meth()
會(huì)被自動(dòng)轉(zhuǎn)換為 (&t).Meth()
。
指針方法和值方法都可以在指針或非指針上被調(diào)用,如下面程序所示,類型 List
在值上有一個(gè)方法 Len()
,在指針上有一個(gè)方法 Append()
,但是可以看到兩個(gè)方法都可以在兩種類型的變量上被調(diào)用。
示例 10.14 methodset1.go:
package main
import (
"fmt"
)
type List []int
func (l List) Len() int { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }
func main() {
// 值
var lst List
lst.Append(1)
fmt.Printf("%v (len: %d)", lst, lst.Len()) // [1] (len: 1)
// 指針
plst := new(List)
plst.Append(2)
fmt.Printf("%v (len: %d)", plst, plst.Len()) // &[2] (len: 1)
}
考慮 person2.go
中的 person
包:類型 Person
被明確的導(dǎo)出了,但是它的字段沒有被導(dǎo)出。例如在 use_person2.go
中 p.firstName
就是錯(cuò)誤的。該如何在另一個(gè)程序中修改或者只是讀取一個(gè) Person
的名字呢?
這可以通過面向?qū)ο笳Z言一個(gè)眾所周知的技術(shù)來完成:提供 getter 和 setter 方法。對于 setter 方法使用 Set 前綴,對于 getter 方法只使用成員名。
示例 10.15 person2.go:
package person
type Person struct {
firstName string
lastName string
}
func (p *Person) FirstName() string {
return p.firstName
}
func (p *Person) SetFirstName(newName string) {
p.firstName = newName
}
示例 10.16—use_person2.go:
package main
import (
"./person"
"fmt"
)
func main() {
p := new(person.Person)
// p.firstName undefined
// (cannot refer to unexported field or method firstName)
// p.firstName = "Eric"
p.SetFirstName("Eric")
fmt.Println(p.FirstName()) // Output: Eric
}
并發(fā)訪問對象
對象的字段(屬性)不應(yīng)該由 2 個(gè)或 2 個(gè)以上的不同線程在同一時(shí)間去改變。如果在程序發(fā)生這種情況,為了安全并發(fā)訪問,可以使用包 sync
(參考第 9.3 節(jié))中的方法。在第 14.17 節(jié)中我們會(huì)通過 goroutines 和 channels 探索另一種方式。
當(dāng)一個(gè)匿名類型被內(nèi)嵌在結(jié)構(gòu)體中時(shí),匿名類型的可見方法也同樣被內(nèi)嵌,這在效果上等同于外層類型 繼承 了這些方法:將父類型放在子類型中來實(shí)現(xiàn)亞型。這個(gè)機(jī)制提供了一種簡單的方式來模擬經(jīng)典面向?qū)ο笳Z言中的子類和繼承相關(guān)的效果,也類似 Ruby 中的混入(mixin)。
下面是一個(gè)示例(可以在練習(xí) 10.8 中進(jìn)一步學(xué)習(xí)):假定有一個(gè) Engine
接口類型,一個(gè) Car
結(jié)構(gòu)體類型,它包含一個(gè) Engine
類型的匿名字段:
type Engine interface {
Start()
Stop()
}
type Car struct {
Engine
}
我們可以構(gòu)建如下的代碼:
func (c *Car) GoToWorkIn() {
// get in car
c.Start()
// drive to work
c.Stop()
// get out of car
}
下面是 method3.go
的完整例子,它展示了內(nèi)嵌結(jié)構(gòu)體上的方法可以直接在外層類型的實(shí)例上調(diào)用:
package main
import (
"fmt"
"math"
)
type Point struct {
x, y float64
}
func (p *Point) Abs() float64 {
return math.Sqrt(p.x*p.x + p.y*p.y)
}
type NamedPoint struct {
Point
name string
}
func main() {
n := &NamedPoint{Point{3, 4}, "Pythagoras"}
fmt.Println(n.Abs()) // 打印5
}
內(nèi)嵌將一個(gè)已存在類型的字段和方法注入到了另一個(gè)類型里:匿名字段上的方法“晉升”成為了外層類型的方法。當(dāng)然類型可以有只作用于本身實(shí)例而不作用于內(nèi)嵌“父”類型上的方法,
可以覆寫方法(像字段一樣):和內(nèi)嵌類型方法具有同樣名字的外層類型的方法會(huì)覆寫內(nèi)嵌類型對應(yīng)的方法。
在示例 10.18 method4.go 中添加:
func (n *NamedPoint) Abs() float64 {
return n.Point.Abs() * 100.
}
現(xiàn)在 fmt.Println(n.Abs())
會(huì)打印 500
。
因?yàn)橐粋€(gè)結(jié)構(gòu)體可以嵌入多個(gè)匿名類型,所以實(shí)際上我們可以有一個(gè)簡單版本的多重繼承,就像:type Child struct { Father; Mother}
。在第 10.6.7 節(jié)中會(huì)進(jìn)一步討論這個(gè)問題。
結(jié)構(gòu)體內(nèi)嵌和自己在同一個(gè)包中的結(jié)構(gòu)體時(shí),可以彼此訪問對方所有的字段和方法。
練習(xí) 10.8 inheritance_car.go
創(chuàng)建一個(gè)上面 Car
和 Engine
可運(yùn)行的例子,并且給 Car
類型一個(gè) wheelCount
字段和一個(gè) numberOfWheels()
方法。
創(chuàng)建一個(gè) Mercedes
類型,它內(nèi)嵌 Car
,并新建 Mercedes
的一個(gè)實(shí)例,然后調(diào)用它的方法。
然后僅在 Mercedes
類型上創(chuàng)建方法 sayHiToMerkel()
并調(diào)用它。
主要有兩種方法來實(shí)現(xiàn)在類型中嵌入功能:
A:聚合(或組合):包含一個(gè)所需功能類型的具名字段。
B:內(nèi)嵌:內(nèi)嵌(匿名地)所需功能類型,像前一節(jié) 10.6.5 所演示的那樣。
為了使這些概念具體化,假設(shè)有一個(gè) Customer
類型,我們想讓它通過 Log
類型來包含日志功能,Log
類型只是簡單地包含一個(gè)累積的消息(當(dāng)然它可以是復(fù)雜的)。如果想讓特定類型都具備日志功能,你可以實(shí)現(xiàn)一個(gè)這樣的 Log
類型,然后將它作為特定類型的一個(gè)字段,并提供 Log()
,它返回這個(gè)日志的引用。
方式 A 可以通過如下方法實(shí)現(xiàn)(使用了第 10.7 節(jié)中的 String()
功能):
示例 10.19 embed_func1.go:
package main
import (
"fmt"
)
type Log struct {
msg string
}
type Customer struct {
Name string
log *Log
}
func main() {
c := new(Customer)
c.Name = "Barak Obama"
c.log = new(Log)
c.log.msg = "1 - Yes we can!"
// shorter
c = &Customer{"Barak Obama", &Log{"1 - Yes we can!"}}
// fmt.Println(c) &{Barak Obama 1 - Yes we can!}
c.Log().Add("2 - After me the world will be a better place!")
//fmt.Println(c.log)
fmt.Println(c.Log())
}
func (l *Log) Add(s string) {
l.msg += "\n" + s
}
func (l *Log) String() string {
return l.msg
}
func (c *Customer) Log() *Log {
return c.log
}
輸出:
1 - Yes we can!
2 - After me the world will be a better place!
相對的方式 B 可能會(huì)像這樣:
package main
import (
"fmt"
)
type Log struct {
msg string
}
type Customer struct {
Name string
Log
}
func main() {
c := &Customer{"Barak Obama", Log{"1 - Yes we can!"}}
c.Add("2 - After me the world will be a better place!")
fmt.Println(c)
}
func (l *Log) Add(s string) {
l.msg += "\n" + s
}
func (l *Log) String() string {
return l.msg
}
func (c *Customer) String() string {
return c.Name + "\nLog:" + fmt.Sprintln(c.Log)
}
輸出:
Barak Obama
Log:{1 - Yes we can!
2 - After me the world will be a better place!}
內(nèi)嵌的類型不需要指針,Customer
也不需要 Add
方法,它使用 Log
的 Add
方法,Customer
有自己的 String
方法,并且在它里面調(diào)用了 Log
的 String
方法。
如果內(nèi)嵌類型嵌入了其他類型,也是可以的,那些類型的方法可以直接在外層類型中使用。
因此一個(gè)好的策略是創(chuàng)建一些小的、可復(fù)用的類型作為一個(gè)工具箱,用于組成域類型。
多重繼承指的是類型獲得多個(gè)父類型行為的能力,它在傳統(tǒng)的面向?qū)ο笳Z言中通常是不被實(shí)現(xiàn)的(C++ 和 Python 例外)。因?yàn)樵陬惱^承層次中,多重繼承會(huì)給編譯器引入額外的復(fù)雜度。但是在 Go 語言中,通過在類型中嵌入所有必要的父類型,可以很簡單的實(shí)現(xiàn)多重繼承。
作為一個(gè)例子,假設(shè)有一個(gè)類型 CameraPhone
,通過它可以 Call()
,也可以 TakeAPicture()
,但是第一個(gè)方法屬于類型 Phone
,第二個(gè)方法屬于類型 Camera
。
只要嵌入這兩個(gè)類型就可以解決這個(gè)問題,如下所示:
package main
import (
"fmt"
)
type Camera struct{}
func (c *Camera) TakeAPicture() string {
return "Click"
}
type Phone struct{}
func (p *Phone) Call() string {
return "Ring Ring"
}
type CameraPhone struct {
Camera
Phone
}
func main() {
cp := new(CameraPhone)
fmt.Println("Our new CameraPhone exhibits multiple behaviors...")
fmt.Println("It exhibits behavior of a Camera: ", cp.TakeAPicture())
fmt.Println("It works like a Phone too: ", cp.Call())
}
輸出:
Our new CameraPhone exhibits multiple behaviors...
It exhibits behavior of a Camera: Click
It works like a Phone too: Ring Ring
練習(xí) 10.9 point_methods.go:
從 point.go
開始(第 10.1 節(jié)的練習(xí)):使用方法來實(shí)現(xiàn) Abs()
和 Scale()
函數(shù),Point
作為方法的接收者類型。也為 Point3
和 Polar
實(shí)現(xiàn) Abs()
方法。完成了 point.go
中同樣的事情,只是這次通過方法。
練習(xí) 10.10 inherit_methods.go:
定義一個(gè)結(jié)構(gòu)體類型 Base
,它包含一個(gè)字段 id
,方法 Id()
返回 id
,方法 SetId()
修改 id
。結(jié)構(gòu)體類型 Person
包含 Base
,及 FirstName
和 LastName
字段。結(jié)構(gòu)體類型 Employee
包含一個(gè) Person
和 salary
字段。
創(chuàng)建一個(gè) employee
實(shí)例,然后顯示它的 id
。
練習(xí) 10.11 magic.go:
首先預(yù)測一下下面程序的結(jié)果,然后動(dòng)手實(shí)驗(yàn)下:
package main
import (
"fmt"
)
type Base struct{}
func (Base) Magic() {
fmt.Println("base magic")
}
func (self Base) MoreMagic() {
self.Magic()
self.Magic()
}
type Voodoo struct {
Base
}
func (Voodoo) Magic() {
fmt.Println("voodoo magic")
}
func main() {
v := new(Voodoo)
v.Magic()
v.MoreMagic()
}
在編程中一些基本操作會(huì)一遍又一遍的出現(xiàn),比如打開(Open)、關(guān)閉(Close)、讀(Read)、寫(Write)、排序(Sort)等等,并且它們都有一個(gè)大致的意思:打開(Open)可以作用于一個(gè)文件、一個(gè)網(wǎng)絡(luò)連接、一個(gè)數(shù)據(jù)庫連接等等。具體的實(shí)現(xiàn)可能千差萬別,但是基本的概念是一致的。在 Go 語言中,通過使用接口(參考 第 11 章),標(biāo)準(zhǔn)庫廣泛的應(yīng)用了這些規(guī)則,在標(biāo)準(zhǔn)庫中這些通用方法都有一致的名字,比如 Open()
、Read()
、Write()
等。想寫規(guī)范的 Go 程序,就應(yīng)該遵守這些約定,給方法合適的名字和簽名,就像那些通用方法那樣。這樣做會(huì)使 Go 開發(fā)的軟件更加具有一致性和可讀性。比如:如果需要一個(gè) convert-to-string 方法,應(yīng)該命名為 String()
,而不是 ToString()
(參考第 10.7 節(jié))。
在如 C++、Java、C# 和 Ruby 這樣的面向?qū)ο笳Z言中,方法在類的上下文中被定義和繼承:在一個(gè)對象上調(diào)用方法時(shí),運(yùn)行時(shí)會(huì)檢測類以及它的超類中是否有此方法的定義,如果沒有會(huì)導(dǎo)致異常發(fā)生。
在 Go 語言中,這樣的繼承層次是完全沒必要的:如果方法在此類型定義了,就可以調(diào)用它,和其他類型上是否存在這個(gè)方法沒有關(guān)系。在這個(gè)意義上,Go 具有更大的靈活性。
下面的模式就很好的說明了這個(gè)問題:
Go 不需要一個(gè)顯式的類定義,如同 Java、C++、C# 等那樣,相反地,“類”是通過提供一組作用于一個(gè)共同類型的方法集來隱式定義的。類型可以是結(jié)構(gòu)體或者任何用戶自定義類型。
比如:我們想定義自己的 Integer
類型,并添加一些類似轉(zhuǎn)換成字符串的方法,在 Go 中可以如下定義:
type Integer int
func (i *Integer) String() string {
return strconv.Itoa(int(*i))
}
在 Java 或 C# 中,這個(gè)方法需要和類 Integer
的定義放在一起,在 Ruby 中可以直接在基本類型 int 上定義這個(gè)方法。
總結(jié)
在 Go 中,類型就是類(數(shù)據(jù)和關(guān)聯(lián)的方法)。Go 不知道類似面向?qū)ο笳Z言的類繼承的概念。繼承有兩個(gè)好處:代碼復(fù)用和多態(tài)。
在 Go 中,代碼復(fù)用通過組合和委托實(shí)現(xiàn),多態(tài)通過接口的使用來實(shí)現(xiàn):有時(shí)這也叫 組件編程(Component Programming)。
許多開發(fā)者說相比于類繼承,Go 的接口提供了更強(qiáng)大、卻更簡單的多態(tài)行為。
備注
如果真的需要更多面向?qū)ο蟮哪芰?,看一?goop
包(Go Object-Oriented Programming),它由 Scott Pakin 編寫: 它給 Go 提供了 JavaScript 風(fēng)格的對象(基于原型的對象),并且支持多重繼承和類型獨(dú)立分派,通過它可以實(shí)現(xiàn)你喜歡的其他編程語言里的一些結(jié)構(gòu)。
問題 10.1
我們在某個(gè)類型的變量上使用點(diǎn)號(hào)調(diào)用一個(gè)方法:variable.method()
,在使用 Go 以前,在哪兒碰到過面向?qū)ο蟮狞c(diǎn)號(hào)?
問題 10.2
a)假設(shè)定義: type Integer int
,完成 get()
方法的方法體: func (p Integer) get() int { ... }
。
b)定義: func f(i int) {}; var v Integer
,如何就 v 作為參數(shù)調(diào)用f?
c)假設(shè) Integer
定義為 type Integer struct {n int}
,完成 get()
方法的方法體:func (p Integer) get() int { ... }
。
d)對于新定義的 Integer
,和 b)中同樣的問題。