概述
go的面向對象,可以說是C和C++的夾生版本。
利用結構體來表示一個類,如果有繼承就用結構體套結構體,這個是C的做法。
但是go添加了方法這個概念,讓一些函數屬於某個結構體。
至於多態,則是引入了接口的概念,也有了面向接口編程的概念。
創建類(結構體)
package main
import "fmt"
type Person struct {
name string
sex byte
age int
}
type Student struct {
Person //只有類型,沒有名字,匿名字段,繼承了Person的成員
id int
addr string
name string //和Person同名了
}
//結構體指針類型
type Student2 struct {
*Person //指針類型
id int
addr string
}
func main() {
//順序初始化
var s1 Student = Student{Person{"father", 'm', 18}, 1, "bj","child"}
fmt.Println("s1 = ", s1)
//自動推導類型
s2 := Student{Person{"father", 'm', 18}, 1, "bj","child"}
fmt.Printf("s2 = %+v\n", s2)//%+v, 顯示更詳細
//指定成員初始化,沒有初始化的自動賦值爲0
s3 := Student{id: 1}
fmt.Printf("s3 = %+v\n", s3)
//賦值
s1.Person = Person{"go", 'm', 18}
fmt.Printf("s1 = %+v\n", s1)
//默認:在兒子這裏找得到就用兒子的,否則就是父親的。
s1.name = "teemo" //操作的是Student的name
fmt.Printf("s1 = %+v\n", s1)//s1 = {Person:{name:go sex:109 age:18} id:1 addr:bj name:teemo}
//顯式調用,非要用父親或兒子的
s1.Person.name = "fteemo" //Person的name
fmt.Printf("s1 = %+v\n", s1)//s1 = {Person:{name:fteemo sex:109 age:18} id:1 addr:bj name:teemo}
//指針匿名字段的初始化
s4 := Student2{&Person{"teemo", 'm', 18}, 666, "bj"}
fmt.Println(s4.name, s4.sex, s4.age, s4.id, s4.addr)
//指針匿名字段可以用new申請空間
var s5 Student2
s5.Person = new(Person) //分配空間
s5.name = "yoyo"
fmt.Println(s5.name, s5.sex, s5.age, s5.id, s5.addr)
}
定義方法
package main
import "fmt"
type Person struct {
name string
sex byte
age int
}
//自定義類型,MyInt其實就可以看做是類名,這裏int可以改成任意type,struct只是裏面的一種
type MyInt int
//這裏只能傳MyInt,不然會報錯:invalid operation: a + b (mismatched types MyInt and int)
//a1 MyInt 就是 表明這個方法屬於MyInt,專業屬於叫指定接收者
//b1屬於參數
func (a1 MyInt) Add(b1 MyInt) MyInt {
return a1 + b1
}
//給結構體添加方法
func (p Person) getInfo() {
fmt.Println(p.name)
}
func main() {
var a MyInt = 1
fmt.Println("a.Add(2) = ",a.Add(2))
fmt.Printf("a= %+v\n", a)//a = 1
var b MyInt = 1
fmt.Println("a.Add(b) = ",a.Add(b))
p1 := Person{"teemo",'a',1}
p1.getInfo()
}
這個接收者也可以傳值和傳引用。
package main
import "fmt"
type Person struct {
name string //名字
sex byte //性別, 字符類型
age int //年齡
}
//接收者爲普通變量,非指針,值語義,一份拷貝
func (p Person) SetInfoValue(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a
fmt.Println("p = ", p) //p = {teemo 109 18}
fmt.Printf("SetInfoValue &p = %p\n", &p)//SetInfoValue &p = 0xc00004c480 和s1的地址不一樣
}
//接收者爲指針變量,引用傳遞
func (p *Person) SetInfoPointer(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a
fmt.Printf("SetInfoPointer p = %p\n", p)//SetInfoPointer p = 0xc00004c460 和s2的地址一樣的
}
func main() {
s1 := Person{"go", 'm', 22}
fmt.Printf("&s1 = %p\n", &s1) //&s1 = 0xc00004c420
s2 := Person{"go", 'm', 22}
fmt.Printf("&s2 = %p\n", &s2) //&s2 = 0xc00004c460
//值語義
s1.SetInfoValue("teemo", 'm', 18)
fmt.Println("s1 = ", s1) //s1 = {go 109 22} 原始值沒變
//引用語義
(&s2).SetInfoPointer("teemo", 'm', 18)
fmt.Println("s2 = ", s2) //s2 = {teemo 109 18} 原始值變了
}
方法的繼承和多態
父類的方法子類可以直接調用,並沒有私有的概念,方法名大寫小寫子類都可以用。
子類重寫了方法,會優先調用子類的,也可以顯示調用父類的。
package main
import "fmt"
type Person struct {
name string //名字
sex byte //性別, 字符類型
age int //年齡
}
//Person類型,實現了一個方法
func (tmp *Person) PrintInfo() {
fmt.Printf("name=%s, sex=%c, age=%d\n", tmp.name, tmp.sex, tmp.age)
}
//Person類型,試試小寫會不會私有
func (tmp *Person) printInfo() {
fmt.Printf("name=%s, sex=%c, age=%d\n", tmp.name, tmp.sex, tmp.age)
}
//有個學生,繼承Person字段,成員和方法都繼承了
type Student struct {
Person //匿名字段
id int
addr string
}
//Student也實現了一個方法,這個方法和Person方法同名,這種方法叫重寫
func (tmp *Student) PrintInfo() {
fmt.Println("Student: tmp = ", tmp)
}
func main() {
s := Student{Person{"teemo", 'm', 18}, 666, "bj"}
s.PrintInfo() //默認調用子類的
//顯式調用父類的的方法
s.Person.PrintInfo()
//小寫還是可以調用
s.Person.printInfo()
}
方法值和方法表達式
如果將某個類的實例化的方法賦值給一個變量,則這個變量叫方法值,直接使用這個變量就可以調用方法。
如果將某個類的方法抽象賦值給一個變量,則這個變量加方法表達式,傳遞實例化作爲參數就可以調用方法。
舉個栗子:
package main
import "fmt"
type Person struct {
name string //名字
sex byte //性別, 字符類型
age int //年齡
}
//值傳遞
func (p Person) SetInfoValue() {
fmt.Printf("SetInfoValue: %p, %v\n", &p, p)
}
//引用傳遞
func (p *Person) SetInfoPointer() {
fmt.Printf("SetInfoPointer: %p, %v\n", p, p)
}
func main() {
p := Person{"mike", 'm', 18} //p是Person的實例化對象
fmt.Printf("main: %p, %v\n", &p, p)
//傳統調用方式
p.SetInfoPointer()
//這個就是方法值,調用函數時,無需再傳遞接收者,隱藏了接收者
pFunc := p.SetInfoPointer
pFunc() //等價於 p.SetInfoPointer()
vFunc := p.SetInfoValue
vFunc() //等價於 p.SetInfoValue()
//方法表達式
f := (*Person).SetInfoPointer
//需要傳遞實例化對象,方法需要指針就傳地址,需要值就傳值
f(&p)
f2 := (Person).SetInfoValue
f2(p)
}
有什麼用?封裝唄。
接口
接口其實就是純虛函數,只定義方法,不實現。
面向接口編程就是向外界只提供調用的方法,至於怎麼實現的你不用關心,至於實現多少個版本不用你關心,你只用關心你想用哪個版本就行了。
package main
import "fmt"
//定義接口類型
type Humaner interface {
//方法,只有聲明,沒有實現,由別的類型(自定義類型)實現
sayhi()
}
type Student struct {
name string
id int
}
//Student實現了此方法
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}
type Teacher struct {
addr string
group string
}
//Teacher實現了此方法
func (tmp *Teacher) sayhi() {
fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.group)
}
type MyStr string
//MyStr實現了此方法
func (tmp *MyStr) sayhi() {
fmt.Printf("MyStr[%s] sayhi\n", *tmp)
}
//定義一個普通函數,函數的參數爲接口類型
//只有一個函數,可以有不同表現,多態
func WhoSayHi(i Humaner) {
i.sayhi()
}
func main() {
s := &Student{"teemo", 666}
t := &Teacher{"bj", "go"}
var str MyStr = "hello mike"
//調用同一函數,不同表現,多態,多種形態
WhoSayHi(s)
WhoSayHi(t)
WhoSayHi(&str)
//創建一個切片
x := make([]Humaner, 3)
x[0] = s
x[1] = t
x[2] = &str
//第一個返回下標,第二個返回下標所對應的值
for _, i := range x {
i.sayhi()
}
}
接口的繼承
A接口繼承B接口,則可以用過A接口調用B接口的所有定義。至於實現與否要看類。
package main
import "fmt"
type Humaner interface { //父
sayhi()
}
type Personer interface { //子
Humaner //匿名字段,繼承了sayhi()
sing(lrc string)
}
//我專門實現Humaner接口
type Humaner1 struct {
name string
id int
}
func (tmp *Humaner1) sayhi() {
fmt.Printf("Humaner1[%s, %d] sayhi\n", tmp.name, tmp.id)
}
//我專門實現Personer接口,但是我需要繼承Humaner,不然我就要自己實現
type Personer1 struct {
Humaner1
name string
id int
}
func (tmp *Personer1) sing(lrc string) {
fmt.Println("Personer1 sing", lrc)
}
func (tmp *Personer1) sayhi() {
fmt.Printf("Personer1[%s, %d] sayhi\n", tmp.name, tmp.id)
}
func main() {
s := &Personer1{}
s.sayhi() //我自己的
s.sing("我是Personer1")
s.Humaner1.sayhi()//父類的
}
空接口
空接口(interface{})不包含任何的方法,正因爲如此,所有的類型都實現了空接口,因此空接口可以存儲任意類型的數值。類似於C語言的void *類型。
var i interface{} = 1
fmt.Println("i = ", i)
i = "abc"
fmt.Println("i = ", i)
常用語不知道參數是什麼類型的情況
func Printf(fmt string, args ...interface{})
func Println(args ...interface{})
那麼傳進來了肯定需要判斷類型:
i := make([]interface{}, 3)
i[0] = 1 //int
i[1] = "hello go" //string
i[2] = Student{"mike", 666} //Student
//類型查詢,類型斷言
//第一個返回下標,第二個返回下標對應的值, data分別是i[0], i[1], i[2]
for index, data := range i {
//第一個返回的是值,第二個返回判斷結果的真假
if value, ok := data.(int); ok == true {
fmt.Printf("x[%d] 類型爲int, 內容爲%d\n", index, value)
} else if value, ok := data.(string); ok == true {
fmt.Printf("x[%d] 類型爲string, 內容爲%s\n", index, value)
} else if value, ok := data.(Student); ok == true {
fmt.Printf("x[%d] 類型爲Student, 內容爲name = %s, id = %d\n", index, value.name, value.id)
}
}
//類型查詢,類型斷言
for index, data := range i {
switch value := data.(type) {
case int:
fmt.Printf("x[%d] 類型爲int, 內容爲%d\n", index, value)
case string:
fmt.Printf("x[%d] 類型爲string, 內容爲%s\n", index, value)
case Student:
fmt.Printf("x[%d] 類型爲Student, 內容爲name = %s, id = %d\n", index, value.name, value.id)
}
}