本文介紹SpringBoot相關内容。和【跨考菌】一起加油吧~

如果你有收獲,記得幫部落客一鍵三連哦😊
1 結構體
1.1 golang面向對象程式設計說明
-
Golang 也支援面向對象程式設計(OOP), 但是和傳統的面向對象程式設計有差別, 并不是純粹的面向對
象語言。 是以我們說 Golang 支援面向對象程式設計特性是比較準确的。
- Golang 沒有類(class), Go 語言的結構體(struct)和其它程式設計語言的類(class)有同等的地位, 你可以了解 Golang 是基于 struct 來實作 OOP 特性的。
-
Golang 面向對象程式設計非常簡潔, 去掉了傳統 OOP 語言的繼承、 方法重載、 構造函數和析構函
數、 隐藏的 this 指針等等
-
Golang 仍然有面向對象程式設計的繼承, 封裝和多态的特性, 隻是實作的方式和其它 OOP 語言不
一樣, 比如繼承 : Golang 沒有 extends 關鍵字, 繼承是通過匿名字段來實作。
-
Golang 面向對象(OOP)很優雅, OOP 本身就是語言類型系統(type system)的一部分, 通過接口
(interface)關聯, 耦合性低, 也非常靈活。 後面同學們會充分體會到這個特點。 也就是說在 Golang 中面向接口程式設計是非常重要的特性。
1.2 結構體和結構體變量的關系圖
說明:
- 将一類事物的特性提取出來(比如貓類), 形成一個新的資料類型, 就是一個結構體。
- 通過這個結構體, 我們可以建立多個變量(執行個體/對象)
- 事物可以貓類, 也可以是 Person , Fish 或是某個工具類。 。 。
1.3 簡單案例
1.4 結構體和結構體變量的差別和聯系
- 結構體是自定義的資料類型, 代表一類事物.
- 結構體變量(執行個體)是具體的, 實際的, 代表一個具體變量
1.5 結構體記憶體布局
1.6 如何聲明結構體
- 基本文法
type 結構體名稱 struct {
field1 type
field2 type
}
- 舉例:
type Student struct {
Name string //字段
Age int //字段
Score float32
}
1.7 字段/屬性
1.7.1 基本介紹
- 從概念或叫法上看: 結構體字段 = 屬性 = field (即授課中, 統一叫字段)
-
字段是結構體的一個組成部分, 一般是基本資料類型、 數組,也可是引用類型。 比如我們前面定
義貓結構體 的 Name string 就是屬性
注意事項和細節說明
- 字段聲明文法同變量, 示例:
字段名 字段類型
- 字段的類型可以為: 基本類型、 數組或引用類型
-
在建立一個結構體變量後, 如果沒有給字段指派, 都對應一個零值(預設值), 規則同前面講的
一樣:
布爾類型是 false , 數值是 0 , 字元串是 “”。
數組類型的預設值和它的元素類型相關, 比如 score [3]int 則為[0, 0, 0]
指針, slice, 和 map 的零值都是 nil , 即還沒有配置設定空間。
案例示範:
package main
import (
"fmt"
)
//如果結構體的字段類型是: 指針,slice,和map的零值都是 nil ,即還沒有配置設定空間
//如果需要使用這樣的字段,需要先make,才能使用.
type Person struct{
Name string
Age int
Scores [5]float64
ptr *int //指針
slice []int //切片
map1 map[string]string //map
}
type Monster struct{
Name string
Age int
}
func main() {
//定義結構體變量
var p1 Person
fmt.Println(p1)
if p1.ptr == nil {
fmt.Println("ok1")
}
if p1.slice == nil {
fmt.Println("ok2")
}
if p1.map1 == nil {
fmt.Println("ok3")
}
//使用slice, 再次說明,一定要make
p1.slice = make([]int, 10)
p1.slice[0] = 100 //ok
//使用map, 一定要先make
p1.map1 = make(map[string]string)
p1.map1["key1"] = "tom~"
fmt.Println(p1)
-
不同結構體變量的字段是獨立, 互不影響, 一個結構體變量字段的更改, 不影響另外一個, 結構體是值類型!!!
案例:
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言
1.8 建立結構體變量和通路結構體字段
方式 1-直接聲明
案例示範:
var person Person
方式 2-{}
案例示範:
var person Person = Person{}
方式 3-&
案例:
var person *Person = new (Person)
方式 4-{}
案例:
var person *Person = &Person{}
說明:
- 第 3 種和第 4 種方式傳回的是 結構體指針。
- 結構體指針通路字段的标準方式應該是:
, 比如(*結構體指針).字段名
(*person).Name = "tom"
-
但 go 做了一個簡化, 也支援 結構體指針.字段名, 比如 person.Name = “tom”。 更加符合程式員
使用的習慣, go 編譯器底層 *對 person.Name 做了轉化 (person).Name。
1.9 struct類型的記憶體配置設定機制
- 先來看一個思考題
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 基本說明
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 結構體在記憶體中示意圖
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 看下面的代碼,分析原因
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 上述代碼記憶體分析:【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 分析下面代碼錯誤原因
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言
1.10 結構體使用細節
- 結構體的所有字段在記憶體中是連續的
-
結構體是使用者單獨定義的類型, 和其它類型進行轉換時需要有完全相同的字段(名字、 個數和類
型)
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 結構體進行 type 重新定義(相當于取别名), Golang 認為是新的資料類型, 但是互相間可以強轉
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 -
struct 的每個字段上, 可以寫上一個 tag, 該 tag 可以通過反射機制擷取, 常見的使用場景就是序
列化和反序列化。
- 序列化的使用場景:
2 方法
2.1 基本介紹
在某些情況下, 我們要需要聲明(定義)方法。 比如 Person 結構體:除了有一些字段外( 年齡, 姓名…),Person 結構體還有一些行為,比如:可以說話、 跑步…,通過學習, 還可以做算術題。 這時就要用方法才能完成。
Golang 中的方法是作用在指定的資料類型上的(即: 和指定的資料類型綁定), 是以自定義類型,都可以有方法, 而不僅僅是 struct。
2.2 方法的聲明和調用
文法說明:
- func (a A) test() {} 表示 A 結構體有一方法, 方法名為 test
- (a A) 展現 test 方法是和 A 類型綁定的
舉例說明:
總結:
- test 方法和 Person 類型綁定
-
test 方法隻能通過 Person 類型的變量來調用, 而不能直接調用, 也不能使用其它類型變量來調
用
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 -
func (p Person) test() {}… p 表示哪個 Person 變量調用, 這個 p 就是它的副本, 這點和函數傳參非
常相似。
- p 這個名字, 有程式員指定, 不是固定, 比如修改成 person 也是可以
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言
2.3 方法入門
- 給 Person 結構體添加 speak 方法,輸出 xxx 是一個好人
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 -
給 Person 結構體添加 jisuan 方法,可以計算從 1+…+1000 的結果, 說明方法體内可以函數一樣,
進行各種運算
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 給 Person 結構體 jisuan2 方法,該方法可以接收一個數 n, 計算從 1+…+n 的結果
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 給 Person 結構體添加 getSum 方法,可以計算兩個數的和, 并傳回結果
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 方法的調用
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言
2.4 方法的聲明/定義
func (recevier type) methodName(參數清單) (傳回值清單){
方法體
return 傳回值
}
- 參數清單: 表示方法輸入
- recevier type : 表示這個方法和 type 這個類型進行綁定, 或者說該方法作用于 type 類型
- receiver type : type 可以是結構體, 也可以其它的自定義類型
- receiver : 就是 type 類型的一個變量(執行個體), 比如 : Person 結構體 的一個變量(執行個體)
- 傳回值清單: 表示傳回的值, 可以多個
- 方法主體: 表示為了實作某一功能代碼塊
- return 語句不是必須的
2.5 使用細節
- 結構體類型是值類型, 在方法調用中, 遵守值類型的傳遞機制, 是值拷貝傳遞方式
- 如程式員希望在方法中, 修改結構體變量的值, 可以通過結構體指針的方式來處理
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 -
Golang 中的方法作用在指定的資料類型上的(即: 和指定的資料類型綁定), 是以自定義類型,
都可以有方法, 而不僅僅是 struct, 比如 int , float32 等都可以有方法
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 -
方法的通路範圍控制的規則, 和函數一樣。 方法名首字母小寫, 隻能在本包通路, 方法首字母
大寫, 可以在本包和其它包通路。 [講解]
- 如果一個類型實作了 String()這個方法, 那麼 fmt.Println 預設會調用這個變量的 String()進行輸出
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言
2.6 方法和函數的差別
1) 調用方式不一樣
函數的調用方式:
函數名(實參清單)
方法的調用方式:
變量.方法名(實參清單)
2) 對于普通函數, 接收者為值類型時, 不能将指針類型的資料直接傳遞, 反之亦然
3) 對于方法(如 struct 的方法) , 接收者為值類型時, 可以直接用指針類型的變量調用方法, 反
過來同樣也可以
總結:
- 不管調用形式如何, 真正決定是值拷貝還是位址拷貝, 看這個方法是和哪個類型綁定.
-
*如果是和值類型, 比如 (p Person) , 則是值拷貝, 如果和指針類型, 比如是 (p Person) 則
是位址拷貝。
3 工廠模式
3.1 說明
Golang 的結構體沒有構造函數, 通常可以使用工廠模式來解決這個問題。
3.2 先看一個需求
3.3 工廠模式如何解決這個問題呢?
使用工廠模式實作跨包建立結構體執行個體(變量)的案例:
如果 model 包的 結構體變量首字母大寫, 引入後, 直接使用, 沒有問題
如果 model 包的 結構體變量首字母小寫, 引入後, 不能直接使用, 可以工廠模式解決, 看代碼:
student.go
main.go
3.4 思考
如果 model 包的 student 的結構體的字段 Score 改成 score, 我們還能正常通路
嗎? 又應該如何解決這個問題呢?
4 面向對象程式設計三大特性-封裝
4.1 封裝的好處
- 隐藏實作細節
- 提可以對資料進行驗證, 保證安全合理(Age)
如何展現封裝呢?
- 對結構體中的屬性進行封裝
- 通過方法, 包 實作封裝
4.2 封裝實作步驟
- 将結構體、 字段(屬性)的首字母小寫(不能導出了, 其它包不能使用, 類似 private)
- 給結構體所在包提供一個工廠模式的函數, 首字母大寫。 類似一個構造函數
- 提供一個首字母大寫的 Set 方法(類似其它語言的 public), 用于對屬性判斷并指派
func (var 結構體類型名) SetXxx(參數清單) (傳回值清單) {
//加入資料驗證的業務邏輯
var.字段 = 參數
}
- 提供一個首字母大寫的 Get 方法(類似其它語言的 public), 用于擷取屬性的值
func (var 結構體類型名) GetXxx() {
return var.age;
}
特别說明: 在 Golang 開發中并沒有特别強調封裝, 這點并不像 Java. 是以提醒學過 java 的朋友,不用總是用 java 的文法特性來看待 Golang, Golang 本身對面向對象的特性做了簡化的.
4.3 簡單案例
請大家看一個程式(person.go),不能随便檢視人的年齡,工資等隐私, 并對輸入的年齡進行合理的驗證。 設計: model 包(person.go) main 包(main.go 調用 Person 結構體)
model/person.go
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
}
main/main.go
package main
import (
"fmt"
"test/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())
}
4.4 案例2
- 建立程式,在 model 包中定義 Account 結構體: 在 main 函數中體會 Golang 的封裝性。
- Account 結構體要求具有字段: 賬号(長度在 6-10 之間) 、 餘額(必須>20)、 密碼(必須是六
- 通過 SetXxx 的方法給 Account 的字段指派。
- 在 main 函數中測試
model/account.go
package model
import (
"fmt"
)
//定義一個結構體account
type account struct {
accountNo string
pwd string
balance float64
}
//工廠模式的函數-構造函數
func NewAccount(accountNo string, pwd string, balance float64) *account {
if len(accountNo) < 6 || len(accountNo) > 10 {
fmt.Println("賬号的長度不對...")
return nil
}
if len(pwd) != 6 {
fmt.Println("密碼的長度不對...")
return nil
}
if balance < 20 {
fmt.Println("餘額數目不對...")
return nil
}
return &account{
accountNo : accountNo,
pwd : pwd,
balance : balance,
}
}
//方法
//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)
}
main/main.go
package main
import (
"fmt"
"test/chapter11/encapexercise/model"
)
func main() {
//建立一個account變量
account := model.NewAccount("jzh11111", "000", 40)
if account != nil {
fmt.Println("建立成功=", account)
} else {
fmt.Println("建立失敗")
}
}
5 面向對象程式設計三大特性-繼承
5.1 為什麼需要繼承呢?
先看一個例子:
看個學生考試系統的程式 extends01.go, 提出代碼複用的問題
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))
}
對上面代碼的小結
-
Pupil 和 Graduate 兩個結構體的字段和方法幾乎, 但是我們卻寫了相同的代碼, 代碼複用性不
強
- 出現代碼備援, 而且代碼不利于維護, 同時也不利于功能的擴充。
- 解決方法-通過繼承方式來解決
5.2 繼承的示意圖
也就是說: 在 Golang 中, 如果一個 struct 嵌套了另一個匿名結構體, 那麼這個結構體可以直接通路匿名結構體的字段和方法, 進而實作了繼承特性。
5.3 嵌套匿名構造體的基本文法
5.4 使用案例
對上述備援的代碼利用繼承來改進:
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))
}
發現,繼承給程式設計帶來下面的便利:
- 代碼的複用性提高了
- 代碼的擴充性和維護性提高了
5.5 繼承的深入
-
結構體可以使用嵌套匿名結構體所有的字段和方法, 即: 首字母大寫或者小寫的字段、 方法,
都可以使用。 【舉例說明】
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 匿名結構體字段通路可以簡化, 如圖
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 對上面的代碼小結
(1) 當我們直接通過 b 通路字段或方法時, 其執行流程如下比如 b.Name
(2) 編譯器會先看 b 對應的類型有沒有 Name, 如果有, 則直接調用 B 類型的 Name 字段
(3) 如果沒有就去看 B 中嵌入的匿名結構體 A 有沒有聲明 Name 字段, 如果有就調用,如果沒有
繼續查找…如果都找不到就報錯.
-
當結構體和匿名結構體有相同的字段或者方法時, 編譯器采用就近通路原則通路, 如希望通路
匿名結構體的字段和方法, 可以通過匿名結構體名來區分【舉例說明】
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 -
結構體嵌入兩個(或多個)匿名結構體, 如兩個匿名結構體有相同的字段和方法(同時結構體本身
沒有同名的字段和方法), 在通路時, 就必須明确指定匿名結構體名字, 否則編譯報錯。 【舉例說明】
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 如果一個 struct 嵌套了一個有名結構體, 這種模式就是組合, 如果是組合關系, 那麼在通路組合的結構體的字段或方法時, 必須帶上結構體的名字
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 嵌套匿名結構體後, 也可以在建立結構體變量(執行個體)時, 直接指定各個匿名結構體字段的值
5.6 案例剖析
結構體的匿名字段是基本資料類型, 如何通路, 下面代碼輸出什麼
說明
- 如果一個結構體有 int 類型的匿名字段, 就不能第二個。
- 如果需要有多個 int 的字段, 則必須給 int 字段指定名字
5 面向對象程式設計三大特性-多重繼承
-
多重繼承說明
如一個 struct 嵌套了多個匿名結構體, 那麼該結構體可以直接通路嵌套的匿名結構體的字段和方
法, 進而實作了多重繼承。
-
案例示範
通過一個案例來說明多重繼承使用
多重繼承細節說明【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言
-
如嵌入的匿名結構體有相同的字段名或者方法名, 則在通路時, 需要通過匿名結構體類型名來
區分。 【案例示範】
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 為了保證代碼的簡潔性, 建議大家盡量不使用多重繼承
6 接口
在 Golang 中 多态特性主要是通過接口來展現的。
6.1 接口入門案例
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)
}
6.2 接口基本文法
小結說明:
-
接口裡的所有方法都沒有方法體, 即接口的方法都是沒有實作的方法。 接口展現了程式設計的
多态和高内聚低偶合的思想。
-
Golang 中的接口, 不需要顯式的實作。 隻要一個變量, 含有接口類型中的所有方法, 那麼這個
變量就實作這個接口。 是以, Golang 中沒有 implement 這樣的關鍵字
6.3 注意事項和細節
- 接口本身不能建立執行個體,但是可以指向一個實作了該接口的自定義類型的變量(執行個體)
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言 - 接口中所有的方法都沒有方法體,即都是沒有實作的方法。
-
在 Golang 中, 一個自定義類型需要将某個接口的所有方法都實作, 我們說這個自定義類型實作
了該接口。
- 一個自定義類型隻有實作了某個接口, 才能将該自定義類型的執行個體(變量)賦給接口類型
- 隻要是自定義資料類型, 就可以實作接口, 不僅僅是結構體類型。
6) 一個自定義類型可以實作多個接口
7) Golang 接口中不能有任何變量
8) 一個接口(比如 A 接口)可以繼承多個别的接口(比如 B,C 接口), 這時如果要實作 A 接口, 也必須将 B,C 接口的方法也全部實作。
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() {
}
func main() {
var stu Stu
var a AInterface = stu
a.test01()
}
- interface 類型預設是一個指針(引用類型), 如果沒有對 interface 初始化就使用, 那麼會輸出 nil
-
空接口 interface{} 沒有任何方法, 是以所有類型都實作了空接口, 即我們可以把任何一個變量
賦給空接口。
type T interface{
}
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)
6.4 案例
6.5 案例:實作對 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
}
7 實作接口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()
}
代碼說明:
-
當 A 結構體繼承了 B 結構體, 那麼 A 結構就自動的繼承了 B 結構體的字段和方法, 并且可以直
接使用
-
當 A 結構體需要擴充功能, 同時不希望去破壞繼承關系, 則可以去實作某個接口即可, 是以我
們可以認為: 實作接口是對繼承機制的補充.
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言
-
接口和繼承解決的解決的問題不同
繼承的價值主要在于: 解決代碼的複用性和可維護性。
接口的價值主要在于: 設計, 設計好各種規範(方法), 讓其它自定義類型去實作這些方法。
- 接口比繼承更加靈活
接口比繼承更加靈活, 繼承是滿足 is - a 的關系, 而接口隻需滿足 like - a 的關系。Person Student BirdAble LittleMonkey
- 接口在一定程度上實作代碼解耦
8 類型斷言
8.1 基本介紹
類型斷言, 由于接口是一般類型, 不知道具體類型, 如果要轉成具體類型, 就需要使用類型斷言,具體的如下:
-
對上面代碼的說明:
在進行類型斷言時, 如果類型不比對, 就會報 panic, 是以進行類型斷言時, 要確定原來的空接口指向的就是斷言的類型.
- 如何在進行斷言時, 帶上檢測機制, 如果成功就 ok,否則也不要報 panic
【golang學習總結】11 golang面向對象程式設計1 結構體2 方法3 工廠模式4 面向對象程式設計三大特性-封裝5 面向對象程式設計三大特性-繼承5 面向對象程式設計三大特性-多重繼承6 接口7 實作接口vs繼承8 類型斷言
8.2 類型斷言案例1
在前面的 Usb 接口案例做改進:
給 Phone 結構體增加一個特有的方法 call(), 當 Usb 接口接收的是 Phone 變量時, 還需要調用 call
方法, 走代碼:
func (computer Computer) Working(usb Usb) {
usb.Start()
//如果usb是指向Phone結構體變量,則還需要調用Call方法
//類型斷言..[注意體會!!!]
if phone, ok := usb.(Phone); ok {
phone.Call()
}
usb.Stop()
}
8.2 類型斷言案例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)
}