本文是學(xué)習(xí) A Tour of Go (中文參考 Go 之旅中文 ) 整理的筆記,介紹Go 語(yǔ)言方法,接口,類型的基本概念和使用。
1. 方法
$GOPATH/src/go_note/gotour/methods/method/method.go
源碼如下:
/**
* go 語(yǔ)言 方法
*/
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) // 此時(shí) v.Scale(10) 會(huì)隱式轉(zhuǎn)為 (&v).Scale(10)
fmt.Println(v.Abs_method())
fmt.Println(Abs_function(v)) // 方法只是個(gè)帶接收者參數(shù)的函數(shù)
f := MyFloat(-2)
fmt.Println(f.Abs_myfloat())
}
Go 沒(méi)有類。不過(guò)你可以為結(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 指針接收者
為指針接收者聲明方法:對(duì)于某類型 T
,指針接收者的類型可以用 *T
表示。(T
不能是像 *int
這樣的指針。)
指針接收者的方法可以修改接收者指向的值。 由于方法經(jīng)常需要修改它的接收者,指針接收者比值接收者更常用。若使用值接收者,方法只會(huì)對(duì)原始值的副本進(jìn)行操作。
1.3 方法與指針重定向(隱式轉(zhuǎn)換)
以指針為接收者的方法被調(diào)用時(shí),接收者既能為值又能為指針:
func (v *Vertex) Scale(f float64) {}
v.Scale(5)
(&v).Scale(5)
由于 Scale
方法有一個(gè)指針接收者,為方便起見(jiàn),Go 會(huì)將語(yǔ)句 v.Scale(5)
解釋為 (&v).Scale(5)
。
而以值為接收者的方法被調(diào)用時(shí),接收者既能為值又能為指針:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
這種情況下,方法調(diào)用 p.Abs()
會(huì)被解釋為 (*p).Abs()
。函數(shù)必須接受與定義相同的類型,不會(huì)隱式轉(zhuǎn)換
1.4 選擇值或指針作為接收者
使用指針接收者的原因有二:
- 方法能夠修改接收者指向的值。
- 避免在每次調(diào)用方法時(shí)復(fù)制該值,若值的類型為大型結(jié)構(gòu)體時(shí),這樣做會(huì)更加高效。
通常來(lái)說(shuō),所有給定類型的方法都應(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
// 實(shí)現(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)
}
接口類型
是由一組方法簽名定義的集合, 接口類型的值可以保存任何實(shí)現(xiàn)了這些方法的值。
類型通過(guò)實(shí)現(xiàn)一個(gè)接口的所有方法來(lái)實(shí)現(xiàn)該接口, 既然無(wú)需專門顯式聲明,也就沒(méi)有“implements“關(guān)鍵字。隱式接口將接口的實(shí)現(xiàn)與定義解耦,這樣接口的實(shí)現(xiàn)可以出現(xiàn)在任何包中,無(wú)需提前定義。
2.1 接口值
在內(nèi)部,接口值可以看做包含值和具體類型的元組:
(value, type)
接口值保存了一個(gè)具體底層類型的具體值,接口值調(diào)用方法時(shí)會(huì)調(diào)用具體類型的的同名方法。
2.2 底層值為 nil 的接口值
即便接口內(nèi)的具體值為 nil
,方法仍然會(huì)被 nil
接收者調(diào)用。保存了 nil
具體值的接口其自身并不為 nil
。
但是接口值nil
時(shí),由于此時(shí)接口值既不保存值也不保存具體類型,調(diào)用方法會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤,因?yàn)榻涌诘脑M內(nèi)并未包含能夠指明該調(diào)用哪個(gè)具體類型的方法。
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 空接口
指定了零個(gè)方法的接口值被稱為空接口:
interface{}
因?yàn)槊總€(gè)類型都至少實(shí)現(xiàn)了零個(gè)方法,空接口可保存任何類型的值。
2.4 類型斷言
類型斷言提供了訪問(wèn)接口值底層具體值的方式。
t := i.(T)
該語(yǔ)句斷言接口值 i
保存了具體類型 T
,并將其底層類型為 T
的值賦予變量 t
。如果 i
并未保存 T
類型的值,該語(yǔ)句就會(huì)觸發(fā)一個(gè)錯(cuò)誤。
為了判斷一個(gè)接口值是否保存了一個(gè)特定的類型, 類型斷言可返回兩個(gè)值:其底層值和判斷斷言是否成功的布爾值。
t, ok := i.(T)
若 i
保存了一個(gè) T
,那么 t
將會(huì)是其底層值,而 ok
為 true 。否則, ok 將為 false
而 t
將為 T
類型的零值,程序并不會(huì)產(chǎn)生錯(cuò)誤。
2.5 類型選擇
類型選擇是一種按順序從幾個(gè)類型斷言中選擇分支的結(jié)構(gòu)。
類型選擇與一般的 switch
語(yǔ)句相似,不過(guò)類型選擇中的 case
為類型(而非值),它們針對(duì)給定接口值所存儲(chǔ)值的類型進(jìn)行比較
switch v := i.(type) {
case T:
// v 的類型為 T
case S:
// v 的類型為 S
default:
// 沒(méi)有匹配,v 與 i 的類型相同
}
類型選擇中的聲明與類型斷言 i.(T)
的語(yǔ)法相同,只是具體類型 T
被替換成了關(guān)鍵字 type
。
此選擇語(yǔ)句判斷接口值 i
保存的值類型是 T
還是 S
。 在 T
或 S
的情況下,變量 v
會(huì)分別按 T
或 S
類型取保存在 i
中的值。在默認(rèn)(沒(méi)有匹配)的情況下,變量 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
是一個(gè)可以用字符串描述自己的類型。fmt
包(還有很多包)都通過(guò)此接口來(lái)打印值。
4. 錯(cuò)誤
$GOPATH/src/go_note/gotour/methods/error/error.go
源碼如下:
/**
* go語(yǔ)言 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
值來(lái)表示錯(cuò)誤狀態(tài)。與 fmt.Stringer
類似, error
類型是一個(gè)內(nèi)建接口:
type error interface {
Error() string
}
通常函數(shù)會(huì)返回一個(gè) error 值,調(diào)用的它的代碼應(yīng)當(dāng)判斷這個(gè)錯(cuò)誤是否等于 nil 來(lái)進(jìn)行錯(cuò)誤處理。
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
時(shí)表示成功;非 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ù)流的末尾進(jìn)行讀取。Go 標(biāo)準(zhǔn)庫(kù)包含了該接口的許多實(shí)現(xiàn),包括文件、網(wǎng)絡(luò)連接、壓縮和加密等等。
io.Reader
接口有一個(gè) Read
方法:
func (T) Read(b []byte) (n int, err error)
Read
用數(shù)據(jù)填充給定的字節(jié)切片并返回填充的字節(jié)數(shù)和錯(cuò)誤值。 在遇到數(shù)據(jù)流的結(jié)尾時(shí),它會(huì)返回一個(gè) io.EOF
錯(cuò)誤。
參考
可以關(guān)注我的微博了解更多信息: