Go語言學習筆記15.面向對象

概述

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)
		}

	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章