方法 - Go 語言學習筆記

什麼是方法?

Go 語言中同時有函數和方法。方法就是一個包含了接收者的函數,接收者能夠是命名類型或者結構體類型的一個值或者是一個指針。全部給定類型的方法屬於該類型的方法集。node

在 Go 中,(接收者)類型關聯的方法不寫在類型結構裏面,就像類那樣;耦合更加寬鬆;類型和方法之間的關聯由接收者來創建。 方法沒有和數據定義(結構體)混在一塊兒:它們是正交的類型;表示(數據)和行爲(方法)是獨立的。bash

聲明方法

語法格式以下:函數

func (variable_receiver_name receiver_type) function_name([parameter list]) [return_type]{
    /* 函數體*/
}
複製代碼
  • variable_receiver_name 接收者必須有一個顯式的名字,這個名字必須在方法中被使用。官方建議使用接收器類型名的第一個小寫字母,而不是 self、this 之類的命名。例如,Socket 類型的接收器變量應該命名爲 s,Connector 類型的接收器變量應該命名爲 c 等。
  • receiver_type 叫作 (接收者)基本類型,這個類型必須在和方法一樣的包中被聲明。
  • 方法名、參數列表、返回參數:格式與函數定義一致。
package main
import (
    "fmt"  
)

// 定義結構體
type Circle struct {
    radius float64
}

func main() {
    var c1 Circle
    c1.radius = 10.00
    fmt.Println("圓的面積 = ", c1.getArea())
}

// 該 method 屬於 Circle 類型對象中的方法
func (c Circle) getArea() float64 {
    // c.radius 即爲 Circle 類型對象中的屬性
    return 3.14 * c.radius * c.radius
}

// 執行結果爲:的面積 =  314
複製代碼

接收者

接收者有兩種,一種是值接收者,一種是指針接收者。顧名思義,值接收者,是接收者的類型是一個值,是一個副本,方法內部沒法對其真正的接收者作更改;指針接收者,接收者的類型是一個指針,是接收者的引用,對這個引用的修改之間影響真正的接收者。像上面同樣定義方法,將 user 改爲 *user 就是指針接收者。ui

調用方法

struct的方法調用this

方法調用至關於普通函數調用的語法糖。Value方法的調用m.Value()等價於func Value(m M) 即把對象實例m做爲函數調用的第一個實參壓棧,這時m稱爲receiver。經過實例或實例的指針其實均可以調用全部方法,區別是複製給函數的receiver不一樣。spa

以下,經過實例m調用Value時,以及經過指針p調用Value時,receiver是m和*p,即複製的是m實例自己。所以receiver是m實例的副本,他們地址不一樣。經過實例m調用Pointer時,以及經過指針p調用Pointer時,複製的是都是&m和p,即複製的都是指向m的指針,返回的都是m實例的地址。指針

type M struct {
    a int
}
func (m M) Value() string {return fmt.Sprintf("Value: %p\n", &m)}
func (m *M) Pointer() string {return fmt.Sprintf("Pointer: %p\n", m)}
var m M
p := &m      // p is address of m 0x2101ef018
m.Value()    // value(m) return 0x2101ef028
m.Pointer()  // value(&m) return 0x2101ef018
p.Value()    // value(*p) return 0x2101ef030
p.Pointer()  // value(p) return 0x2101ef018
複製代碼

方法用法

1. 基於指針對象的方法

當接受者變量自己比較大時,能夠用其指針而不是對象來聲明方法,這樣能夠節省內存空間的佔用。code

package main
import (
    "fmt"
    "math"
)

// 方法聲明
type Point struct {
	X,Y float64
}

func (p *Point) Distance(q *Point) float64 {
	return math.Hypot(q.X - p.X, q.Y - p.Y)
}

func main() {
	p := &Point{3,5}
	fmt.Println(p.Distance(&Point{5, 6}))
}

// 執行結果爲:2.23606797749979
複製代碼

2. 將nil做爲接收器

package main

import "fmt"

// 將nil做爲接收器
type IntNode struct {
	Value int
	Next *IntNode
}

func (node *IntNode) Sum() int  {
	if node == nil {
		return 0
	}
	return node.Value + node.Next.Sum()

}

func main()  {
	node1 := IntNode{30, nil}
	node2 := IntNode{12, nil}
	node3 := IntNode{43, nil}

	node1.Next = &node2
	node2.Next = &node3

	fmt.Println(node1.Sum())
	fmt.Println(node2.Sum())

	node := &IntNode{3, nil}
	node = nil
	fmt.Println(node.Sum())
}
// 執行結果爲:
// 85
// 55
// 0
複製代碼

3. 經過嵌入結構體拓展類型

示例:對象

package main

import (
    "fmt"
)

// 基礎顏色
type BasicColor struct {
    // 紅、綠、藍三種顏色份量
    R, G, B float32
}

// 完整顏色定義
type Color struct {
    // 將基本顏色做爲成員
    BasicColor
    
    // 透明度
    Alpha float32
}

func main() {
    
    // 設置基本顏色份量
    var c Color
    c.R = 1
    c.G = 1
    c.B = 0
    
    // 設置透明度
    c.Alpha = 1
    
    // 顯示整個結構體內容
    fmt.Printf("%+v", c)
}
複製代碼

代碼輸出以下: {Basic:{R:1 G:1 B:0} Alpha:1}內存

Go語言的結構體內嵌特性:

  1. 內嵌的結構體能夠直接訪問其成員變量
    嵌入結構體的成員,能夠經過外部結構體的實例直接訪問。若是結構體有多層嵌入結構體,結構體實例訪問任意一級的嵌入結構體成員時都只用給出字段名,而無須像傳統結構體字段同樣,經過一層層的結構體字段訪問到最終的字段。例如,ins.a.b.c的訪問能夠簡化爲ins.c。
  2. 內嵌結構體的字段名是它的類型名
    內嵌結構體字段仍然可使用詳細的字段進行一層層訪問,內嵌結構體的字段名就是它的類型名,代碼以下:
var c Color
c.BasicColor.R = 1
c.BasicColor.G = 1
c.BasicColor.B = 0
複製代碼

一個結構體只能嵌入一個同類型的成員,無須擔憂結構體重名和錯誤賦值的狀況,編譯器在發現可能的賦值歧義時會報錯。

相關文章
相關標籤/搜索