結構體定義python
結構體的定義只是一種內存佈局的描述(至關因而一個模板),只有當結構體實例化時,纔會真正分配內存空間app
結構體是一種複合的基本類型,經過關鍵字 type 定義爲 自定義 類型後,使結構體更便於使用ide
定義一個簡單的結構體:函數
type Point struct { X int Y int }
同類型的變量也能夠寫在一行:佈局
type Color struct { R, G, B byte }
實例性能
結構體實例與實例間的內存是徹底獨立的,this
能夠經過多種方式實例化結構體,根據實際需求選用不一樣的寫法spa
1)基本的實例化形式(不推薦)設計
結構體是一種值類型,能夠像整型,字符串同樣,以 var 開頭的方式聲明結構體便可完成實例化3d
基本實例化格式:
var ins T 其中,T 爲結構體類型,ins 爲結構體的實例
var ins *T
建立指針類型的結構體,T 爲結構體指針類型
Demo
func main(){
type Person struct{
Name string
Age int
}
var p Person
fmt.Printf("%T\n", p)
p.Name = "johny"
p.Age = 12
fmt.Println(p.Name, p.Age)
}
運行結果:
main.Person
johny 12
用聲明的方式建立指針類型的結構體,而後進行賦值會觸發 panic(空指針引用)
func main(){ type Person struct{ Name string Age int } var p1 *Person // var p1 *Person = new(Person) (*p1).Name = "anson" (*p1).Age = 13 fmt.Println((*p1).Name, (*p1).Age) } 運行結果: panic: runtime error: invalid memory address or nil pointer dereference
2)建立指針類型的結構體(推薦使用)
Go語言中,可使用 new 關鍵字對值類型(包括結構體,整型,字符串等)進行實例化,獲得指針類型
ins := new(T) 其中,T 爲結構體類型,ins 爲指針類型 *T
Demo(定義並實例化一個遊戲玩家信息的結構體)
func main(){ type Player struct{ Name string HealthPoint int MagicPoint int } player := new(Player) fmt.Printf("%T\n", player) player.Name = "johny" player.HealthPoint = 100 player.MagicPoint = 100 fmt.Println(player.Name, player.HealthPoint, player.MagicPoint) } 運行結果: *main.Player johny 100 100
3)使用 鍵值對 初始化結構體(實例化時直接填充值)
每一個鍵對應結構體中的一個字段,值對應字段中須要初始化的值
鍵值對的填充是可選的,不須要初始化的字段能夠不在初始化語句塊中體現(字段的默認值,是字段類型的默認值,例如:整數是0,字符串是 "",布爾是 false,指針是 nil 等)
實例化結構體
player := Player{ Name: "johny", HealthPoint: 100, MagicPoint: 100, } 其中,player是結構體實例,Player是結構體類型名,中間是鍵值對(字段名: 初始值)
實例化指針類型的結構體
player := &Player{ Name: "johny", HealthPoint: 100, MagicPoint: 100, }
結構體的嵌套(遞歸)
結構體成員中只能包含結構體的指針類型,包含非指針類型會引發編譯錯誤(invalid recursive type 'People')
func main(){ type People struct{ Name string Child *People } relation := People{ Name: "grandPa", Child: &People{ Name: "father", Child: &People{ Name: "me", }, }, } // 與上面等效 me := People{ Name: "me", } father := People{ Name: "father", Child: &me, } grandPa := People{ Name: "grandPa", Child: &father, } fmt.Printf("%T\n%v\n", relation, relation.Child.Child.Name) fmt.Printf("%T\n%v\n", grandPa, grandPa.Child.Child.Name) } 運行結果: main.People me main.People me
匿名結構體
func main(){ ins := struct{ Name string Age int }{ Name: "johny", Age: 12, } fmt.Printf("%T\n", ins) fmt.Println(ins.Name, ins.Age) } 運行結果: struct { Name string; Age int } johny 12
模擬構造函數 初始化結構體
若是使用結構體來描述貓的特性,那麼根據貓的名稱與顏色能夠有不一樣的類型,那麼可使用不一樣名稱與顏色能夠構造不一樣貓的實例:
type Cat struct{ Name string Color string } func NewCatByName(name string) *Cat { return &Cat{ Name: name, } } func NewCatByColor(color string) *Cat { return &Cat{ Color: color, } } func main(){ cat1 := NewCatByName("johny") cat2 := NewCatByColor("white") fmt.Printf("%T\n%T\n", cat1, cat2) fmt.Printf("%v\n%v\n", cat1.Name, cat2.Color) } 運行結果: *main.Cat *main.Cat johny white
帶有 繼承關係 的結構體的構造與初始化
貓是基本結構體(只有姓名和顏色)
黑貓繼承自貓,是子結構體(不只有姓名和顏色,還有技能)
使用不一樣的兩個構造函數分別構造貓與黑貓兩個結構體實例:
// 貓的結構體 type Cat struct{ Name string Color string } // 構造貓的函數 func NewCat(name, color string) *Cat { return &Cat{ Name: name, Color: color, } } // 黑貓的結構體(繼承了貓,增長了技能字段) type BlackCat struct{ Cat Skill string } // 構造黑貓的函數(不能用) //func NewBlackCat(name, color, skill string) *BlackCat { // return &BlackCat{ // Name: name, // Color: color, // Skill: skill, // } //} // 構造黑貓的函數 func NewBlackCat(name, color, skill string) *BlackCat { blackCat := &BlackCat{} blackCat.Name = name blackCat.Color = color blackCat.Skill = skill return blackCat } func main(){ cat := NewCat("tom", "white") blackCat := NewBlackCat("blackTom", "black", "climb tree") fmt.Printf("%T\n%T\n", cat, blackCat) fmt.Printf("%v\n%v\n", cat.Name, cat.Color) fmt.Printf("%v\n%v\n%v\n", blackCat.Name, blackCat.Color, blackCat.Skill) } 運行結果: *main.Cat *main.BlackCat tom white blackTom black climb tree
Cat 結構體相似於面向對象中的「基類」。BlackCat 嵌入 Cat 結構體,相似於面向對象中的「派生」。實例化時,BlackCat 中的 Cat 也會一併被實例化
結構體匿名字段
上面的 黑貓 與 貓 的繼承關係中,定義黑貓字段的時候,就用到了匿名字段:
// 黑貓的結構體(繼承了貓,增長了技能字段) type BlackCat struct{ Cat Skill string }
1)匿名的結構體字段:
2)匿名的基本類型字段:
type Data struct { int float32 bool } func main(){ var data Data data.int = 100 fmt.Println(data.int, data.float32, data.bool) } 運行結果: 100 0 false
一個結構體中只能有一個同類型的匿名字段,不須要擔憂結構體字段重複問題
方法(method)是一種做用於特定類型的函數,這種特定類型叫作接收器(receiver)(目標接收器)
若是將特定類型理解爲結構體或「類」時,接收器的概念就至關因而實例,也就是其它語言中的 this 或 self
接收器的類型能夠是任何類型,不單單是結構體,任何類型均可以擁有方法
爲結構體添加方法
需求說明:使用揹包做爲「對象」,將物品放入揹包的過程做爲「方法」,經過面向過程的方式和結構體的方式來解釋「方法」的概念
1)面向過程方式:
type Bag struct{ items []string } func put(bag *Bag, item string) { bag.items = append(bag.items, item) } func main(){ var bag *Bag = new(Bag) var item string = "foods" put(bag, item) fmt.Println(bag.items) } 運行結果: [foods]
2)結構體方法:
爲 *Bag 建立一個方法,(bag *Bag) 表示接收器,即 put 方法做用的對象實例
type Bag struct{ items []string } func (b *Bag) put(item string) { b.items = append(b.items, item) } func main(){ var bag *Bag = new(Bag) var item string = "foods" bag.put(item) fmt.Println(bag.items) } 運行結果: [foods]
結構體方法的繼承
模擬面向對象的設計思想(人和鳥的特性)
type Flying struct {} type Walkable struct {} func (f Flying) fly(){ fmt.Println("can fly") } func (w Walkable) walk(){ fmt.Println("can walk") } type Person struct { Walkable } type Bird struct { Flying Walkable } func main(){ var p *Person = new(Person) fmt.Printf("%T\n", p) p.walk() var b *Bird= new(Bird) fmt.Printf("%T\n", b) b.fly() b.walk() } 運行結果: *main.Person can walk *main.Bird can fly can walk
爲任意類型添加方法
由於結構體也是一種類型,給其它類型添加方法和給結構體添加方法同樣
給基本類型添加方法:
type myInt int func (a *myInt) set(num int){ *a = myInt(num) } func main(){ var a myInt a.set(66) fmt.Printf("%T %v\n", a, a) } 運行結果: 66
time 包中的基本類型方法:
time.Second 的類型是 Duration,而 Duration 實際是一個 int64 的類型
對於 Duration 類型有一個 String() 方法,能夠將 Duration 的值轉爲字符串
func main(){
var a string = (time.Second*2).String()
var b time.Duration = time.Second*2
fmt.Printf("%T %v\n", a, a)
fmt.Printf("%T %v\n", b, b)
}
運行結果:
string 2s
time.Duration 2s
接收器
每一個方法只能有一個接收器,以下圖:
接收器的格式以下:
func (接收器變量 接收器類型) 方法名(參數列表) (返回參數) { 函數體 }
各部分說明:
1)理解指針類型的接收器
更接近於面向對象中的 this 或 self;因爲指針的特性,調用方法時,修改接收器指針的任意成員變量,在方法結束後,修改都是有效的
Demo:接收一個結構體的指針,並作修改
// 定義屬性結構 type Person struct{ name string } // 設置name func (p *Person) setName (name string){ p.name = name } // 獲取name func (p *Person) getName()(name string){ return p.name } func main(){ var person *Person = new(Person) person.setName("johny") name := person.getName() fmt.Println(name) }
2)理解非指針類型的接收器
當方法做用於非指針接收器時,會在代碼運行時將接收器的值複製一份;能夠獲取接收器的成員值,但修改後無效
Demo:定義一個空間座標(二維)的結構體,接收非指針的結構體,兩點進行相加
type Point struct{ x int y int } func (p1 Point) add(p2 Point) Point{ return Point{p1.x + p2.x, p1.y + p2.y} } func main(){ var p1 Point = Point{1,1} var p2 Point = Point{1,2} p := p1.add(p2) fmt.Printf("(%d, %d)\n", p.x, p.y) } 運行結果: (2, 3)
3)關於指針和非指針接收器的使用
在計算機中,小對象因爲值複製時的速度較快,因此適合使用非指針接收器。大對象由於複製性能較低,適合使用指針接收器(在接收器和參數間傳遞時不進行復制,只是傳遞指針)
實例:二維矢量模擬玩家移動
在遊戲中,通常使用二維矢量保存玩家的位置,使用矢量計算能夠計算出玩家移動的位置,下面的 demo 中,首先實現二維矢量對象,接着構造玩家對象,最後使用矢量對象和玩家對象共同模擬玩家移動的過程
1)實現二維矢量結構
矢量是數據中的概念,二維矢量擁有兩個方向的信息,同時能夠進行加、減、乘(縮放)、距離、單位化等計算
在計算機中,使用擁有 x 和 y 兩個份量的 Vecor2 結構體實現數學中二維向量的概念,以下:
package main import "math" type Vector struct { x float32 y float32 } // 座標點操做的方法 func (v1 Vector) add(v2 Vector) Vector { return Vector{v1.x + v2.x, v1.y + v2.y} } func (v1 Vector) sub(v2 Vector) Vector { return Vector{v1.x - v2.x, v1.y - v2.y} } func (v1 Vector) multi(speed float32) Vector { return Vector{v1.x * speed, v1.y * speed} } // 計算距離 func (v1 Vector) distanceTo(v2 Vector) float32 { dx := v1.x - v2.x dy := v1.y - v2.y distance := math.Sqrt(float64(dx*dx + dy*dy)) return float32(distance) } // 矢量單位化 func (v1 Vector) normalize() Vector { mag := v1.x * v1.x + v1.y * v1.y if mag > 0 { oneOverMag := 1 / float32(math.Sqrt(float64(mag))) return Vector{v1.x * oneOverMag, v1.y * oneOverMag} } else { return Vector{0, 0} } }
2)實現玩家對象
玩家對象負責存儲玩家的當前位置、目標位置和移動速度,使用 moveTo() 爲玩家設定目的地座標,使用 update() 更新玩家座標
package main type Player struct { currentVector Vector targetVector Vector speed float32 } // 初始化玩家,設置速度 func newPlayer(speed float32) Player { return Player{speed: speed} } // 設置目標位置 func (p *Player) moveTo(v Vector) { p.targetVector = v } // 獲取當前位置 func (p Player) posision() Vector { return p.currentVector } // 是否到達目標位置 func (p Player) isArrived() bool { return p.currentVector.distanceTo(p.targetVector) < p.speed } // 更新玩家位置 func (p *Player) update() { // 使用矢量減法,將目標位置 targetVector 減去當前位置 currentVector,便可得出移動方向的新矢量 directionVector := p.targetVector.sub(p.currentVector) // 矢量單位化 normalizeVector := directionVector.normalize() // 計算 x, y 方向上改變的距離 pointChange := normalizeVector.multi(p.speed) // 玩家新的座標位置 newVector := p.currentVector.add(pointChange) // 更新玩家座標 p.currentVector = newVector }
更新座標稍微複雜一些,須要經過矢量計算得到玩家移動後的新位置,步驟以下:
3)主程序
玩家移動是一個不斷更新位置的循環過程,每次檢測玩家是否靠近目標點附近,若是尚未到達,則不斷地更新位置,並打印出玩家的當前位置,直到玩家到達終點
package main import "fmt" func main(){ // 建立玩家,設置玩家速度 var p Player = newPlayer(0.2) fmt.Println(p.speed) // 設置玩家目標位置 p.moveTo(Vector{2, 2}) p.currentVector = Vector{1, 3} fmt.Println(p.targetVector) for !p.isArrived() { // 更新玩家座標位置 p.update() // 打印玩家位置 fmt.Println(p.posision()) // 一秒更新一次 time.Sleep(time.Second) } fmt.Println("reach destination~") }
實例(python版本)
抽空寫了個python版本,增強理解
# coding=utf-8 import math import time # 座標類 class Vector(object): def __init__(self, x=0, y=0): self.x = x self.y = y # 相加 def add(self, vector): self.x += vector.x self.y += vector.y # 相減 def sub(self, vector): x = self.x - vector.x y = self.y - vector.y return Vector(x, y) # 相乘 def multi(self, speed): self.x *= speed self.y *= speed return self # 計算距離 def distance(self, vector): dx = self.x - vector.x dy = self.y - vector.y return math.sqrt(dx ** 2 + dy ** 2) # 矢量單位化 def normalize(self): mag = self.x ** 2 + self.y ** 2 if mag > 0: one_over_mag = 1 / math.sqrt(mag) vector = Vector(x=self.x * one_over_mag, y=self.y * one_over_mag) else: vector = Vector() return vector # 玩家類 class Player(object): def __init__(self, current_vector=None, target_vector=None, speed=0): self.current_vector = current_vector self.target_vector = target_vector self.speed = speed # 獲取玩家座標 def get_current_vector(self): return self.current_vector # 判斷是否到達終點 def is_arrived(self): return self.current_vector.distance(self.target_vector) < self.speed # 更新玩家位置 def update_vector(self): # 獲取方向矢量(固定值) direction_vector = self.target_vector.sub(self.current_vector) # 矢量單位化(固定值) normalize_vector = direction_vector.normalize() # 根據速度計算 x, y 方向上前進的長度 ongoing_vector = normalize_vector.multi(self.speed) # 更新位置 self.current_vector.add(ongoing_vector) if __name__ == '__main__': p = Player() p.current_vector = Vector(0, 0) p.target_vector = Vector(2, 2) p.speed = 0.2 while not p.is_arrived(): p.update_vector() print(f"({p.current_vector.x}, {p.current_vector.y})") time.sleep(1) print("arrive at the destination")