主頁 > 後端開發 > go語言系列-面向物件編程

go語言系列-面向物件編程

2020-09-14 04:54:49 後端開發

目錄

  • 面向物件編程
    • 結構體
      • 結構體與結構體變數(實體/物件)的關系示意圖
      • 結構體和結構體變數(實體)的區別和聯系
      • 結構體變數(實體)在記憶體的布局【重要】
      • 欄位/屬性
      • 創建結構體變數和訪問結構體欄位
      • struct型別的記憶體分配機制
      • 結構體使用注意事項和細節
    • 方法
      • 方法的宣告和呼叫
      • 快速入門
      • 方法的呼叫和傳參機制原理[重要]
      • 方法的宣告(定義)
      • 方法的注意事項和細節
      • 方法和函式的區別
      • 方法練習題
    • 面向物件編程應用實體
      • 步驟
      • 學生案例
      • 小狗案例
      • 盒子案例
      • 景區門票案例
    • 創建結構體變數時指定欄位值
    • 工廠模式
      • 看一個需求
      • 工廠模式來解決問題
      • 思考題
    • 面向物件編程思想-抽象
      • 快速入門案例
    • 面向物件編程三大特性-封裝
      • 封裝的好處
      • 如何體現封裝
      • 封裝的實作步驟
      • 快速入門案例
    • 面向物件編程三大特性-繼承
      • 看一個問題,引出繼承的必要性
      • 繼承基本介紹和示意圖
      • 嵌套匿名結構體的基本語法
      • 快速入門案例
      • 繼承給編程帶來的便利
      • 繼承的深入討論
      • 課堂練習
      • 面向物件編程-多重繼承
    • 介面
      • 基本語法
      • 介面使用的應用場景
      • 注意事項和細節
      • 介面編程最佳實踐
      • 介面練習題
    • 實作介面 vs 繼承
    • 面向物件編程三大特性 - 多型
      • 快速入門案例
      • 介面體現多型的兩種形式
    • 型別斷言
    • 案例演示
    • 型別斷言的最佳實踐

面向物件編程

結構體

一個程式就是一個世界,有很多物件(變數)

Go也支持面向物件編程(OOP),但是和傳統的面向物件編程有區別,并不是純粹的面向物件語言,所以說Go支持面向物件編程特性是比較準確的

Go沒有類(class),Go語言的結構體(struct)和其它編程語言的類(class)有同等的地位,可以理解Go是基于struct來實作OOP特性的,

Go面向物件編程非常簡潔,去掉了傳統OOP語言的繼承、方法多載、建構式和解構式、隱藏的this指標等等

Go仍然有面向物件編程的繼承、封裝和多型的特性,只是實作的方式和其它OOP語言不一樣,比如繼承:Go沒有extends關鍵字,繼承是通過匿名欄位來實作

  1. 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 就是屬性

注意事項和細節說明

  1. 欄位宣告語法同變數,示例:欄位名 欄位型別

  2. 欄位的型別可以為:基本型別、陣列或參考型別

  3. 在創建一個結構體變數后,如果沒有給欄位賦值,都對應一個零值(默認值),規則如下:

布爾型別是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]}
  1. 不同結構體變數的欄位是獨立的,互不影響,一個結構體變數欄位的更改,不影響另外一個,結構體是值型別
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}
}
  1. 結構體進行type重新定義(相當于取別名),Go認為是新的資料型別,但是相互間可以強轉
  2. 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() // 呼叫方法
}

對上面的總結

  1. test方法和Person型別系結

2)test方法只能通過Person型別的變數來呼叫,而不能直接呼叫,也不能使用其它型別變數來呼叫

  1. func (p Person)test(){}... p表示哪個Person變數呼叫,這個p就是它的副本,這點和函式傳參非常相似

  2. 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() // 呼叫方法
}

快速入門

  1. 給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()
}
  1. 給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()
}
  1. 給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)
}
  1. 給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方法的執行程序+說明

  1. 在通過一個變數去呼叫方法時,其呼叫機制和函式一樣

  2. 不一樣的地方時,變數呼叫方法時,該變數本身也會作為一個引數傳遞到方法(如果變數是值型別,則進行值拷貝,如果變數是參考型別,則進行地址拷貝)

案例2

請撰寫一個程式,要求如下:

  1. 宣告一個結構體Circle,欄位為radius

  2. 宣告一個方法area和Circle系結,可以回傳面積

  3. 提示:畫出area執行程序+說明

方法的宣告(定義)

func (recevier type) methodName (引數串列) (回傳值串列) {
	方法體
	return 回傳值
}

1)引數串列:表示方法輸入
2)recevier type:表示這個方法和type這個型別進行系結,或者說該方法作用于type型別
3)recevier type:type可以是結構體,也可以其它的自定義型別
4)recevier:就是type型別的一個變數(實體),比如:Person結構體的一個變數(實體)
5)回傳值串列:表示回傳的值,可以多個
6)方法主體:表示為了實作某一功能代碼塊
7)return陳述句不是必須的

方法的注意事項和細節

  1. 結構體型別是值型別,在方法呼叫中,遵守值型別的傳遞機制,是值拷貝傳遞方式

  2. 如果程式員希望在方法中,修改結構體變數的值,可以通過結構體指標的方式來處理

  3. 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
  1. 方法的訪問范圍控制的規則,和函式一樣,方法名首字母小寫,只能在本包訪問,方法首字母大寫,可以在本包和其它包訪問

  2. 如果一個型別實作了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]

方法和函式的區別

  1. 呼叫方式不一樣

? 函式的呼叫方式:函式名(實參串列)

? 方法的呼叫方式:變數.方法名(實參串列)

  1. 對于普通函式,接收者為值型別時,不能將指標型別的資料直接傳遞,反之亦然
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)
}
  1. 對于方法(如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

總結

  1. 不管呼叫形式如何,真正決定是值拷貝還是地址拷貝,看這個方法是和哪個型別系結

  2. 如果是和值型別,比如(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)轉置

面向物件編程應用實體

步驟

  1. 宣告(定義)結構體,確定結構體名

  2. 撰寫結構體的欄位

  3. 撰寫結構體的方法

學生案例

  1. 撰寫一個Student結構體,包含name、gender、age、id、score欄位,分別為string、string、int、int、float64型別

  2. 結構體中宣告一個say方法,回傳string型別,方法回傳資訊中包含所有欄位值

  3. 在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]

小狗案例

  1. 撰寫一個Dog結構體,包含name、age、weight欄位

  2. 結構體中宣告一個say方法,回傳string型別,方法回傳資訊中包含所有欄位值

  3. 在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]

盒子案例

  1. 編程創建一個Box結構體,在其中宣告三個欄位表示一個立方體的長、寬和高,長寬高要從終端獲取

  2. 宣告一個方法獲取立方體的體積

  3. 創建一個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

景區門票案例

  1. 一個景區根據游人的年齡收取不同價格的門票,比如年齡大于18,收費20元,其它情況門票免費

  2. 請撰寫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包的*結構體變**量**首字母大寫,引入后,直接使用*,沒有問題

![img](file:///C:\Users\linkun\AppData\Local\Temp\ksohtml1372\wps1.jpg)

如果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)就是把抽象出的欄位和對欄位的操作封裝在一起,資料被保護在內部,程式的其它包只有通過被授權的操作(方法),才能對欄位進行操作

封裝的好處

  1. 隱藏實作細節

  2. 可以對資料進行驗證,保證安全合理(Age)

如何體現封裝

  1. 對結構體中的屬性進行封裝

  2. 通過方法,包實作封裝

封裝的實作步驟

  1. 將結構體、欄位(屬性)的首字母小寫(不能匯出了,其它包不能使用,類似private)

  2. 給結構體所在包提供一個工廠模式的函式,首字母大寫,類似一個建構式

  3. 提供一個首字母大寫的Set方法(類似其它語言的public),用于對屬性判斷并賦值

func(var 結構體型別名) SetXxx(引數串列) (回傳值串列) {
	//加入資料驗證的業務邏輯
	var.欄位 = 引數
}
  1. 提供一個首字母大寫的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())
}

要求

  1. 創建程式,在model包中定義Account結構體:在main函式中體現Go的封裝性

  2. Account結構體要求具有欄位:賬號(長度在6-10之間)、余額(必須>20)、密碼(必須是6位數)

  3. 通過SetXxx的方法給Account的欄位賦值

  4. 在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. 代碼的復用性提高了

  2. 代碼的擴展性和維護性提高了

繼承的深入討論

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. 匿名結構體欄位訪問可以簡化

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

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

  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嵌套了多個匿名結構體,那么該結構體可以直接訪問嵌套的匿名結構體的欄位和方法,從而實作了多重繼承,
案例演示
通過一個案例來說明多重繼承使用

多重繼承細節

  1. 若嵌入的匿名結構體有相同的欄位名或者方法名,則在訪問時,需要通過匿名結構體型別名來區分

    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)要使用的時候,在根據具體情況把這些方法寫出來(實作)

基本語法


小結說明:

  1. 介面里的所有方法都沒有方法體,即介面的方法都是沒有實作的方法,介面體現了程式設計的多型和高內聚低偶合的思想

  2. Go中的介面,不需要顯示的實作,只要一個變數,含有介面型別中的所有方法,那么變數就實作了這個介面,因此,Go中沒有implement這樣的關鍵字

介面使用的應用場景

  1. 中國要制造的轟炸機,專家只需要把飛機需要的功能/規格定下來即可,然后讓別的人具體實作即可

  2. 現在有一個專案經理,管理三個程式員,開發一個軟體,為了控制和管理軟體,專案經理可以定義一些介面,然后由程式員具體實作

......

注意事項和細節

介面本身不能創建實體,但是可以指向一個實作了該介面的自定義型別的變數(實體)

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

下一篇:架構設計 | 基于電商交易流程,圖解TCC事務分段提交

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more