go-面向對象編程(下)

面向對象編程思想-抽象

抽象的介紹

咱們在前面去定義一個結構體時候,實際上就是把一類事物的共有的 屬性( 字段)和 行爲( 方法)提取
出來,造成一個 物理模型(結構體)。這種研究問題的方法稱爲抽象
好比一個銀行帳戶:java

package main

import (
    "fmt"
)
//定義一個結構體Account
type Account struct {
    AccountNo string
    Pwd string
    Balance float64
}

//方法
//1. 存款
func (account *Account) Deposite(money float64, pwd string)  {

    //看下輸入的密碼是否正確
    if pwd != account.Pwd {
        fmt.Println("你輸入的密碼不正確")
        return 
    }

    //看看存款金額是否正確
    if money <= 0 {
        fmt.Println("你輸入的金額不正確")
        return 
    }

    account.Balance += money
    fmt.Println("存款成功~~")

}

//取款
func (account *Account) WithDraw(money float64, pwd string)  {

    //看下輸入的密碼是否正確
    if pwd != account.Pwd {
        fmt.Println("你輸入的密碼不正確")
        return 
    }

    //看看取款金額是否正確
    if money <= 0  || money > account.Balance {
        fmt.Println("你輸入的金額不正確")
        return 
    }

    account.Balance -= money
    fmt.Println("取款成功~~")

}

//查詢餘額
func (account *Account) Query(pwd string)  {

    //看下輸入的密碼是否正確
    if pwd != account.Pwd {
        fmt.Println("你輸入的密碼不正確")
        return 
    }

    fmt.Printf("你的帳號爲=%v 餘額=%v \n", account.AccountNo, account.Balance)

}


func main() {

    //測試一把
    account := Account{
        AccountNo : "gs1111111",
        Pwd : "666666",
        Balance : 100.0,
    }

    //這裏能夠作的更加靈活,就是讓用戶經過控制檯來輸入命令...
    //菜單....
    account.Query("666666")
    account.Deposite(200.0, "666666")
    account.Query("666666")
    account.WithDraw(150.0, "666666")
    account.Query("666666")

    


}

面向對象編程三大特性-封裝

基本介紹

Golang 仍然有面向對象編程的繼承,封裝和多態的特性,只是實現的方式和其它 OOP 語言不一
樣,下面咱們一一爲同窗們進行詳細的講解 Golang 的三大特性是如何實現的。程序員

封裝介紹

封裝(encapsulation)就是把抽象出的字段和對字段的操做封裝在一塊兒,數據被保護在內部,程序的其
它包只有經過被受權的操做(方法),才能對字段進行操做編程

封裝的理解和好處

1) 隱藏實現細節
2) 提升對 數據進行驗證,保證安全合理(Age)數組

如何體現封裝

1) 對結構體中的屬性進行封裝
2) 經過 方法,包 包 實現封裝安全

封裝的實現步驟

1) 將結構體、字段(屬性)的首字母小寫(不能導出了,其它包不能使用,相似 private)
2) 給結構體所在包提供一個工廠模式的函數,首字母大寫。相似一個構造函數
3) 提供一個首字母大寫的 Set 方法(相似其它語言的 public),用於對屬性判斷並賦值
func (var 結構體類型名) SetXxx(參數列表) (返回值列表) {
//加入數據驗證的業務邏輯
var.字段 = 參數
}
4) 提供一個首字母大寫的 Get 方法(相似其它語言的 public),用於獲取屬性的值
func (var 結構體類型名) GetXxx() {
return var.age;
}
特別說明:在 Golang 開發中並無特別強調封裝,這點並不像 Java. 因此提醒學過 java 的朋友,
不用老是用 java 的語法特性來看待 Golang, Golang 自己對面向對象的特性作了簡化的.app

看一個案例

請你們看一個程序(person.go),不能隨便查看 人的年齡, 工資等隱私,並對輸入的年齡進行合理的驗
證。設計: model 包(person.go) main 包(main.go 調用 Person 結構體)
main.go函數

package main
import (
    "fmt"
    "go_code/code/chapter11/encapsulate/model"
)

func main() {

    p := model.NewPerson("smith")
    p.SetAge(18)
    p.SetSal(5000)
    fmt.Println(p)
    fmt.Println(p.Name, " age =", p.GetAge(), " sal = ", p.GetSal())
    
}

moudel學習

package model
import "fmt"

type person struct {
    Name string
    age int   //其它包不能直接訪問..
    sal float64
}

//寫一個工廠模式的函數,至關於構造函數
func NewPerson(name string) *person {
    return &person{
        Name : name,
    }
}

//爲了訪問age 和 sal 咱們編寫一對SetXxx的方法和GetXxx的方法
func (p *person) SetAge(age int) {
    if age >0 && age <150 {
        p.age = age
    } else {
        fmt.Println("年齡範圍不正確..")
        //給程序員給一個默認值
    }
}

func (p *person) GetAge() int {
    return p.age
}


func (p *person) SetSal(sal float64) {
    if sal >= 3000 && sal <= 30000 {
        p.sal = sal
    } else {
        fmt.Println("薪水範圍不正確..")
        
    }
}

func (p *person) GetSal() float64 {
    return p.sal
}

面向對象編程三大特性-繼承

看一個問題,引出繼承的必要性

一個小問題,看個學生考試系統的程序 extends01.go,提出代碼複用的問題
1) Pupil 和 Graduate 兩個結構體的字段和方法幾乎,可是咱們卻寫了相同的代碼, 代碼複用性不

2) 出現代碼冗餘,並且代碼 不利於維護,同時 也不利於功能的擴展。
3) 解決方法-經過 繼承方式來解決測試

package main

import (
    "fmt"
)




//編寫一個學生考試系統

type Student struct {
    Name string
    Age int
    Score int
}

//將Pupil 和 Graduate 共有的方法也綁定到 *Student
func (stu *Student) ShowInfo() {
    fmt.Printf("學生名=%v 年齡=%v 成績=%v\n", stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetScore(score int) {
    //業務判斷
    stu.Score = score
}

//給 *Student 增長一個方法,那麼 Pupil 和 Graduate均可以使用該方法
func (stu *Student) GetSum(n1 int, n2 int) int {
    return n1 + n2
}

//小學生
type Pupil struct { 
    Student //嵌入了Student匿名結構體
}

//顯示他的成績

//這時Pupil結構體特有的方法,保留
func (p *Pupil) testing() {
    fmt.Println("小學生正在考試中.....")
}

//大學生, 研究生。。


//大學生
type Graduate struct {
    Student //嵌入了Student匿名結構體
}

//顯示他的成績
//這時Graduate結構體特有的方法,保留
func (p *Graduate) testing() {
    fmt.Println("大學生正在考試中.....")
}

//代碼冗餘.. 高中生....

func main() {

    //當咱們對結構體嵌入了匿名結構體使用方法會發生變化
    pupil := &Pupil{}
    pupil.Student.Name = "tom~"
    pupil.Student.Age = 8
    pupil.testing() 
    pupil.Student.SetScore(70)
    pupil.Student.ShowInfo()
    fmt.Println("res=", pupil.Student.GetSum(1, 2))


    graduate := &Graduate{}
    graduate.Student.Name = "mary~"
    graduate.Student.Age = 28
    graduate.testing() 
    graduate.Student.SetScore(90)
    graduate.Student.ShowInfo()
    fmt.Println("res=", graduate.Student.GetSum(10, 20))
}

繼承能夠解決代碼複用,讓咱們的編程更加靠近人類思惟。
當多個結構體存在相同的屬性(字段)和方法時,能夠從這些結構體中抽象出結構體(好比剛纔的
Student),在該結構體中定義這些相同的屬性和方法。
其它的結構體不須要從新定義這些屬性(字段)和方法,只需嵌套一個 Student 匿名結構體便可
也就是說:在 Golang 中,若是一個 struct 嵌套了另外一個匿名結構體,那麼這個結構體能夠直接訪
問匿名結構體的字段和方法,從而實現了繼承特性。this

嵌套匿名結構體的基本語法

type Goods struct {
Name string
Price int
}
type Book struct {
Goods //這裏就是嵌套匿名結構體 Goods
Writer string
}

繼承給編程帶來的便利

1) 代碼的複用性提升了
2) 代碼的擴展性和維護性提升了

繼承的深刻討論

1) 結構體能夠 使用嵌套匿名結構體全部的字段和方法,即:首字母大寫或者小寫的字段、方法,
均可以使用。【舉例說明】
2) 匿名結構體字段訪問能夠簡化

package main

import (
    "fmt"
)

type A struct {
    Name string
    age int
}

func (a *A) SayOk() {
    fmt.Println("A SayOk", a.Name)
}

func (a *A) hello() {
    fmt.Println("A hello", a.Name)
}

type B struct {
    A
    Name string 
}

func (b *B) SayOk() {
    fmt.Println("B SayOk", b.Name)
}

func main() {

    // var b B
    // b.A.Name = "tom"
    // b.A.age = 19
    // b.A.SayOk()
    // b.A.hello()

    // //上面的寫法能夠簡化

    // b.Name = "smith"
    // b.age = 20
    // b.SayOk()
    // b.hello()

    var b B
    b.Name = "jack" // ok
    b.A.Name = "scott"
    b.age = 100  //ok
    b.SayOk()  // B SayOk  jack
    b.A.SayOk() //  A SayOk scott
    b.hello() //  A hello ? "jack" 仍是 "scott"

}

對上面的代碼小結

(1) 當咱們直接經過 b 訪問字段或方法時,其執行流程以下好比 b.Name

(2) 編譯器會先看 b 對應的類型有沒有 Name, 若是有,則直接調用 B 類型的 Name 字段

(3) 若是沒有就去看 B 中嵌入的匿名結構體 A 有沒有聲明 Name 字段,若是有就調用,若是沒有
繼續查找..若是都找不到就報錯.
3) 當 結構體和 匿名結構體有相同的字段或者方法時, 編譯器採用就近訪問原則訪問,如但願訪問
匿名結構體的字段和方法,能夠經過匿名結構體名來區分【舉例說明】
4) 結構體嵌入兩個(或多個)匿名結構體,如 兩個匿名結構體有相同的字段和方法( 同時結構體自己
沒有同名的字段和方法),在訪問時,就必須明確指定匿名結構體名字,不然編譯報錯。【舉例說明】
5) 若是一個 struct 嵌套了一個有名結構體,這種模式就是 組合,若是是組合關係,那麼在訪問組合
的結構體的字段或方法時,必須帶上結構體的名字
6) 嵌套匿名結構體後,也能夠在建立結構體變量(實例)時,直接指定各個 匿名結構體字段的值
說明
1) 若是一個結構體有 int 類型的匿名字段,就不能第二個。
2) 若是須要有多個 int 的字段,則必須給 int 字段指定名字

面向對象編程-多重繼承

多重繼承說明
如 一個 struct 嵌套了多個匿名結構體,那麼該結構體能夠直接訪問嵌套的匿名結構體的字段和方
法, 從而實現了多重繼承。
多重繼承細節說明
1) 如嵌入的匿名結構體有相同的字段名或者方法名,則在訪問時,須要經過匿名結構體類型名來
區分。【案例演示】
2) 爲了保證代碼的簡潔性,建議你們儘可能不使用多重繼承

package main
import (
    "fmt"
)

type A struct {
    Name string
    age int
}
type B struct {
    Name string
    Score float64
}
type C struct {
    A
    B
    //Name string
}

type D struct {
    a A //有名結構體
}


type Goods struct {
    Name string
    Price float64
}

type Brand struct {
    Name string
    Address string
}

type TV struct {
    Goods
    Brand   
}

type TV2 struct {
    *Goods
    *Brand  
}

type Monster struct  {
    Name string
    Age int
}

type E struct {
    Monster
    int
    n int
}

func main() {
    var c C
    //若是c 沒有Name字段,而A 和 B有Name, 這時就必須經過指定匿名結構體名字來區分
    //因此 c.Name 就會包編譯錯誤, 這個規則對方法也是同樣的!
    c.A.Name = "tom" // error
    fmt.Println("c")

    //若是D 中是一個有名結構體,則訪問有名結構體的字段時,就必須帶上有名結構體的名字
    //好比 d.a.Name 
    var d D 
    d.a.Name = "jack"


    //嵌套匿名結構體後,也能夠在建立結構體變量(實例)時,直接指定各個匿名結構體字段的值
    tv := TV{ Goods{"電視機001", 5000.99},  Brand{"海爾", "山東"}, }

    //演示訪問Goods的Name
    fmt.Println(tv.Goods.Name)
    fmt.Println(tv.Price) 

    tv2 := TV{ 
        Goods{
            Price : 5000.99,
            Name : "電視機002", 
        },  
        Brand{
            Name : "夏普", 
            Address :"北京",
        }, 
    }

    fmt.Println("tv", tv)
    fmt.Println("tv2", tv2)

    tv3 := TV2{ &Goods{"電視機003", 7000.99},  &Brand{"創維", "河南"}, }

    tv4 := TV2{ 
            &Goods{
                Name : "電視機004", 
                Price : 9000.99,
            },  
            &Brand{
                Name : "長虹", 
                Address : "四川",
            }, 
        }

    fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
    fmt.Println("tv4", *tv4.Goods, *tv4.Brand)


    //演示一下匿名字段時基本數據類型的使用
    var e E
    e.Name = "狐狸精"
    e.Age = 300
    e.int = 20
    e.n = 40
    fmt.Println("e=", e)

}

接口(interface)

基本介紹

按順序,咱們應該學習多態,可是在學習多態前,咱們須要講解接口(interface),由於在 Golang 中 多態
特性主要是經過接口來體現的。

接口快速入門

這樣的設計需求在 Golang 編程中也是會大量存在的,我曾經說過,一個程序就是一個世界,在現實世
界存在的狀況,在程序中也會出現。 咱們用程序來模擬一下前面的應用場景。
代碼實現

package main
import (
    "fmt"
)

//聲明/定義一個接口
type Usb interface {
    //聲明瞭兩個沒有實現的方法
    Start() 
    Stop()
}


//聲明/定義一個接口
type Usb2 interface {
    //聲明瞭兩個沒有實現的方法
    Start() 
    Stop()
    Test()
}



type Phone struct {

}  

//讓Phone 實現 Usb接口的方法
func (p Phone) Start() {
    fmt.Println("手機開始工做。。。")
}
func (p Phone) Stop() {
    fmt.Println("手機中止工做。。。")
}

type Camera struct {

}
//讓Camera 實現   Usb接口的方法
func (c Camera) Start() {
    fmt.Println("相機開始工做~~~。。。")
}
func (c Camera) Stop() {
    fmt.Println("相機中止工做。。。")
}


//計算機
type Computer struct {

}

//編寫一個方法Working 方法,接收一個Usb接口類型變量
//只要是實現了 Usb接口 (所謂實現Usb接口,就是指實現了 Usb接口聲明全部方法)
func (c Computer) Working(usb Usb) {

    //經過usb接口變量來調用Start和Stop方法
    usb.Start()
    usb.Stop()
}

func main() {

    //測試
    //先建立結構體變量
    computer := Computer{}
    phone := Phone{}
    camera := Camera{}

    //關鍵點
    computer.Working(phone)
    computer.Working(camera) //
}

接口概念的再說明

interface 類型能夠定義一組方法,可是這些不須要實現。而且 interface 不能包含任何變量。到某個
自定義類型(好比結構體 Phone)要使用的時候,在根據具體狀況把這些方法寫出來(實現)。

說明
1) 接口裏的 全部方法都沒有方法體,即接口的方法都是沒有實現的方法。接口體現了程序設計的
多態和 高內聚低偶合的思想。
2) Golang 中的接口, 不須要 顯式的實現。只要一個變量,含有接口類型中的全部方法,那麼這個
變量就實現這個接口。所以,Golang 中 沒有 implement 這樣的關鍵字

注意事項和細節
1) 接口自己 不能建立實例,可是 能夠指向一個實現了該接口的自定義類型的變量(實例)
2) 接口中全部的方法都沒有方法體,即都是沒有實現的方法。
3) 在 Golang 中,一個自定義類型須要將某個接口的全部方法都實現,咱們說這個自定義類型實現了該接口。
4) 一個自定義類型只有實現了某個接口,才能將該自定義類型的實例(變量)賦給接口類型
5) 只要是自定義數據類型,就能夠實現接口,不只僅是結構體類型。
6) 一個自定義類型能夠實現多個接口
7) Golang 接口中不能有任何變量
8) 一個接口(好比 A 接口)能夠繼承多個別的接口(好比 B,C 接口),這時若是要實現 A 接口,也必須將 B,C 接口的方法也所有實現。
9) interface 類型默認是一個指針(引用類型),若是沒有對 interface 初始化就使用,那麼會輸出 nil
10) 空接口 interface{} 沒有任何方法, 因此全部類型都實現了空接 口, 即咱們能夠 把任何一個變量
賦給空接口。

package main
import (
    "fmt"
)



type Stu struct {
    Name string
}

func (stu Stu) Say() {
    fmt.Println("Stu Say()")
}


type integer int

func (i integer) Say() {
    fmt.Println("integer Say i =" ,i )
}


type AInterface interface {
    Say()
}

type BInterface interface {
    Hello()
}
type Monster struct {

}
func (m Monster) Hello() {
    fmt.Println("Monster Hello()~~")
}

func (m Monster) Say() {
    fmt.Println("Monster Say()~~")
}

func main() {
    var stu Stu //結構體變量,實現了 Say() 實現了 AInterface
    var a AInterface = stu
    a.Say()


    var i integer = 10
    var b AInterface = i
    b.Say() // integer Say i = 10


    //Monster實現了AInterface 和 BInterface
    var monster Monster
    var a2 AInterface = monster
    var b2 BInterface = monster
    a2.Say()
    b2.Hello()
}
package main
import (
    "fmt"
)

type BInterface interface {
    test01()
}

type CInterface interface {
    test02()
}

type AInterface interface {
    BInterface
    CInterface
    test03()
}

//若是須要實現AInterface,就須要將BInterface CInterface的方法都實現
type Stu struct {
}
func (stu Stu) test01() {

}
func (stu Stu) test02() {
    
}
func (stu Stu) test03() {
    
}

type T  interface{

}

func main() {
    var stu Stu
    var a AInterface = stu
    a.test01()

    var t T = stu //ok
    fmt.Println(t)
    var t2 interface{}  = stu
    var num1 float64 = 8.8
    t2 = num1
    t = num1
    fmt.Println(t2, t)
}
package main
import "fmt"
type Usb interface {
    Say()
}
type Stu struct {
}
func (this *Stu) Say() {
    fmt.Println("Say()")
}
func main() {
    var stu Stu = Stu{}
    // 錯誤! 會報 Stu類型沒有實現Usb接口 , 
    // 若是但願經過編譯,  var u Usb = &stu
    var u Usb = stu  
    u.Say()
    fmt.Println("here", u)
}

接口編程的最佳實踐

實現對 Hero 結構體切片的排序: sort.Sort(data Interface)

package main
import (
    "fmt"
    "sort"
    "math/rand"
)

//1.聲明Hero結構體
type  Hero struct{
    Name string
    Age int
}

//2.聲明一個Hero結構體切片類型
type HeroSlice []Hero

//3.實現Interface 接口
func (hs HeroSlice) Len() int {
    return len(hs)
}

//Less方法就是決定你使用什麼標準進行排序
//1. 按Hero的年齡從小到大排序!!
func (hs HeroSlice) Less(i, j int) bool {
    return hs[i].Age < hs[j].Age
    //修改爲對Name排序
    //return hs[i].Name < hs[j].Name
}

func (hs HeroSlice) Swap(i, j int) {
    //交換
    // temp := hs[i]
    // hs[i] = hs[j]
    // hs[j] = temp
    //下面的一句話等價於三句話
    hs[i], hs[j] = hs[j], hs[i]
}


//1.聲明Student結構體
type  Student struct{
    Name string
    Age int
    Score float64
}

//將Student的切片,安Score從大到小排序!!

func main() {

    //先定義一個數組/切片
    var intSlice = []int{0, -1, 10, 7, 90}
    //要求對 intSlice切片進行排序
    //1. 冒泡排序...
    //2. 也可使用系統提供的方法 
    sort.Ints(intSlice) 
    fmt.Println(intSlice)

    //請你們對結構體切片進行排序
    //1. 冒泡排序...
    //2. 也可使用系統提供的方法

    //測試看看咱們是否能夠對結構體切片進行排序
    var heroes HeroSlice
    for i := 0; i < 10 ; i++ {
        hero := Hero{
            Name : fmt.Sprintf("英雄|%d", rand.Intn(100)),
            Age : rand.Intn(100),
        }
        //將 hero append到 heroes切片
        heroes = append(heroes, hero)
    }

    //看看排序前的順序
    for _ , v := range heroes {
        fmt.Println(v)
    }

    //調用sort.Sort
    sort.Sort(heroes)
    fmt.Println("-----------排序後------------")
    //看看排序後的順序
    for _ , v := range heroes {
        fmt.Println(v)
    }

    i := 10
    j := 20
    i, j = j, i
    fmt.Println("i=", i, "j=", j) // i=20 j = 10
}

實現接口 vs 繼承

你們可能會對實現接口和繼承比較迷茫了, 那麼他們究竟有什麼區別呢

package main
import (
    "fmt"
)

//Monkey結構體
type Monkey struct {
    Name string
}

//聲明接口
type BirdAble interface {
    Flying()
}

type FishAble interface {
    Swimming()
}

func (this *Monkey) climbing() {
    fmt.Println(this.Name, " 生來會爬樹..")
}

//LittleMonkey結構體
type LittleMonkey struct {
    Monkey //繼承
}


//讓LittleMonkey實現BirdAble
func (this *LittleMonkey) Flying() {
    fmt.Println(this.Name, " 經過學習,會飛翔...")
}

//讓LittleMonkey實現FishAble
func (this *LittleMonkey) Swimming() {
    fmt.Println(this.Name, " 經過學習,會游泳..")
}

func main() {

    //建立一個LittleMonkey 實例
    monkey := LittleMonkey{
        Monkey {
            Name : "悟空",
        },
    }
    monkey.climbing()
    monkey.Flying()
    monkey.Swimming()

}

1) 當 A 結構體繼承了 B 結構體,那麼 A 結構就自動的繼承了 B 結構體的字段和方法,而且能夠直
接使用
2) 當 A 結構體須要擴展功能,同時不但願去破壞繼承關係,則能夠去實現某個接口便可,所以咱們能夠認爲:實現接口是對繼承機制的補充.實現接口能夠看做是對 繼承的一種補充

接口和繼承解決的解決的問題不一樣
繼承的價值主要在於:解決代碼的 複用性和 可維護性。
接口的價值主要在於: 設計,設計好各類規範(方法),讓其它自定義類型去實現這些方法。
接口比繼承更加靈活 Person Student BirdAble LittleMonkey
接口比繼承更加靈活,繼承是知足 is - a 的關係,而接口只需知足 like - a 的關係。
接口在必定程度上實現 代碼解耦

面向對象編程-多態

基本介紹

變量(實例)具備多種形態。面向對象的第三大特徵,在 Go 語言,多態特徵是經過接口實現的。能夠按照統一的接口來調用不一樣的實現。這時接口變量就呈現不一樣的形態。
快速入門
在前面的 Usb 接口案例,Usb usb ,既能夠接收手機變量,又能夠接收相機變量,就體現了 Usb 接口 多態特性。

接口體現多態的兩種形式

多態參數

在前面的 Usb 接口案例,Usb usb ,便可以接收手機變量,又能夠接收相機變量,就體現了 Usb 接多態。
多態數組
演示一個案例:給 Usb 數組中,存放 Phone 結構體 和 Camera 結構體變量
案例說明:

package main
import (
    "fmt"
)

//聲明/定義一個接口
type Usb interface {
    //聲明瞭兩個沒有實現的方法
    Start()
    Stop()
}

type Phone struct {
    name string
}  

//讓Phone 實現 Usb接口的方法
func (p Phone) Start() {
    fmt.Println("手機開始工做。。。")
}
func (p Phone) Stop() {
    fmt.Println("手機中止工做。。。")
}


type Camera struct {
    name string
}
//讓Camera 實現   Usb接口的方法
func (c Camera) Start() {
    fmt.Println("相機開始工做。。。")
}
func (c Camera) Stop() {
    fmt.Println("相機中止工做。。。")
}



func main() {
    //定義一個Usb接口數組,能夠存放Phone和Camera的結構體變量
    //這裏就體現出多態數組
    var usbArr [3]Usb
    usbArr[0] = Phone{"vivo"}
    usbArr[1] = Phone{"小米"}
    usbArr[2] = Camera{"尼康"}
    
    fmt.Println(usbArr)

}

類型斷言

由一個具體的須要,引出了類型斷言
基本介紹
類型斷言,因爲接口是通常類型,不知道具體類型,若是要轉成具體類型,就須要使用類型斷言,
具體的以下:

package main
import (
    "fmt"
)
type Point struct {
    x int
    y int
}
func main() {
    var a interface{}
    var point Point = Point{1, 2}
    a = point  //oK
    // 如何將 a 賦給一個Point變量?
    var b Point
    // b = a 不能夠
    // b = a.(Point) // 能夠
    b = a.(Point) 
    fmt.Println(b) // 


    //類型斷言的其它案例
    // var x interface{}
    // var b2 float32 = 1.1
    // x = b2  //空接口,能夠接收任意類型
    // // x=>float32 [使用類型斷言]
    // y := x.(float32)
    // fmt.Printf("y 的類型是 %T 值是=%v", y, y)


    //類型斷言(帶檢測的)
    var x interface{}
    var b2 float32 = 2.1
    x = b2  //空接口,能夠接收任意類型
    // x=>float32 [使用類型斷言]

    //類型斷言(帶檢測的)
    if y, ok := x.(float32); ok {
        fmt.Println("convert success")
        fmt.Printf("y 的類型是 %T 值是=%v", y, y)
    } else {
        fmt.Println("convert fail")
    }
    fmt.Println("繼續執行...")

}

對上面代碼的說明:
在進行類型斷言時,若是類型不匹配,就會報 panic, 所以進行類型斷言時,要確保原來的空接口
指向的就是斷言的類型.
如何在進行斷言時,帶上檢測機制,若是成功就 ok,不然也不要報 panic

類型斷言的最佳實踐 1

在前面的 Usb 接口案例作改進:
給 Phone 結構體增長一個特有的方法 call(), 當 Usb 接口接收的是 Phone 變量時,還須要調用 call
方法, 走代碼:

package main
import (
    "fmt"
)

//聲明/定義一個接口
type Usb interface {
    //聲明瞭兩個沒有實現的方法
    Start()
    Stop()
}

type Phone struct {
    name string
}  

//讓Phone 實現 Usb接口的方法
func (p Phone) Start() {
    fmt.Println("手機開始工做。。。")
}
func (p Phone) Stop() {
    fmt.Println("手機中止工做。。。")
}

func (p Phone) Call() {
    fmt.Println("手機 在打電話..")
}


type Camera struct {
    name string
}
//讓Camera 實現   Usb接口的方法
func (c Camera) Start() {
    fmt.Println("相機開始工做。。。")
}
func (c Camera) Stop() {
    fmt.Println("相機中止工做。。。")
}

type Computer struct {

}

func (computer Computer) Working(usb Usb) {
    usb.Start()
    //若是usb是指向Phone結構體變量,則還須要調用Call方法
    //類型斷言..[注意體會!!!]
    if phone, ok := usb.(Phone); ok {
        phone.Call()
    }
    usb.Stop()
}

func main() {
    //定義一個Usb接口數組,能夠存放Phone和Camera的結構體變量
    //這裏就體現出多態數組
    var usbArr [3]Usb
    usbArr[0] = Phone{"vivo"}
    usbArr[1] = Phone{"小米"}
    usbArr[2] = Camera{"尼康"}

    //遍歷usbArr
    //Phone還有一個特有的方法call(),請遍歷Usb數組,若是是Phone變量,
    //除了調用Usb 接口聲明的方法外,還須要調用Phone 特有方法 call. =》類型斷言
    var computer Computer
    for _, v := range usbArr{
        computer.Working(v)
        fmt.Println()
    }
    //fmt.Println(usbArr)
}

 類型斷言的最佳實踐 2

寫一函數,循環判斷傳入參數的類型:

package main
import (
    "fmt"
)


//定義Student類型
type Student struct {

}

//編寫一個函數,能夠判斷輸入的參數是什麼類型
func TypeJudge(items... interface{}) {
    for index, x := range items {
        switch x.(type) {
            case bool :
                fmt.Printf("第%v個參數是 bool 類型,值是%v\n", index, x)
            case float32 :
                fmt.Printf("第%v個參數是 float32 類型,值是%v\n", index, x)
            case float64 :
                fmt.Printf("第%v個參數是 float64 類型,值是%v\n", index, x)
            case int, int32, int64 :
                fmt.Printf("第%v個參數是 整數 類型,值是%v\n", index, x)
            case string :
                fmt.Printf("第%v個參數是 string 類型,值是%v\n", index, x)
            case Student :
                fmt.Printf("第%v個參數是 Student 類型,值是%v\n", index, x)
            case *Student :
                fmt.Printf("第%v個參數是 *Student 類型,值是%v\n", index, x)
            default :
                fmt.Printf("第%v個參數是  類型 不肯定,值是%v\n", index, x)
        }
    }
}

func main() {

    var n1 float32 = 1.1
    var n2 float64 = 2.3
    var n3 int32 = 30
    var name string = "tom"
    address := "北京"
    n4 := 300

    stu1 := Student{}
    stu2 := &Student{}

    TypeJudge(n1, n2, n3, name, address, n4, stu1, stu2)


}

go的面向對象終於完了(^▽^)!~~~~

相關文章
相關標籤/搜索