本文是學(xué)習(xí) A Tour of Go (中文參考 Go 之旅中文 ) 整理的筆記徒河,介紹Go 語言方法囱淋,接口方椎,類型的基本概念和使用拐袜。
1. 方法
$GOPATH/src/go_note/gotour/methods/method/method.go
源碼如下:
/**
* go 語言 方法
*/
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
/**
* 方法名: Abs_method
* 方法接收者: Vertex
*/
func (v Vertex) Abs_method() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// 傳指針
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
// 函數(shù)
func Abs_function(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// 為非結(jié)構(gòu)體聲明方法
type MyFloat float64
func (f MyFloat) Abs_myfloat() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
v := Vertex{3, 4}
v.Scale(10) // 此時 v.Scale(10) 會隱式轉(zhuǎn)為 (&v).Scale(10)
fmt.Println(v.Abs_method())
fmt.Println(Abs_function(v)) // 方法只是個帶接收者參數(shù)的函數(shù)
f := MyFloat(-2)
fmt.Println(f.Abs_myfloat())
}
Go 沒有類赫粥。不過你可以為結(jié)構(gòu)體類型定義方法龟劲。
方法是一類帶特殊的 接收者
參數(shù)的函數(shù)胃夏,方法接收者位于方法 func
關(guān)鍵字和方法名之間。
1.1 非結(jié)構(gòu)體類型聲明方法
只能為在同一包內(nèi)定義的類型添加方法咸灿, 而不能為其它包內(nèi)定義的類型(包括 int
之類的內(nèi)建類型)的接收者聲明方法构订。即接收者的類型定義和方法聲明必須在同一包內(nèi);不能為內(nèi)建類型聲明方法避矢。
type MyFloat float64
func (f MyFloat) Abs_myfloat () float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
1.2 指針接收者
為指針接收者聲明方法:對于某類型 T
悼瘾,指針接收者的類型可以用 *T
表示。(T
不能是像 *int
這樣的指針审胸。)
指針接收者的方法可以修改接收者指向的值亥宿。 由于方法經(jīng)常需要修改它的接收者,指針接收者比值接收者更常用砂沛。若使用值接收者烫扼,方法只會對原始值的副本進行操作。
1.3 方法與指針重定向(隱式轉(zhuǎn)換)
以指針為接收者的方法被調(diào)用時碍庵,接收者既能為值又能為指針:
func (v *Vertex) Scale(f float64) {}
v.Scale(5)
(&v).Scale(5)
由于 Scale
方法有一個指針接收者映企,為方便起見悟狱,Go 會將語句 v.Scale(5)
解釋為 (&v).Scale(5)
。
而以值為接收者的方法被調(diào)用時堰氓,接收者既能為值又能為指針:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
這種情況下挤渐,方法調(diào)用 p.Abs()
會被解釋為 (*p).Abs()
。函數(shù)必須接受與定義相同的類型双絮,不會隱式轉(zhuǎn)換
1.4 選擇值或指針作為接收者
使用指針接收者的原因有二:
- 方法能夠修改接收者指向的值浴麻。
- 避免在每次調(diào)用方法時復(fù)制該值,若值的類型為大型結(jié)構(gòu)體時囤攀,這樣做會更加高效软免。
通常來說,所有給定類型的方法都應(yīng)該有值或指針接收者焚挠,但并不應(yīng)該二者混用膏萧。
2. 接口
$GOPATH/src/go_note/gotour/methods/interface/interface.go
源碼如下:
/**
* go 接口
*/
package main
import (
"fmt"
"math"
)
// 定義接口
type Abser interface {
Abs() float64
}
func main() {
// 使用接口
var a Abser
f := MyFloat(-math.Sqrt2)
a = f
fmt.Println(a.Abs())
v := Vertex{3, 4}
a = &v
fmt.Println(a.Abs())
var i I
var t *T
i = t
i.M()
i = &T{"hello"}
i.M()
// 空接口
var inter_empty interface{}
inter_empty = 42
fmt.Printf("%v, %T\n", inter_empty, inter_empty)
inter_empty = "hello"
fmt.Printf("%v, %T\n", inter_empty, inter_empty)
// 類型斷言
var j interface{} = "hello"
s := j.(string)
fmt.Println(s)
s, ok := j.(string)
fmt.Println(s, ok)
inter_float, ok := j.(float64)
fmt.Println(inter_float, ok)
// 類型選擇
do(21)
do("hello")
do(true)
}
type MyFloat float64
// 實現(xiàn)接口
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
接口類型
是由一組方法簽名定義的集合, 接口類型的值可以保存任何實現(xiàn)了這些方法的值。
類型通過實現(xiàn)一個接口的所有方法來實現(xiàn)該接口, 既然無需專門顯式聲明宣蔚,也就沒有“implements“關(guān)鍵字向抢。隱式接口將接口的實現(xiàn)與定義解耦,這樣接口的實現(xiàn)可以出現(xiàn)在任何包中胚委,無需提前定義挟鸠。
2.1 接口值
在內(nèi)部,接口值可以看做包含值和具體類型的元組:
(value, type)
接口值保存了一個具體底層類型的具體值亩冬,接口值調(diào)用方法時會調(diào)用具體類型的的同名方法艘希。
2.2 底層值為 nil 的接口值
即便接口內(nèi)的具體值為 nil
,方法仍然會被 nil
接收者調(diào)用硅急。保存了 nil
具體值的接口其自身并不為 nil
覆享。
但是接口值nil
時,由于此時接口值既不保存值也不保存具體類型营袜,調(diào)用方法會產(chǎn)生運行時錯誤撒顿,因為接口的元組內(nèi)并未包含能夠指明該調(diào)用哪個具體類型的方法。
func main() {
var i I
i.M() // panic: runtime error
var t *T
i = t
i.M() // <nil>
}
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}
2.3 空接口
指定了零個方法的接口值被稱為空接口:
interface{}
因為每個類型都至少實現(xiàn)了零個方法荚板,空接口可保存任何類型的值凤壁。
2.4 類型斷言
類型斷言提供了訪問接口值底層具體值的方式。
t := i.(T)
該語句斷言接口值 i
保存了具體類型 T
跪另,并將其底層類型為 T
的值賦予變量 t
拧抖。如果 i
并未保存 T
類型的值,該語句就會觸發(fā)一個錯誤免绿。
為了判斷一個接口值是否保存了一個特定的類型唧席, 類型斷言可返回兩個值:其底層值和判斷斷言是否成功的布爾值。
t, ok := i.(T)
若 i
保存了一個 T
,那么 t
將會是其底層值淌哟,而 ok
為 true 迹卢。否則, ok 將為 false
而 t
將為 T
類型的零值绞绒,程序并不會產(chǎn)生錯誤婶希。
2.5 類型選擇
類型選擇是一種按順序從幾個類型斷言中選擇分支的結(jié)構(gòu)。
類型選擇與一般的 switch
語句相似蓬衡,不過類型選擇中的 case
為類型(而非值),它們針對給定接口值所存儲值的類型進行比較
switch v := i.(type) {
case T:
// v 的類型為 T
case S:
// v 的類型為 S
default:
// 沒有匹配彤枢,v 與 i 的類型相同
}
類型選擇中的聲明與類型斷言 i.(T)
的語法相同狰晚,只是具體類型 T
被替換成了關(guān)鍵字 type
。
此選擇語句判斷接口值 i
保存的值類型是 T
還是 S
缴啡。 在 T
或 S
的情況下壁晒,變量 v
會分別按 T
或 S
類型取保存在 i
中的值。在默認(rèn)(沒有匹配)的情況下业栅,變量 v
與 i
的接口類型和值相同秒咐。
3. Stringer
$GOPATH/src/go_note/gotour/methods/stringer/stringer.go
源碼如下:
/**
* go String
*/
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
a := Person{"Author", 42}
z := Person{"Modifier", 1989}
fmt.Println(a, z)
}
fmt
包中定義的 Stringer
是最普遍的接口之一。
type Stringer interface {
String() string
}
Stringer
是一個可以用字符串描述自己的類型碘裕。fmt
包(還有很多包)都通過此接口來打印值携取。
4. 錯誤
$GOPATH/src/go_note/gotour/methods/error/error.go
源碼如下:
/**
* go語言 error
*/
package main
import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s", e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
Go 程序使用 error
值來表示錯誤狀態(tài)。與 fmt.Stringer
類似帮孔, error
類型是一個內(nèi)建接口:
type error interface {
Error() string
}
通常函數(shù)會返回一個 error 值雷滋,調(diào)用的它的代碼應(yīng)當(dāng)判斷這個錯誤是否等于 nil 來進行錯誤處理。
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)
error
為 nil
時表示成功文兢;非 nil
的 error
表示失敗晤斩。
5. Reader
$GOPATH/src/go_note/gotour/methods/reader/reader.go
源碼如下:
/**
* go read
*/
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello, workd!")
b := make([]byte, 4)
for {
n, err := r.Read(b)
fmt.Printf("n = %v, err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}
io
包指定了 io.Reader
接口, 它表示從數(shù)據(jù)流的末尾進行讀取姆坚。Go 標(biāo)準(zhǔn)庫包含了該接口的許多實現(xiàn)澳泵,包括文件、網(wǎng)絡(luò)連接兼呵、壓縮和加密等等兔辅。
io.Reader
接口有一個 Read
方法:
func (T) Read(b []byte) (n int, err error)
Read
用數(shù)據(jù)填充給定的字節(jié)切片并返回填充的字節(jié)數(shù)和錯誤值。 在遇到數(shù)據(jù)流的結(jié)尾時萍程,它會返回一個 io.EOF
錯誤幢妄。
參考
可以關(guān)注我的微博了解更多信息: