目錄
- 面向物件編程
- 結構體
- 結構體與結構體變數(實體/物件)的關系示意圖
- 結構體和結構體變數(實體)的區別和聯系
- 結構體變數(實體)在記憶體的布局【重要】
- 欄位/屬性
- 創建結構體變數和訪問結構體欄位
- struct型別的記憶體分配機制
- 結構體使用注意事項和細節
- 方法
- 方法的宣告和呼叫
- 快速入門
- 方法的呼叫和傳參機制原理[重要]
- 方法的宣告(定義)
- 方法的注意事項和細節
- 方法和函式的區別
- 方法練習題
- 面向物件編程應用實體
- 步驟
- 學生案例
- 小狗案例
- 盒子案例
- 景區門票案例
- 創建結構體變數時指定欄位值
- 工廠模式
- 看一個需求
- 工廠模式來解決問題
- 思考題
- 面向物件編程思想-抽象
- 快速入門案例
- 面向物件編程三大特性-封裝
- 封裝的好處
- 如何體現封裝
- 封裝的實作步驟
- 快速入門案例
- 面向物件編程三大特性-繼承
- 看一個問題,引出繼承的必要性
- 繼承基本介紹和示意圖
- 嵌套匿名結構體的基本語法
- 快速入門案例
- 繼承給編程帶來的便利
- 繼承的深入討論
- 課堂練習
- 面向物件編程-多重繼承
- 介面
- 基本語法
- 介面使用的應用場景
- 注意事項和細節
- 介面編程最佳實踐
- 介面練習題
- 實作介面 vs 繼承
- 面向物件編程三大特性 - 多型
- 快速入門案例
- 介面體現多型的兩種形式
- 型別斷言
- 案例演示
- 型別斷言的最佳實踐
- 結構體
面向物件編程
結構體
一個程式就是一個世界,有很多物件(變數)
Go也支持面向物件編程(OOP),但是和傳統的面向物件編程有區別,并不是純粹的面向物件語言,所以說Go支持面向物件編程特性是比較準確的
Go沒有類(class),Go語言的結構體(struct)和其它編程語言的類(class)有同等的地位,可以理解Go是基于struct來實作OOP特性的,
Go面向物件編程非常簡潔,去掉了傳統OOP語言的繼承、方法多載、建構式和解構式、隱藏的this指標等等
Go仍然有面向物件編程的繼承、封裝和多型的特性,只是實作的方式和其它OOP語言不一樣,比如繼承:Go沒有extends關鍵字,繼承是通過匿名欄位來實作
- Go面向物件(OOP)很優雅,OOP本身就是語言型別系統(type system)的一部分,通過介面(interface)關聯,耦合性低,非常靈活,后面會充分體會這個特點,也就是說在Go中面向介面編程是非常重要的特性
結構體與結構體變數(實體/物件)的關系示意圖

*注意:從貓結構體到變數,就是創建一個Cat結構體變數,也可以說是定義Cat結構體變數*
對上圖的說明
? 將一類事物的特性提取出來(比如貓類),形成一個新的資料型別,就是一個結構體
? 通過這個結構體,我們可以創建多個變數(實體/物件)
? 事物可以貓類,也可以是Person,Fish 或是某個工具類
基本語法
type 結構體名稱 struct {
field1 type
field2 type
}
type Cat struct {
Name string
Age int
Color string
Hobby string
}
func main() {
//創建一個Cat的變數
var cat1 Cat
cat1.Name = "小白"
cat1.Age = 3
cat1.Color = "白色"
cat1.Hobby = "吃 <·)))><<"
fmt.Println("cat1 = ", cat1)
fmt.Println("貓貓的資訊如下:")
fmt.Println("name = ", cat1.Name)
fmt.Println("age = ", cat1.Age)
fmt.Println("color = ", cat1.Color)
fmt.Println("hobby = ", cat1.Hobby)
}
//cat1 = {小白 3 白色 吃 <·)))><<}
//貓貓的資訊如下:
//name = 小白
//age = 3
//color = 白色
//hobby = 吃 <·)))><<
結構體和結構體變數(實體)的區別和聯系
結構體是自定義的資料型別,代表一類事物
結構體變數(實體)是具體的,實際的,代表一個具體變數
結構體變數(實體)在記憶體的布局【重要】

欄位/屬性
基本介紹
? 1) 從概念或叫法上看:結構體欄位 = 屬性 = field
? 2) 欄位是結構體的一個組成部分,一般是基本資料型別、陣列,也可是參考型別,比如我們前面定義貓結構體的Name string 就是屬性
注意事項和細節說明
-
欄位宣告語法同變數,示例:欄位名 欄位型別
-
欄位的型別可以為:基本型別、陣列或參考型別
-
在創建一個結構體變數后,如果沒有給欄位賦值,都對應一個零值(默認值),規則如下:
布爾型別是false,數值是0,字串是””
陣列型別的默認值和它的元素型別相關,比如score[3]int 則為[0,0,0]
指標,slice和map的零值都是nil,即還沒有分配空間
//如果結構體的欄位型別是:指標,slice和map的零值都是nil,即還沒有分配空間
//如果需要使用這樣的欄位,需要先make,才能使用
type Person struct {
Name string
Age int
Scores [5]float64
ptr *int //指標
slice []int //切片
map1 map[string]string //map
}
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
//使用map,一定要先make
p1.map1 = make(map[string]string)
p1.map1["key1"] = "tom"
fmt.Println(p1)
}
//輸出:{ 0 [0 0 0 0 0] <nil> [] map[]}
//ok1
//ok2
//ok3
//{ 0 [0 0 0 0 0] <nil> [100 0 0 0 0 0 0 0 0 0] map[key1:tom]}
- 不同結構體變數的欄位是獨立的,互不影響,一個結構體變數欄位的更改,不影響另外一個,結構體是值型別
type Monster struct {
Name string
Age int
}
func main(){
var monster1 Monster
monster1.Name = "牛魔王"
monster1.Age = 500
monster2 := monster1 //結構體是值型別,默認為值拷貝
monster2.Name = "青牛精"
fmt.Println("monster1 = ",monster1)
fmt.Println("monster2 = ",monster2)
}
//輸出:monster1 = {牛魔王 500}
//monster2 = {青牛精 500}

創建結構體變數和訪問結構體欄位
方式1:直接宣告 var person Person
type Cat struct {
Name string
Age int
Color string
Hobby string
}
func main() {
//創建一個Cat的變數
var cat1 Cat
cat1.Name = "小白"
cat1.Age = 3
cat1.Color = "白色"
cat1.Hobby = "吃 <·)))><<"
方式2:{} var person Person = Person{}
type Monster struct {
Name string
Age int
}
func main(){
//{}
p := Monster{"zisefeizhu",21}
fmt.Println(p)
//輸出:{zisefeizhu 21}
}
方式3: & var person *Person = new (Person)
type Monster struct {
Name string
Age int
}
func main(){
//方式3
//案例:var person *Person = new (Person)
var p3 *Monster = new(Monster)
//因為p3是一個指標,因此標準的給欄位賦值方式
//(*p3).Name = "smith" 也可以這樣寫 p3.Name = "smith"
//原因:Go的設計者為了程式員使用方便,底層會對p3.Name = "smith"進行處理
//會給p3加上取值運算(*p3).Name = "smith"
(*p3).Name = "smith"
p3.Name = "john"
(*p3).Age = 30
p3.Age = 100
fmt.Println(*p3) //{john 100}
}
方式4: {} var person *Person = &Person{}
type Monster struct {
Name string
Age int
}
func main(){
//方式4 - {}
//var person *Person = &Person{}
//下面的陳述句,也可以直接給字符賦值
//var person *Person = &Person{"mary",60}
var person *Monster = &Monster{}
//因為person是一個指標,因此標準的訪問欄位的方法
//(*person).Name = "scott"
//go的設計者為了程式使用方便,也可以person.Name = "scott"
//原因和上面一樣,底層會對person.Name = "scott" 進行處理,會加上(*person)
(*person).Name = "scott"
person.Name = "scott ~"
(*person).Age = 88
person.Age = 10
fmt.Println(*person) //{scott ~ 10}
}
說明:
1)第3種和第4種方式回傳的是結構體指標
2)結構體指標訪問欄位的標準方式應該是:(*結構體指標)欄位名,比如(*person).Name = “tom”
3)但Go做了一個簡化,也支持 結構體指標.欄位名,比如person.Name = “tom”,更加符合程式員使用的習慣,Go編譯器底層對person.Name做了轉化(*person).Name
struct型別的記憶體分配機制
定義一個Person結構體(包括 名字,年齡)
type Person struct {
Name string
Age int
}
func main() {
var p1 Person
p1.Age = 10
p1.Name = "小明"
var p2 Person = p1
fmt.Println(p2.Age)
p2.Name = "tom"
fmt.Printf("p2.Name = %v p1.Name = %v",p2.Name,p1.Name)
}
//輸出:10
//p2.Name = tom p1.Name = 小明
變數總是存在記憶體中的,那么結構體變數在記憶體中究竟是怎樣存在的?
畫一個圖說明:結構體變數在記憶體中如何存在

看下面代碼,分析原因
type Person struct {
Name string
Age int
}
func main() {
var p1 Person
p1.Age = 10
p1.Name = "小明"
var p2 *Person = &p1 //這里是關鍵 --> 畫圖示意圖
fmt.Println((*p2).Age) //10
fmt.Println(p2.Age) //10
p2.Name = "tom ~"
fmt.Printf("p2.Name = %v p1.Name = %v \n",p2.Name,p1.Name) //p2.Name = tom ~ p1.Name = tom ~
fmt.Printf("p2.Name = %v p1.Name = %v \n",(*p2).Name,p1.Name) //p2.Name = tom ~ p1.Name = tom ~
fmt.Printf("p1的地址%p\n",&p1) //p1的地址0xc00004a420
fmt.Printf("p2的地址%p p2的值%p\n",&p2, p2) //p2的地址0xc000080018 p2的值0xc00004a420
fmt.Println(p2.Age) //10
p2.Name = "tom"
fmt.Printf("p2.Name = %v p1.Name = %v",p2.Name,p1.Name) //p2.Name = tom p1.Name = tom
}


結構體使用注意事項和細節
1)結構體的所有欄位在記憶體中連續的
//結構體
type Point struct {
x int
y int
}
//結構體
type Rect struct {
leftUp, rightDown Point
}
//結構體
type Rect2 struct {
leftUp, rightDown *Point
}
func main() {
r1 := Rect{Point{1,2},Point{3,4}}
//r1有四個int,在記憶體中是連續分布
//列印地址
fmt.Printf("r1.leftUp.x 地址=%p r1.leftUp.y 地址=%p r1.rightDown.x 地址=%p r1.rightDown.y 地址=%p\n" ,
&r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y)
//r.leftUp.x 地址=0xc000052140 r1.leftUp.y 地址=0xc000052148 r1.rightDown.x 地址=0xc000052150 r1.rightDown.y 地址=0xc000052158
//r2有兩個*Point型別,這兩個*Point型別的本身地址也是連續的
//但是它們指向的地址不一定是連續
r2 := Rect2{&Point{10,20},&Point{30,40}}
//列印地址
//列印地址
fmt.Printf("r2.leftUp 本身地址 = %p r2.rightDown 本身地址 = %p \n",
&r2.leftUp, &r2.rightDown)
//r2.leftUp 本身地址 = 0xc0000401c0 r2.rightDown 本身地址 = 0xc0000401c8
//它們指向的地址不一定是連續... 這個要看系統在運行時是如何分配
fmt.Printf("r2.leftUp 指向地址 = %p r2.rightDown 指向地址 = %p \n",
r2.leftUp, r2.rightDown)
//r2.leftUp 指向地址 = 0xc000054090 r2.rightDown 指向地址 = 0xc0000540a0
}
對應的分析圖

2)結構體是用戶單獨定義的型別,和其它型別進行轉換時需要有完全相同的欄位(名字、個數和型別)
type A struct {
Num int
}
type B struct {
Num int
}
func main() {
var a A
var b B
a = A(b) //? 可以轉換,但是有要求,就是結構體的欄位要完全一樣(包括:名字、個數和型別)
fmt.Println(a, b) //{0} {0}
}
- 結構體進行type重新定義(相當于取別名),Go認為是新的資料型別,但是相互間可以強轉

- struct的每個欄位上,可以寫上一個tag,該tag可以通過反射機制獲取,常見的使用就是序列化和反序列化
序列化的常見使用

import (
"encoding/json"
"fmt"
)
type Monster struct {
Name string `json:"name"` // `json:"name"` 就是struct tag
Age int `json:"age"`
Skill string `json:"skill"`
}
func main() {
//1.創建一個Monster變數
Monster := Monster{"牛魔王",500,"芭蕉扇"}
//2.將monster變數序列化為json格式字串
// json.Marshal 函式中使用反射,這里只是用一下反射,在后面會詳細介紹
jsonStr, err := json.Marshal(Monster)
if err != nil {
fmt.Println("json 處理錯誤",err)
}
fmt.Println("jsonStr",string(jsonStr)) //jsonStr {"name":"牛魔王","age":500,"skill":"芭蕉扇"}
}
方法
在某些情況下,需要宣告(定義)方法,比如Person結構體:除了有一些欄位外(年齡,姓名...),Person結構體還有一些行為比如:可以說話、跑步...,通過學習,還可以做算術題,這時就要用方法才能完成
Go中的方法是作用在指定的資料型別上的(即:和指定的資料型別系結),因此自定義型別,都可以有方法,而不僅僅是struct
方法的宣告和呼叫
type A struct {
Num int
}
func (a A)test(){
fmt.Println(a.Num)
}
對上面的語法的說明
func (a A)test() {} 表示A結構體有 - 方法,方法名為test
(a A)體現test方法是和A型別系結的
type Person struct {
Name string
}
//給Person型別系結 -- 方法
func (p Person) test() {
fmt.Println("test() name = ", p.Name) //test() name = tom
}
func main() {
var p Person
p.Name = "tom"
p.test() // 呼叫方法
}
對上面的總結
- test方法和Person型別系結
2)test方法只能通過Person型別的變數來呼叫,而不能直接呼叫,也不能使用其它型別變數來呼叫
-
func (p Person)test(){}... p表示哪個Person變數呼叫,這個p就是它的副本,這點和函式傳參非常相似
-
p這個名字,由程式員指定,不是固定,比如修改成person也是可以
type Person struct {
Name string
}
//給Person型別系結 -- 方法
func (person Person) test() {
fmt.Println("test() name = ", person.Name) //test() name = tom
}
func main() {
var p Person
p.Name = "tom"
p.test() // 呼叫方法
}
快速入門
- 給Person結構體添加speak方法,輸出xxx是一個好人
type Person struct {
Name string
}
//給Person結構體添加speak方法,輸出xxx是一個好人
func (p Person) speak() {
fmt.Println(p.Name,"是一個goodman~") //tom 是一個goodman~
}
func main() {
var p Person
p.Name = "tom"
p.speak()
}
- 給Person結構體添加jisuan方法,可以計算從1+..+1000的1結果,說明:方法體內可以像函式一樣進行各種運算
type Person struct {
Name string
}
//給Person結構題添加jisuan方法,可以計算從1+..+1000的1結果
func (p Person) jisuan() {
res := 0
for i:=1; i <= 1000; i++ {
res += i
}
fmt.Println(p.Name,"計算的結構是 = ", res) //tom 計算的結構是 = 500500
}
func main() {
var p Person
p.Name = "tom"
p.jisuan()
}
- 給Person結構體jisuan2方法,該方法可以接收一個數n,計算從1+..+n的結果
type Person struct {
Name string
}
func (p Person) jisuan2(n int) {
res := 0
for i:=1; i <= n; i++ {
res += i
}
fmt.Println(p.Name,"計算的結構是 = ", res) //tom 計算的結構是 = 210
}
func main() {
var p Person
p.Name = "tom"
p.jisuan2(20)
}
- 給Person結構體添加getSum方法,可以計算兩個數的和,并回傳結果
type Person struct {
Name string
}
func (p Person) getSum(n1 int, n2 int) int {
return n1 + n2
}
func main() {
var p Person
res := p.getSum(10, 20)
fmt.Println("res = ", res) //res = 30
}
方法的呼叫和傳參機制原理[重要]
方法的呼叫和傳參機制和函式基本一樣,不一樣的地方是方法呼叫時,會將呼叫方法的變數,當作實參也傳遞給方法,
案例1
畫出前面getSum方法的執行程序+說明

-
在通過一個變數去呼叫方法時,其呼叫機制和函式一樣
-
不一樣的地方時,變數呼叫方法時,該變數本身也會作為一個引數傳遞到方法(如果變數是值型別,則進行值拷貝,如果變數是參考型別,則進行地址拷貝)
案例2
請撰寫一個程式,要求如下:
-
宣告一個結構體Circle,欄位為radius
-
宣告一個方法area和Circle系結,可以回傳面積
-
提示:畫出area執行程序+說明

方法的宣告(定義)
func (recevier type) methodName (引數串列) (回傳值串列) {
方法體
return 回傳值
}
1)引數串列:表示方法輸入
2)recevier type:表示這個方法和type這個型別進行系結,或者說該方法作用于type型別
3)recevier type:type可以是結構體,也可以其它的自定義型別
4)recevier:就是type型別的一個變數(實體),比如:Person結構體的一個變數(實體)
5)回傳值串列:表示回傳的值,可以多個
6)方法主體:表示為了實作某一功能代碼塊
7)return陳述句不是必須的
方法的注意事項和細節
-
結構體型別是值型別,在方法呼叫中,遵守值型別的傳遞機制,是值拷貝傳遞方式
-
如果程式員希望在方法中,修改結構體變數的值,可以通過結構體指標的方式來處理

-
Go中的方法作用在指定的資料型別上的(即:和指定的資料型別系結),因此自定義型別,都可以有方法,而不僅僅是struct,比如int,float32等都可以有方法
type integer int
func (i integer) print() {
fmt.Println("i = ",i)
}
//撰寫一個方法,可以改變i的值
func (i *integer) change() {
*i = *i + 1
}
func main() {
var i integer = 10
i.print()
i.change()
fmt.Println("i = ", i)
}
//輸出:i = 10
//i = 11
-
方法的訪問范圍控制的規則,和函式一樣,方法名首字母小寫,只能在本包訪問,方法首字母大寫,可以在本包和其它包訪問
-
如果一個型別實作了String()這個方法,那么fmt.Println默認會呼叫這個變數的String()進行輸出
type Student struct {
Name string
Age int
}
//給*Student實作方法String()
func (stu *Student) String() string {
str := fmt.Sprintf("Name = [%v] Age = [%v]",stu.Name, stu.Age)
return str
}
func main() {
//定義一個Student變數
stu := Student{
Name: "tom",
Age: 20,
}
//如果實作了*Student 型別的string方法,就會自動呼叫
fmt.Println(&stu)
}
//輸出:Name = [tom] Age = [20]
方法和函式的區別
- 呼叫方式不一樣
? 函式的呼叫方式:函式名(實參串列)
? 方法的呼叫方式:變數.方法名(實參串列)
- 對于普通函式,接收者為值型別時,不能將指標型別的資料直接傳遞,反之亦然
type Person struct {
Name string
}
//函式
//對于普通函式,接收者為值型別時,不能將指標型別的資料直接傳遞,反之亦然
func test01(p Person) {
fmt.Println(p.Name) //tom
}
func test02(p *Person) {
fmt.Println(p.Name) //tom
}
func main() {
p := Person{"tom"}
test01(p)
test02(&p)
}
- 對于方法(如struct的方法),接收者為值型別時,可以直接用指標型別的變數呼叫方法,反過來同樣也可以
type Person struct {
Name string
}
//3)對于方法(如struct的方法),
// 接收者為值型別時,可以直接用指標型別的變數呼叫方法,反過來同樣也可以
func (p Person) test03() {
p.Name = "jack"
fmt.Println("test03() = ",p.Name)
}
func (p *Person) test04() {
p.Name = "mary"
- fmt.Println("test04() = ",p.Name)
}
func main() {
p := Person{"tom"}
p.test03()
fmt.Println("main() p.name = ",p.Name)
(&p).test03() //從形式上傳入地址,但是本質仍然是值拷貝
fmt.Println("main() p.name = ",p.Name)
(&p).test04()
fmt.Println("main() p.name = ",p.Name)
p.test04()
}
//test03() = jack
//main() p.name = tom
//test03() = jack
//main() p.name = tom
//test04() = mary
//main() p.name = mary
//test04() = mary
總結
-
不管呼叫形式如何,真正決定是值拷貝還是地址拷貝,看這個方法是和哪個型別系結
-
如果是和值型別,比如(p Person),則是值拷貝,如果和指標型別,比如是(p Person)則是地址拷貝
方法練習題
撰寫結構體(MethodUtils),編程一個方法,方法不需要引數,在方法中列印一個 10*8的矩形,在main方法中呼叫該方法
type MethodUtils struct {
//欄位...
}
//給MethodUtils撰寫方法
func (mu MethodUtils) print() {
for i := 1; i <= 10; i++ {
for j := 1; j <= 8; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
func main() {
var mu MethodUtils
mu.print()
}
//輸出:
//********
//********
//********
//********
//********
//********
//********
//********
//********
//********
撰寫一個方法,提供m 和 n 兩個引數,方法中列印一個 m * n 的矩形
type MethodUtils struct {
//欄位...
}
func (mu MethodUtils) print2(m int, n int) {
for i := 1; i <= 10; i++ {
for j := 1; j <= 8; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
func main() {
var mu MethodUtils
mu.print2(10,8)
}
//輸出:
//********
//********
//********
//********
//********
//********
//********
//********
//********
//********
撰寫一個方法算該矩形的面積(可以接收長len,和寬width),將其作為方法回傳值,在main方法中呼叫該方法,接識訓傳的面積值并列印
type MethodUtils struct {
//欄位...
}
func (mu MethodUtils) area(len float64, width float64) (float64) {
return len * width
}
func main() {
var mu MethodUtils
fmt.Println("面積 =",mu.area(10,20)) //面積 = 200
}
撰寫方法:判斷一個數是奇數還是偶數
type MethodUtils struct {
//欄位...
}
func (mu *MethodUtils) JudgeNum(num int) {
if num % 2 == 0 {
fmt.Println(num,"是偶數..") // 10 是偶數..
} else {
fmt.Println(num,"是奇數..")
}
}
func main() {
var mu MethodUtils
mu.JudgeNum(10)
}
根據行、列、字符列印對應行數和列數的字符,比如:行:3,列:2,字符*,則列印相應的效果
type MethodUtils struct {
//欄位...
}
func (mu *MethodUtils) print(n int, m int, key string) {
for i := 1; i <= n; i++ {
for j := 1; j <= m; j++ {
fmt.Print(key)
}
fmt.Println()
}
}
func main() {
var mu MethodUtils
mu.print(5,5,"+")
}
定義小小計算器結構體(Calcuator),實作加減乘除四個功能
? 實作形式1:分四個方法完成
? 實作形式2:用一個方法搞成
//實作形式1
type Calcuator struct {
Num1 float64
Num2 float64
}
func (calcuator *Calcuator) getSum() float64 {
return calcuator.Num1 + calcuator.Num2
}
func (calcuator *Calcuator) getSub() float64 {
return calcuator.Num1 - calcuator.Num2
}
package main
import "fmt"
//實作形式2
type Calcuator struct {
Num1 float64
Num2 float64
}
func (calcuator *Calcuator) getRes(operator byte) float64 {
res := 0.0
switch operator {
case '+':
res = calcuator.Num1 + calcuator.Num2
case '-':
res = calcuator.Num1 - calcuator.Num2
case '*':
res = calcuator.Num1 * calcuator.Num2
case '/':
res = calcuator.Num1 / calcuator.Num2
default:
fmt.Println("運算子輸入有誤...")
}
return res
}
在MerhodUtils結構體編個方法,從鍵盤接收整數(1-9),列印對應乘法表

type MerhodUtils struct {
//欄位
}
func (m MerhodUtils) jiu(n int) {
for i := 1; i <= n; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%v * %v = %v\t",i,j,i*j)
}
fmt.Println()
}
}
func main() {
var mu MerhodUtils
var num int
fmt.Println("請鍵入要輸入的大于等于1小于等于9的自然數: ")
fmt.Scanln(&num)
mu.jiu(num)
}
//請鍵入要輸入的大于等于1小于等于9的自然數:
//2
//1 * 1 = 1
//2 * 1 = 2 2 * 2 = 4
撰寫方法,使給定的一個二維陣列(3 × 3)轉置

面向物件編程應用實體
步驟
-
宣告(定義)結構體,確定結構體名
-
撰寫結構體的欄位
-
撰寫結構體的方法
學生案例
-
撰寫一個Student結構體,包含name、gender、age、id、score欄位,分別為string、string、int、int、float64型別
-
結構體中宣告一個say方法,回傳string型別,方法回傳資訊中包含所有欄位值
-
在main方法中,創建Student結構體實體(變數),并訪問say方法,并將呼叫結果列印輸出
type Student struct {
name string
gender string
age int
id int
score float64
}
func (student *Student) say() string {
infoStr := fmt.Sprintf("student 的資訊 name = [%v] gender = [%v] age = [%v] id = [%v] score = [%v]",
student.name, student.gender, student.age, student.id, student.score)
return infoStr
}
func main() {
//創建一個Student實體變數
var stu = Student{
name: "zisefeizhu",
gender:"male",
age: 18,
id: 1000,
score: 99.98,
}
fmt.Println(stu.say())
}
//輸出:student 的資訊 name = [zisefeizhu] gender = [male] age = [18] id = [1000] score = [99.98]
小狗案例
-
撰寫一個Dog結構體,包含name、age、weight欄位
-
結構體中宣告一個say方法,回傳string型別,方法回傳資訊中包含所有欄位值
-
在main方法中,創建Dog結構體實體(變數),并回傳say方法,將呼叫結果列印輸出
type Dog struct {
name string
age int
wgight float64
}
func (dog *Dog) say() string {
infoStr := fmt.Sprintf("dog 的資訊 name = [%v] age = [%v] weight = [%v]",
dog.name, dog.age, dog.wgight)
return infoStr
}
func main() {
//創建一個Student實體變數
var stu = Dog{
name: "xiaohua",
age: 18,
wgight: 23,
}
fmt.Println(stu.say())
}
//輸出:dog 的資訊 name = [xiaohua] age = [18] weight = [23]
盒子案例
-
編程創建一個Box結構體,在其中宣告三個欄位表示一個立方體的長、寬和高,長寬高要從終端獲取
-
宣告一個方法獲取立方體的體積
-
創建一個Box結構體變數,列印給定尺寸的立方體的體積
type Box struct {
len float64
width float64
height float64
}
//宣告一個方法獲取立方體的體積
func (box *Box) getVolum() float64 {
return box.len * box.width * box.height
}
func main() {
var box Box
box.len = 1.1
box.width = 2.0
box.height = 3.0
volumn := box.getVolum()
fmt.Printf("體積為=%.2f",volumn)
}
//輸出:體積為=6.60
景區門票案例
-
一個景區根據游人的年齡收取不同價格的門票,比如年齡大于18,收費20元,其它情況門票免費
-
請撰寫Visitor結構體,根據年齡段決定能夠購買的門票價格并輸出
type Visitor struct {
Name string
Age int
}
//宣告一個方法獲取立方體的體積
func (visitor *Visitor) showPrice() {
if visitor.Age >= 90 || visitor.Age <= 8 {
fmt.Println("考慮到安全,就不要玩了")
return
}
if visitor.Age > 18 {
fmt.Printf("游客的名字為 %v 年齡為 %v 收費20元\n", visitor.Name, visitor.Age)
} else {
fmt.Printf("游客的名字為 %v 年齡為%v 免費 \n", visitor.Name, visitor.Age)
}
}
func main() {
var v Visitor
for {
fmt.Println("請輸入你的名字")
fmt.Scanln(&v.Name)
if v.Name == "n" {
fmt.Println("退出程式...")
break
}
fmt.Println("請輸入你的年齡")
fmt.Scanln(&v.Age)
v.showPrice()
}
}
//輸出:請輸入你的名字
//zisefeizhu
//請輸入你的年齡
//20
//游客的名字為 zisefeizhu 年齡為 20 收費20元
//請輸入你的名字
//n
//退出程式...
創建結構體變數時指定欄位值
Go在創建結構體實體(變數)時,可以直接指定欄位的值
方式1:
type Stu struct {
Name string
Age int
}
func main() {
//方式1
//在創建結構體變數時,就直接指定欄位的值
var stu1 = Stu{"zisefeizhu",20} // stu1 --> 結構體資料空間
stu2 := Stu{"jingxing",20}
//在創建結構體變數時,把欄位名和欄位值寫在一起,這種寫法,就不依賴欄位的定義順序
var stu3 = Stu{
Name: "yike",
Age: 20,
}
var stu4 = Stu{
Name: "gengpan",
Age: 20,
}
fmt.Println(stu1, stu2, stu3, stu4)
}
//輸出:{zisefeizhu 20} {jingxing 20} {yike 20} {gengpan 20}
方式2:
type Stu struct {
Name string
Age int
}
func main() {
//方式2
var stu5 *Stu = &Stu{"小王", 20} //stu5 --> 地址 --> 結構體資料[xxxx,xxxx]
stu6 := &Stu{"小紫", 20}
//在創建結構體指標變數時,把欄位名和欄位值寫在一起,這種寫法,就不依賴欄位的定義順序
var stu7 = &Stu{
Name: "小林",
Age: 20,
}
var stu8 = &Stu{
Age: 20,
Name: "小耿",
}
fmt.Println(*stu5, *stu6, *stu7, *stu8)
}
//輸出:{小王 20} {小紫 20} {小林 20} {小耿 20}
工廠模式
Go的結構體沒有建構式,通常可以使用工廠模式來解決這個問題
看一個需求
一個結構體的宣告是這樣的:
pachage model
type Student struct {
Name string ...
}
因為這里的Student的首字母S是大寫的,如果我們想在其它包創建Student的實體(比如main包),引入model包后,就可以直接創建Student結構體的變數(實體),但是問題來了,如果首字母是小寫的,比如是type student struct {...}就不行了,怎么辦-->工廠模式來解決>
工廠模式來解決問題
使用工廠模式實作跨包創建結構體實體(變數)的案例:
如果model包的*結構體變**量**首字母大寫,引入后,直接使用*,沒有問題

如果model包的結構體變數首字母小寫,引入后,不能直接使用,可以工廠模式解決
student.go
package model
//定義一個結構體
type student struct {
Name string
Score float64
}
//因為student結構體首字母是小寫,因此只能在model使用
//通過工廠模式來解決
func NewStudent(n string, s float64) *student {
return &student{
Name: n,
Score: s,
}
}
main.go
package main
import (
"2020-04-04/model"
"fmt"
)
func main() {
var stu = model.NewStudent("tom", 21)
fmt.Println(*stu) //&{...}
fmt.Println("name = ", stu.Name, "score = ", stu.Score)
}
//name = tom score = 21
思考題
如果model包的student 的結構體的欄位Score 改成score, 我們還能正常訪問嗎?又應該如何解決這個問題呢?
解決方法如下:
? student.go
//定義一個結構體
type student struct {
Name string
score float64
}
//因為student結構體首字母是小寫,因此只能在model使用
//通過工廠模式來解決
func NewStudent(n string, s float64) *student {
return &student{
Name: n,
score: s,
}
}
//如果score欄位首字母小寫,則,在其它包不可以直接訪問,可以提供一個方法
func (s *student) GetScore() float64 {
return s.score
}
main.go
import (
"2020-04-04/model"
"fmt"
)
func main() {
var stu = model.NewStudent("tom", 22)
fmt.Println(*stu) //&{...}
fmt.Println("name = ", stu.Name, "score = ", stu.GetScore())
}
//{tom 22}
//name = tom score = 22
面向物件編程思想-抽象
定義一個結構體的時候,實際上就是把一類事物的共有的屬性(欄位)和行為(方法)提取出來,形成一個物理模型(結構) ,這種研究問題的方法稱為抽象,
快速入門案例

//定義一個結構體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() {
var pwd string
var balance float64
account := Account{
AccountNo: "1111111",
Pwd: "666666",
Balance: 0.0,
}
fmt.Println("請輸入密碼")
fmt.Scanln(&pwd)
if pwd == account.Pwd {
fmt.Println("請輸入金額")
fmt.Scanln(&balance)
account.Query(pwd)
account.Deposite(balance,pwd)
account.Query(pwd)
}
}
//請輸入密碼
//666666
//請輸入金額
//2000
//你的賬號為=1111111 余額=0
//存款成功!
//你的賬號為=1111111 余額=2000
對上面代碼進行修飾:增加一個控制臺的選單,可以讓用戶動態的輸入選項
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() {
var xuanxiang byte
var pwd string
var balance float64
account := Account{
AccountNo: "1111111",
Pwd: "666666",
Balance: 0.0,
}
fmt.Println("請輸入密碼")
fmt.Scanln(&pwd)
if pwd == account.Pwd {
for {
fmt.Println("請輸入選單選項:")
fmt.Println("1. 存款")
fmt.Println("2. 取款")
fmt.Println("3. 余額")
fmt.Println("4. 退出")
fmt.Println("請輸入選單選項:")
fmt.Scanln(&xuanxiang)
switch xuanxiang {
case 1:
fmt.Println("請輸入金額")
fmt.Scanln(&balance)
account.Query(pwd)
account.Deposite(balance,pwd)
account.Query(pwd)
//fmt.Println("請輸入金額")
//fmt.Scanln(&balance)
//account.Deposite(balance,pwd)
case 2:
fmt.Println("請輸入金額")
fmt.Scanln(&balance)
account.Query(pwd)
account.WithDraw(balance,pwd)
account.Query(pwd)
case 3:
//
account.Query(pwd)
default:
return
}
}
//fmt.Println("請輸入金額")
//fmt.Scanln(&balance)
//account.Query(pwd)
//account.Deposite(balance,pwd)
//account.Query(pwd)
}
}
//請輸入密碼
//666666
//請輸入金額
//2000
//你的賬號為=1111111 余額=0
//存款成功!
//你的賬號為=1111111 余額=2000
面向物件編程三大特性-封裝
Go仍然有面向物件編程的繼承,封裝和多型的特性,只是實作的方式和其它OOP語言不一樣
封裝(encapsulation)就是把抽象出的欄位和對欄位的操作封裝在一起,資料被保護在內部,程式的其它包只有通過被授權的操作(方法),才能對欄位進行操作
封裝的好處
-
隱藏實作細節
-
可以對資料進行驗證,保證安全合理(Age)
如何體現封裝
-
對結構體中的屬性進行封裝
-
通過方法,包實作封裝
封裝的實作步驟
-
將結構體、欄位(屬性)的首字母小寫(不能匯出了,其它包不能使用,類似private)
-
給結構體所在包提供一個工廠模式的函式,首字母大寫,類似一個建構式
-
提供一個首字母大寫的Set方法(類似其它語言的public),用于對屬性判斷并賦值
func(var 結構體型別名) SetXxx(引數串列) (回傳值串列) {
//加入資料驗證的業務邏輯
var.欄位 = 引數
}
- 提供一個首字母大寫的Get方法(類似其它語言的public),用于獲取屬性的值
func (var 結構體型別名) GetXxx() {
return var.age
}
*特別說明*:在Go開發中并沒有特別強調封裝,這點并不像Java,所以不用總是用Java的語法特性來看待Go,Go本身對面向物件的特性做了簡化的
快速入門案例
撰寫一個程式(person.go),不能隨便查看人的年齡、工資等隱私,并對輸入的年齡進行合理的驗證,
設計:model包(person.go),main包(main.go呼叫Person結構體)
person.go
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.go
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())
}
//輸出:&{smith 18 5000}
//smith age = 18 sal = 5000
改
type person struct {
name string
age int //其它包不能直接訪問
sal float64
}
//寫一個工廠模式的函式,相當于建構式
func NewPerson() *person {
return &person{
name: "",
age: 0,
sal: 0,
}
}
//為了訪問age和sal 撰寫一對SetXxx的方法和GetXxx的方法
func (p *person) SetAge(name string ,age int, sal float64) {
if name != "" {
p.name = name
}
if age > 0 && age < 150 {
p.age = age
} else {
fmt.Println("年齡范圍不正確..")
//給程式員一個默認值
}
if sal >= 3000 && sal <= 30000 {
p.sal = sal
} else {
fmt.Println("薪水范圍不正確...")
}
}
func (p *person) GetAge() (string,int, float64) {
return p.name ,p.age, p.sal
}
func main() {
//p := model.NewPerson("smith")
p := model.NewPerson()
p.SetAge("smith",18, 5000)
fmt.Println(p)
fmt.Println(p.GetAge())
}
再改
//寫一個工廠模式的函式,相當于建構式
func NewPerson(name string, age int, sal float64) *person {
if name == "" {
fmt.Println("輸入名字錯誤")
return nil
}
if age < 0 && age > 150 {
fmt.Println("年齡范圍不對...")
return nil
}
if sal < 3000 && sal > 30000 {
fmt.Println("薪水范圍不正確...")
return nil
}
return &person{
name: name,
age: age,
sal: sal,
}
}
func (p *person) GetAge() (string,int, float64) {
return p.name ,p.age, p.sal
}
func main() {
p := model.NewPerson("smith",18,50000)
fmt.Println(p)
fmt.Println(p.GetAge())
}
要求
-
創建程式,在model包中定義Account結構體:在main函式中體現Go的封裝性
-
Account結構體要求具有欄位:賬號(長度在6-10之間)、余額(必須>20)、密碼(必須是6位數)
-
通過SetXxx的方法給Account的欄位賦值
-
在main函式中測驗
account.go
//定義一個結構體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,
}
}
//方法
//存款
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.go
func main() {
//創建一個account變數
account := model.NewAccount("zisefeizhu","000",40)
if account != nil {
fmt.Println("創建成功 = ", account)
} else {
fmt.Println("創建失敗")
}
}
//輸出:密碼的長度不對...
//創建失敗
增加如下功能:通過SetXxx的方法給Account的欄位賦值通過GetXxx方法獲取欄位的值
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 {
return &account{
accountNo: accountNo,
pwd: pwd,
balance: balance,
}
}
func (accounter *account) SetAccountNo(accountNo string) {
if len(accountNo) < 6 || len(accountNo) > 10 {
fmt.Println("賬號的長度不對")
}
}
func (accounter *account) GetAccountNo() string {
return accounter.accountNo
}
//方法
//存款
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.go
package main
import (
"2020-04-04/model"
"fmt"
)
func main() {
//創建一個account變數
account := model.NewAccount("zisefeizhu","000",40)
fmt.Println(account)
fmt.Println(account.GetAccountNo())
}
//&{zisefeizhu 000 40}
//zisefeizhu
類似改法
面向物件編程三大特性-繼承
看一個問題,引出繼承的必要性
看一個學生考試系統的程式extend01.go,提出代碼復用的問題

//撰寫一個學生考試系統
//小學生
type Pupil struct {
Name string
Age int
Score int
}
//顯示他的成績
func (p *Pupil) ShowInfo() {
fmt.Printf("學生名 = %v 年齡 = %v 成績 = %v\n", p.Name, p.Age, p.Score)
}
func (p *Pupil) SetScore(score int) {
//業務判斷
p.Score = score
}
func (p *Pupil) testing() {
fmt.Println("小學生正在考試中...")
}
//大學生,研究生...
//大學生
type Graduate struct {
Name string
Age int
Score int
}
//顯示他的成績
func (p *Graduate) ShowInfo() {
fmt.Printf("學生名 = %v 年齡 = %v 成績 = %v\n", p.Name, p.Age, p.Score)
}
func (p *Graduate) SetScore(score int) {
//業務判斷
p.Score = score
}
func (p *Graduate) testing() {
fmt.Println("大學生正在考試中...")
}
//代碼冗余... 研究生
//代碼冗余... 高中生
func main() {
//測驗
var pupil = &Pupil{
Name: "tom",
Age: 10,
}
pupil.testing()
pupil.SetScore(90)
pupil.ShowInfo()
//測驗
var graduate = &Graduate{
Name: "tom",
Age: 20,
}
graduate.testing()
graduate.SetScore(90)
graduate.ShowInfo()
}
//輸出:小學生正在考試中...
//學生名 = tom 年齡 = 10 成績 = 90
//大學生正在考試中...
//學生名 = tom 年齡 = 20 成績 = 90
對上面代碼的小結
1)Pupil和Graduate兩個結構體的欄位和方法幾乎一樣,但是我們卻寫了相同的代碼,代碼復用性不強
2)出現代碼冗余,而且代碼不利于維護,同時也不利于功能的擴展
3)解決方法 - 通過繼承方式來解決
繼承基本介紹和示意圖
繼承可以解決代碼復用,讓編程更加靠近人類思維
當多個結構體存在相同的屬性(欄位)和方法時,可以從這些結構體中抽象出結構體(比如剛才的Student),在該結構體中定義這些相同的屬性和方法,
其它的結構體不需要重新定義這些屬性(欄位)和方法,只需嵌套一個Student匿名結構體即可

在Go中,如果一個struct嵌套了另一個匿名結構體,那么這個結構體可以直接訪問匿名結構體的欄位和方法,從而實作了繼承特性
嵌套匿名結構體的基本語法
type Goods struct {
Name string
Price int
}
type Book struct {
Goods //這里就是嵌套匿名結構體Goods
Writer string
}
快速入門案例
對extends01.go改進,使用嵌套匿名結構體的方式來實作繼承特性,體會繼承的好處
//撰寫一個學生考試系統
//小學生
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
}
//小學生
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.SetScore(70)
pupil.ShowInfo()
//測驗
graduate := &Graduate{}
graduate.Student.Name = "marry"
graduate.Student.Age = 28
graduate.testing()
graduate.SetScore(90)
graduate.ShowInfo()
}
//輸出:小學生正在考試中...
//學生名 = tom 年齡 = 8 成績 = 70
//大學生正在考試中...
//學生名 = marry 年齡 = 28 成績 = 90
繼承給編程帶來的便利
-
代碼的復用性提高了
-
代碼的擴展性和維護性提高了
繼承的深入討論
1)結構體可以使用嵌套匿名結構體所有的欄位和方法,即:首字母大寫或者小寫的欄位、方法,都可以使用
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
}
func main() {
var b B
b.A.Name = "zisefeizhu"
b.A.age = 19
b.A.SayOk()
b.A.Hello()
}
//輸出:A SayOk zisefeizhu
//A Hello zisefeizhu
-
匿名結構體欄位訪問可以簡化

對上面的代碼小結
(1)當我們直接通過b訪問欄位或方法時,其執行流程如下比如b.Name
(2)編譯器會先看b對應的型別有沒有Name,如果有,則直接呼叫B型別的Name欄位
(3)如果沒有就去看B中嵌入的匿名結構體A有沒有宣告Name欄位,如果有就呼叫,如果沒有繼續查找...如果都找不到就報錯
3) 當結構體和匿名結構體有相同的欄位或者方法時,編譯器采用就近訪問原則訪問,如希望訪問匿名結構體的欄位和方法,可以通過匿名結構體名來區分

-
結構體嵌入兩個(或多個)匿名結構體,如果兩個匿名結構體有相同的欄位和方法(同時結構體本身沒有同名的欄位和方法),在訪問時,就必須明指定匿名結構體名字,否則編譯報錯

-
如果一個struct嵌套了一個有名結構體,這種模式就是組合,如果是組合關系,那么在訪問組合的結構體的欄位或方法時,必須帶上結構體的名字

6 ) 嵌套匿名結構體后,也可以在創建結構體變數(實體)時,直接指定各個匿名結構體欄位的值
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Address string
}
type TV struct {
Goods
Brand
}
type TV2 struct {
*Goods
*Brand
}
func main() {
tv := TV{ Goods{"電視機001", 5000.99},Brand{"海爾","山東"},}
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)
}
//tv {{電視機001 5000.99} {海爾 山東}}
//tv2 {{電視機002 5000.99} {夏普 北京}}
//tv3 {電視機003 7000.99} {創維 河南}
//tv4 {電視機004 9000.99} {長虹 四川}
課堂練習
結構體的匿名欄位是基本資料型別,如何訪問?
type Monster struct {
Name string
Age int
}
type E struct {
Monster
int
n int
}
func main() {
//演示一下匿名欄位時基本資料型別的使用
var e E
e.Name = "狐貍精"
e.Age = 300
e.int = 20
e.n = 40
fmt.Println("e = ", e)
}
//輸出:e = {{狐貍精 300} 20 40}
說明
1)如果一個結構體有int型別的匿名欄位,就不能有第二個
2)如果需要有多個int的欄位,則必須給int欄位指定名字
面向物件編程-多重繼承
多重繼承說明
如一個struct嵌套了多個匿名結構體,那么該結構體可以直接訪問嵌套的匿名結構體的欄位和方法,從而實作了多重繼承,
案例演示
通過一個案例來說明多重繼承使用

多重繼承細節
- 若嵌入的匿名結構體有相同的欄位名或者方法名,則在訪問時,需要通過匿名結構體型別名來區分

2)為了保證代碼的簡潔性,建議盡量不使用多重繼承
介面
Go中多型特性主要是通過介面來體現的
usb插槽就是現實中的介面你可以把手機,相機,u盤都插在usb插槽上,而不用擔心那個插槽是專門插哪個的,原因是做usb插槽的廠家和做各種設備的廠家都遵守了統一的規定包括尺寸,排線等等,
這樣的設計需求在Go編程中也是會大量存在的,一個程式就是一個世界,在現實世界存在的情況,在程式中也會出現,用程式來模擬一個前面的應用場景
//宣告/定義一個介面
type Usb interface {
//宣告了兩個沒有實作的方法
Start()
Stop()
}
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變數會根據傳入的實參,來判斷到底是Phone,還是Camera
//通過usb介面變數來呼叫Start和Stop方法
usb.Start()
usb.Stop()
}
func main() {
//測驗
//先創建結構體變數
computer := Computer{}
phone := Phone{}
camera := Camera{}
//關鍵點
computer.Working(phone)
computer.Working(camera)
}
interface型別可以定義一組方法,但是這些不需要實作,并且interface不能包含任何變數,到某個自定義型別(比如結構體Phone)要使用的時候,在根據具體情況把這些方法寫出來(實作)
基本語法

小結說明:
-
介面里的所有方法都沒有方法體,即介面的方法都是沒有實作的方法,介面體現了程式設計的多型和高內聚低偶合的思想
-
Go中的介面,不需要顯示的實作,只要一個變數,含有介面型別中的所有方法,那么變數就實作了這個介面,因此,Go中沒有implement這樣的關鍵字
介面使用的應用場景
-
中國要制造的轟炸機,專家只需要把飛機需要的功能/規格定下來即可,然后讓別的人具體實作即可
-
現在有一個專案經理,管理三個程式員,開發一個軟體,為了控制和管理軟體,專案經理可以定義一些介面,然后由程式員具體實作
......

注意事項和細節
介面本身不能創建實體,但是可以指向一個實作了該介面的自定義型別的變數(實體)
type AInterface interface {
say()
}
type stu struct {
Name string
}
func (stu stu) say() {
fmt.Println("stu say()")
}
func main() {
var stu stu //結構體變數,實作了say() 實作了AInterface
var a AInterface = stu
a.say()
}
介面中所有的方法都沒有方法體,即都是沒有實作的方法
在Go中,一個自定義型別需要將某個介面的所有方法都實作,我們說這個自定義型別實作了該介面
一個自定義型別只有實作了某個介面,才能將該自定義型別的實體(變數)賦給介面型別
只要是自定義資料型別,就可以實作介面,不僅僅是結構體型別
type AInterface interface {
say()
}
type stu struct {
Name string
}
type integer int
func (i integer) say() {
fmt.Println("integer say i =", i)
}
func (stu stu) say() {
fmt.Println("stu say()")
}
func main() {
var i integer = 10
var b AInterface = i
b.say()
var stu stu //結構體變數,實作了say() 實作了AInterface
var a AInterface = stu
a.say()
}
//輸出:integer say i = 10
//stu say()
一個自定義型別可以實作多個介面
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() {
//Monster實作了AInterface 和BInterface
var monster Monster
var a2 AInterface = monster
var b2 BInterface = monster
a2.say()
b2.Hello()
}
//輸出:Monster say
//Monster Hello()
Go介面中不能有任何變量

一個介面(比如A介面)可以繼承多個別的介面(比如B,C介面),這時如果要實作A介面,也必須將B,C介面的方法也全部實作
package main
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 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 t T = stu //ok
fmt.Println(t)
var t2 interface{} = stu
var num1 float64 = 8.8
t2 = num1
t = num1
fmt.Println(t2, t)
var a AInterface = stu
a.test01()
}
//輸出:{}
//8.8 8.8
介面編程最佳實踐
import (
"fmt"
"math/rand"
"sort"
)
//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) {
//交換
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)
}
//[-1 0 7 10 90]
//{英雄 81 87}
//{英雄 47 59}
//{英雄 81 18}
//{英雄 25 40}
//{英雄 56 0}
//{英雄 94 11}
//{英雄 62 89}
//{英雄 28 74}
//{英雄 11 45}
//{英雄 37 6}
//______________排序后______________
//{英雄 56 0}
//{英雄 37 6}
//{英雄 94 11}
//{英雄 81 18}
//{英雄 25 40}
//{英雄 11 45}
//{英雄 47 59}
//{英雄 28 74}
//{英雄 81 87}
//{英雄 62 89}
//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()
}
//輸出:悟空 生來會爬樹..
//悟空 通過學習,會飛翔...
//悟空 通過學習,會游泳...
對上面代碼的小結
當A結構體繼承了B結構體,那么A結構就自動的繼承了B結構體的欄位和方法,并且可以直接使用
當A結構體需要擴展功能,同時不希望去破壞繼承關系,則可以去實作某個介面即可,因此可以認為:實作介面是對繼承機制的補充

實作介面可以看作是對繼承的一種補充

介面和繼承解決的問題不同
? 繼承的價值主要在于:解決代碼的*復用性*和*可維護性*
? 介面的價值主要在于:*設計*,設計好各種規范(方法),讓其它自定義型別去實作這些方法
介面比繼承更加靈活 Person Student BirdAble LittleMonkey
? 介面比繼承更加靈活,繼承是滿足is - a的關系,而介面只需滿足like - a的關系
介面在一定程度上實作*代碼*解耦
面向物件編程三大特性 - 多型
變數(實體)具有多種形態,面向物件的第三大特性,在Go語言,多型特征是通過介面實作的,可以按照統一的介面來呼叫不同的變數實作,這時介面變數就呈現不同的形態
快速入門案例
在前面的Usb介面案例中,Usb usb 即可以接收手機變數,又可以接收相機變數,就體現了Usb介面多型特性,
//宣告/定義一個介面
type Usb interface {
//宣告了兩個沒有實作的方法
Start()
Stop()
}
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變數會根據傳入的實參,來判斷到底是Phone,還是Camera //usb介面變數就體現出多型的特點
//通過usb介面變數來呼叫Start和Stop方法
usb.Start()
usb.Stop()
}
func main() {
//測驗
//先創建結構體變數
computer := Computer{}
phone := Phone{}
camera := Camera{}
//關鍵點
computer.Working(phone)
computer.Working(camera)
}
介面體現多型的兩種形式
多型引數
? 在前面的Usb介面案例中,Usb usb 即可以接收手機變數,又可以接收相機變數,就體現了Usb介面多型特性,
多型陣列
? 演示一個案例:在Usb陣列中,存放Phone結構體和Camera結構體變數
//宣告/定義一個介面
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("相機停止作業...")
}
//計算機
type Computer struct {
}
func main() {
//定義一個Usb介面陣列,可以存放Phone和Camera的結構體變數
//這里體現出多型陣列
var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"華為"}
usbArr[2] = Phone{"小米"}
fmt.Println(usbArr)
}
型別斷言
型別斷言:由于介面是一般型別,不知道具體型別,如果要轉成具體型別,就需要使用型別斷言

案例演示
func main() {
//型別斷言的其它案例
var x interface{}
var b2 float32 = 2.2
x = b2 //空介面,可以接收任意型別
//x => float32 [使用型別那個斷言]
y := x.(float32) //轉成具體型別
fmt.Printf("y 的型別是 %T 值是 = %v", y, y)
}
//輸出:y 的型別是 float32 值是 = 2.2
對上面代碼的說明:
在進行型別斷言時,如果型別不匹配,就會報panic,因此進行型別斷言時, 要確保原來的空介面指向的就是斷言的型別
如何在進行斷言時,帶上檢測機制,如果成功就OK,否則也不要報panic
func main() {
//型別斷言的其它案例
var x interface{}
var b2 float32 = 3.3
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("繼續執行...")
//y := x.(float32) //轉成具體型別
//fmt.Printf("y 的型別是 %T 值是 = %v", y, y)
}
//輸出:y 的型別是float32 值是3.3繼續執行...
型別斷言的最佳實踐
在前面的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(p.name, "手機開始作業...")
}
func (p Phone) Stop() {
fmt.Println(p.name, "手機停止作業...")
}
type Camera struct {
name string
}
//讓Camera實作Usb介面的方法
func (c Camera) Start() {
fmt.Println(c.name, "相機開始作業...")
}
func (c Camera) Stop() {
fmt.Println(c.name, "相機停止作業...")
}
//計算機
type Computer struct {
}
//撰寫一個方法Working方法,接收一個Usb介面型別變數
//只要是實作了Usb介面(所謂實作Usb介面,就是指實作了Usb介面宣告所有方法)
func (c Computer) Working(usb Usb) { //usb變數會根據傳入的實參,來判斷到底是Phone,還是Camera //usb介面變數就體現出多型的特點
//通過usb介面變數來呼叫Start和Stop方法
usb.Start()
usb.Stop()
}
func main() {
//測驗
computer := Computer{}
var usbArr [3]Usb
usbArr[0] = Phone{"華為"}
usbArr[1] = Phone{"vivo"}
usbArr[2] = Camera{"華為"}
fmt.Println(usbArr)
for i := 0; i < len(usbArr); i++ {
computer.Working(usbArr[i])
}
}
//[{華為} {vivo} {華為}]
//華為 手機開始作業...
//華為 手機停止作業...
//vivo 手機開始作業...
//vivo 手機停止作業...
//華為 相機開始作業...
//華為 相機停止作業...
寫一函式,回圈判斷傳入引數的型別:
/撰寫一個函式,可以判斷輸入的引數是什么型別
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)
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
TypeJudge(n1, n2, n3, name, address, n4)
}
//第0個引數是 float32 型別,值是1.1
//第1個引數是 float64 型別,值是2.3
//第2個引數是 整數 型別,值是30
//第3個引數是 string 型別,值是tom
//第4個引數是 string 型別,值是北京
//第5個引數是 整數 型別,值是300
在前面代碼的基礎上,增加判斷Student型別和*Student型別
package main
import (
"fmt"
)
type Student struct {
name string
}
//撰寫一個函式,可以判斷輸入的引數是什么型別
func TypeJudge(items... interface{}) {
for index, x := range items {
switch x.(type) {
case Student:
fmt.Printf("第%v個引數是 Student 型別,值是%v\n", index, x)
case *Student:
fmt.Printf("第%v個引數是 *Student 型別,值是%v\n", index, x)
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)
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
var b bool = false
stu1 := Student{"zise"}
stu2 := &Student{"feizhzu"}
TypeJudge(n1, n2, n3, name, address, n4, b, stu1, stu2)
}
//第0個引數是 float32 型別,值是1.1
//第1個引數是 float64 型別,值是2.3
//第2個引數是 整數 型別,值是30
//第3個引數是 string 型別,值是tom
//第4個引數是 string 型別,值是北京
//第5個引數是 整數 型別,值是300
//第6個引數是 bool 型別,值是false
//第7個引數是 Student 型別,值是{zise}
//第8個引數是 *Student 型別,值是&{feizhzu}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/30786.html
標籤:Go
上一篇:go語言系列-從陣列到map
