1. 結構體
Go語言可以通過自定義的方式形成新的類型,結構體就是這些類型中的一種復合類型,結構體是由零個或多個任意類型的值聚合成的實體,每個值都可以稱為結構體的成員。
結構體成員也可以稱為“字段”,這些字段有以下特性:
- 字段擁有自己的類型和值;
- 字段名必須唯一;
- 字段的類型也可以是結構體,甚至是字段所在結構體的類型。
使用關鍵字 type 可以將各種基本類型定義為自定義類型,基本類型包括整型、字符串、布爾等。結構體是一種復合的基本類型,通過 type 定義為自定義類型后,使結構體更便于使用。
結構體的定義格式如下:
type 類型名 struct {字段1 字段1類型字段2 字段2類型…
}
- 類型名:標識自定義結構體的名稱,在同一個包內不能重復。
- struct{}:表示結構體類型,type 類型名 struct{}可以理解為將 struct{} 結構體定義為類型名的類型。
- 字段1、字段2……:表示結構體字段名,結構體中的字段名必須唯一。
- 字段1類型、字段2類型……:表示結構體各個字段的類型。
示例:
type Point struct {X intY int
}
顏色的紅、綠、藍 3 個分量可以使用 byte 類型:
type Color struct {R, G, B byte
}
結構體的定義只是一種內存布局的描述,只有當結構體實例化時,才會真正地分配內存
1.1 實例化
實例化就是根據結構體定義的格式創建一份與格式一致的內存區域,結構體實例與實例間的內存是完全獨立的。
基本的實例化形式:
結構體本身是一種類型,可以像整型、字符串等類型一樣,以 var 的方式聲明結構體即可完成實例化。
var ins T
1
T 為結構體類型,ins 為結構體的實例。
type Index struct {x inty int
}func main() {var myIndex Index//使用.來訪問結構體的成員變量,結構體成員變量的賦值方法與普通變量一致。myIndex.y = 10myIndex.x = 20fmt.Println(myIndex) //{20 10}}
//如果不賦值 結構體中的變量會使用零值初始化
type Point struct {X intY int
}
func main() {//可以使用var p = Point{X: 1,Y: 2,}var p = Point{1,2,}fmt.Printf("%v,x=%d,y=%d",p,p.X,p.Y )
}
創建指針類型的結構體:
Go語言中,還可以使用 new 關鍵字對類型(包括結構體、整型、浮點數、字符串等)進行實例化,結構體在實例化后會形成指針類型的結構體。
ins := new(T)
1
- T 為類型,可以是結構體、整型、字符串等。
- ins:T 類型被實例化后保存到 ins 變量中,ins 的類型為 *T,屬于指針。
下面的例子定義了一個玩家(Player)的結構,玩家擁有名字、生命值和魔法值:
type Player struct {name stringhp intpower int}play := new(Player)play.power = 10play.name = "小米"play.hp = 150fmt.Println(*play) //{小米 150 10}
取結構體的地址實例化:
在Go語言中,對結構體進行&取地址操作時,視為對該類型進行一次 new 的實例化操作,取地址格式如下:
ins := &T{}
其中:
- T 表示結構體類型。
- ins 為結構體的實例,類型為 *T,是指針類型。
type Commend struct {Name stringVar *intCommend string
}func newCommend(name string, Var *int, commend string) *Commend {return &Commend{name,Var,commend,}
}var version = 1func main() {m := newCommend("lct", &version, "test")fmt.Println(*m) //{lct 0x5d4318 test}
}
1.2 匿名結構體
匿名結構體沒有類型名稱,無須通過 type 關鍵字定義就可以直接使用。
ins := struct {// 匿名結構體字段定義字段1 字段類型1字段2 字段類型2…
}{// 字段值初始化初始化字段1: 字段1的值,初始化字段2: 字段2的值,…
}
- 字段1、字段2……:結構體定義的字段名。
- 初始化字段1、初始化字段2……:結構體初始化時的字段名,可選擇性地對字段初始化。
- 字段類型1、字段類型2……:結構體定義字段的類型。
- 字段1的值、字段2的值……:結構體初始化字段的初始值。
// 打印消息類型, 傳入匿名結構體
func printMsg(msg *struct {id intdata string
}) {fmt.Printf("type :%T,msg:%v", msg, msg)
}func main() {msg := &struct {id intdata string}{21,"hello",}printMsg(msg) //type :*struct { id int; data string },msg:&{21 hello}
}
2. 方法
在Go語言中,結構體就像是類的一種簡化形式,那么類的方法在哪里呢?
在Go語言中有一個概念,它和方法有著同樣的名字,并且大體上意思相同,Go 方法是作用在接收器(receiver)上的一個函數,接收器是某種類型的變量,因此方法是一種特殊類型的函數。
接收器類型可以是(幾乎)任何類型,不僅僅是結構體類型,任何類型都可以有方法,甚至可以是函數類型,可以是 int、bool、string 或數組的別名類型,但是接收器不能是一個接口類型,因為接口是一個抽象定義,而方法卻是具體實現,如果這樣做了就會引發一個編譯錯誤invalid receiver type…
接收器也不能是一個指針類型,但是它可以是任何其他允許類型的指針。
一個類型加上它的方法等價于面向對象中的一個類
在Go語言中,類型的代碼和綁定在它上面的方法的代碼可以不放置在一起,它們可以存在不同的源文件中,唯一的要求是它們必須是同一個包的。
類型 T(或 T)上的所有方法的集合叫做類型 T(或 T)的方法集。
在面向對象的語言中,類擁有的方法一般被理解為類可以做的事情。在Go語言中“方法”的概念與其他語言一致,只是Go語言建立的“接收器”強調方法的作用對象是接收器,也就是類實例,而函數沒有作用對象。
為結構體添加方法:
需求:將物品放入背包
面向對象的寫法:
將背包做為一個對象,將物品放入背包的過程作為“方法”
type Bag struct {items []int
}func (b *Bag) Insert(item int) {b.items = append(b.items, item)
}func main() {var bag Bagbag.Insert(10)fmt.Println(bag.items) //[10]
}
(b*Bag) 表示接收器,即 Insert 作用的對象實例。每個方法只能有一個接收器
2.1 接收器
接收器的格式如下:
func (接收器變量 接收器類型) 方法名(參數列表) (返回參數) {函數體
}
- 接收器變量:接收器中的參數變量名在命名時,官方建議使用接收器類型名的第一個小寫字母,而不是 self、this 之類的命名。例如,Socket 類型的接收器變量應該命名為 s,Connector 類型的接收器變量應該命名為 c 等。
- 接收器類型:接收器類型和參數類似,可以是指針類型和非指針類型。
- 方法名、參數列表、返回參數:格式與函數定義一致。
接收器根據接收器的類型可以分為指針接收器、非指針接收器,兩種接收器在使用時會產生不同的效果,根據效果的不同,兩種接收器會被用于不同性能和功能要求的代碼中。
指針類型的接收器:
指針類型的接收器由一個結構體的指針組成,更接近于面向對象中的 this 或者 self。
由于指針的特性,調用方法時,修改接收器指針的任意成員變量,在方法結束后,修改都是有效的。
示例:
使用結構體定義一個屬性(Property),為屬性添加 SetValue() 方法以封裝設置屬性的過程,通過屬性的 Value() 方法可以重新獲得屬性的數值,使用屬性時,通過 SetValue() 方法的調用,可以達成修改屬性值的效果:
type Property struct {val int
}// set val
func (p *Property) setVal(val int) {p.val = val
}//get valfunc (p *Property) getVal() int {return p.val
}func main() {p := new(Property)p.setVal(20)fmt.Println(p.getVal()) //20
}
非指針類型的接收器:
當方法作用于非指針接收器時,Go語言會在代碼運行時將接收器的值復制一份,在非指針接收器的方法中可以獲取接收器的成員值,但修改后無效。
點(Point)使用結構體描述時,為點添加 Add() 方法,這個方法不能修改 Point 的成員 X、Y 變量,而是在計算后返回新的 Point 對象,Point 屬于小內存對象,在函數返回值的復制過程中可以極大地提高代碼運行效率:
package main
import ("fmt"
)
// 定義點結構
type Point struct {X intY int
}
// 非指針接收器的加方法
func (p Point) Add(other Point) Point {// 成員值與參數相加后返回新的結構return Point{p.X + other.X, p.Y + other.Y}
}
func main() {// 初始化點p1 := Point{1, 1}p2 := Point{2, 2}// 與另外一個點相加result := p1.Add(p2)// 輸出結果fmt.Println(result)
}
在計算機中,小對象由于值復制時的速度較快,所以適合使用非指針接收器,大對象因為復制性能較低,適合使用指針接收器,在接收器和參數間傳遞時不進行復制,只是傳遞指針
4. 給任意類型添加方法
Go語言可以對任何類型添加方法,給一種類型添加方法就像給結構體添加方法一樣,因為結構體也是一種類型。
為基本類型添加方法:
在Go語言中,使用 type 關鍵字可以定義出新的自定義類型,之后就可以為自定義類型添加各種方法了。我們習慣于使用面向過程的方式判斷一個值是否為 0,例如:
if v == 0 {// v等于0
}
如果將 v 當做整型對象,那么判斷 v 值就可以增加一個 IsZero() 方法,通過這個方法就可以判斷 v 值是否為 0,例如:
if v.IsZero() {// v等于0
}
為基本類型添加方法的詳細實現流程如下:
package main
import ("fmt"
)
// 將int定義為MyInt類型
type MyInt int
// 為MyInt添加IsZero()方法
func (m MyInt) IsZero() bool {return m == 0
}
// 為MyInt添加Add()方法
func (m MyInt) Add(other int) int {return other + int(m)
}
func main() {var b MyIntfmt.Println(b.IsZero())b = 1fmt.Println(b.Add(2))
}
5. 匿名字段
結構體可以包含一個或多個匿名(或內嵌)字段,即這些字段沒有顯式的名字,只有字段的類型是必須的,此時類型也就是字段的名字。
匿名字段本身可以是一個結構體類型,即結構體可以包含內嵌結構體。
Go語言中的繼承是通過內嵌或組合來實現的,所以可以說,在Go語言中,相比較于繼承,組合更受青睞。
type User struct {id intname string
}type Manager struct {User
}func (u *User) ToString() string {return fmt.Sprintf("User:%p,%+v", u, u)
}func main() {m := Manager{User{1, "lct"},}fmt.Printf("manger:%p\n", &m) //manger:0xc000094030fmt.Println(m.ToString()) //User:0xc000094030,&{id:1 name:lct}
}
有點類似于重寫
type User struct {id intname string
}type Manager struct {Usertitle string
}func (u *User) ToString() string {return fmt.Sprintf("User:%p,%+v", u, u)
}func (m *Manager) ToString() string {return fmt.Sprintf("Manger:%p,%+v", m, m)
}func main() {m := Manager{User{1,"lct",}, "hahah"}fmt.Println(m.ToString()) //Manger:0xc00001a0f0,&{User:{id:1 name:lct} title:hahah}fmt.Println(m.User.ToString()) //User:0xc00001a0f0,&{id:1 name:lct}}