GO-第 11 章面向对象编程(下)

179 阅读15分钟

11.1 面向对象编程思想-抽象

11.1.1 抽象的介绍

我们在前面去定义一个结构体时候,实际上就是把一类事物的共有的属性(字段)和行为(方法)提取 出来,形成一个物理模型(结构体)。这种研究问题的方法称为抽象。

image.png

11.1.2 代码实现

package main
​
import (
    "fmt"
)
​
//定义一个结构体Account
type Account struct {
    AccountNo string
    Pwd       string
    Balance   float64
}
​
//方法
//1. 存款
func (account *Account) Deposite(money float64, pwd string) {
​
    //看下输入的密码是否正确
    if pwd != account.Pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
​
    //看看存款金额是否正确
    if money <= 0 {
        fmt.Println("你输入的金额不正确")
        return
    }
​
    account.Balance += money
    fmt.Println("存款成功~~")
​
}
​
//取款
func (account *Account) WithDraw(money float64, pwd string) {
​
    //看下输入的密码是否正确
    if pwd != account.Pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
​
    //看看取款金额是否正确
    if money <= 0 || money > account.Balance {
        fmt.Println("你输入的金额不正确")
        return
    }
​
    account.Balance -= money
    fmt.Println("取款成功~~")
​
}
​
//查询余额
func (account *Account) Query(pwd string) {
​
    //看下输入的密码是否正确
    if pwd != account.Pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
​
    fmt.Printf("你的账号为=%v 余额=%v \n", account.AccountNo, account.Balance)
​
}
​
func main() {
​
    //测试一把
    account := Account{
        AccountNo: "gs1111111",
        Pwd:       "11",
        Balance:   100.0,
    }
​
    //这里可以做的更加灵活,就是让用户通过控制台来输入命令...
    //菜单....for {
        fmt.Printf("输入需要的操作:\n" +
            "1:存钱\n" +
            "2:取钱\n" +
            "3:查询余额\n" +
            "4:退出~\n")
        num := 0
        fmt.Scanln(&num)
        if num == 4{
            break
        }
        var pwd string
        var money float64
        switch num {
        case 1:
            fmt.Print("输入密码:")
            fmt.Scanln(&pwd)
            fmt.Print("输入要存的金额:")
            fmt.Scanln(&money)
            account.Deposite(money, pwd)
        case 2:
            fmt.Print("输入密码:")
            fmt.Scanln(&pwd)
            fmt.Print("输入要取的金额:")
            fmt.Scanln(&money)
            account.WithDraw(money, pwd)
        case 3:
            fmt.Print("输入密码:")
            fmt.Scanln(&pwd)
            account.Query(pwd)
        default:
            fmt.Println("输入有误~~")
        }
​
    }
​
}

11.2面向对象编程三大特性-封装

11.2.1 基本介绍

Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一 样,下面我们一一为同学们进行详细的讲解 Golang 的三大特性是如何实现的

11.2.2 封装介绍

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其 它包只有通过被授权的操作(方法),才能对字段进行操作

image.png

11.2.3 封装的理解和好处

  • 隐藏实现细节
  • 可以对数据进行验证,保证安全合理(Age)

11.2.4 如何体现封装

  1. 对结构体中的属性进行封装
  2. 通过方法,包 实现封装

11.2.5 封装的实现步骤

  1. 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)

  2. 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数

  3. 提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值

    func (var 结构体类型名) SetXxx(参数列表) (返回值列表){
        //加入数据验证的业务逻辑
        var.字段 = 参数
    }
    
  4. 提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值

    func (var 结构体类型名)GetXxx(){
        return var.age
    }
    

    特别说明: 在 Golang 开发中并没有特别强调封装,这点并不像 Java. 所以提醒学过 java 的朋友, 不用总是用 java 的语法特性来看待 Golang, Golang 本身对面向对象的特性做了简化的

11.2.6 快速入门案例

看一个案例

请大家看一个程序(person.go),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证。设计: model 包(person.go) main 包(main.go 调用 Person 结构体)

代码实现

image.png

image.png

11.2.7 课堂练习(学员先做)

要求

  1. 创建程序,在 model 包中定义 Account 结构体:在 main 函数中体会 Golang 的封装性
  2. Account 结构体要求具有字段:账号(长度在 6-10 之间)、余额(必须>20)、密码(必须是六位数)
  3. 通过 SetXxx 的方法给 Account 的字段赋值。
  4. 在 main 函数中测试

代码实现

model/account.go

package model
​
import (
    "fmt"
)
​
//定义一个结构体account
type account struct {
    accountNo string
    pwd       string
    balance   float64
}
​
//工厂模式的函数-构造函数
func NewAccount(accountNo string, pwd string, balance float64) *account {
​
    if len(accountNo) < 6 || len(accountNo) > 10 {
        fmt.Println("账号的长度不对...")
        return nil
    }
​
    if len(pwd) != 6 {
        fmt.Println("密码的长度不对...")
        return nil
    }
​
    if balance < 20 {
        fmt.Println("余额数目不对...")
        return nil
    }
​
    return &account{
        accountNo: accountNo,
        pwd:       pwd,
        balance:   balance,
    }
​
}
​
//方法
//1. 存款
func (account *account) Deposite(money float64, pwd string) {
​
    //看下输入的密码是否正确
    if pwd != account.pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
​
    //看看存款金额是否正确
    if money <= 0 {
        fmt.Println("你输入的金额不正确")
        return
    }
​
    account.balance += money
    fmt.Println("存款成功~~")
​
}
​
//取款
func (account *account) WithDraw(money float64, pwd string) {
​
    //看下输入的密码是否正确
    if pwd != account.pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
​
    //看看取款金额是否正确
    if money <= 0 || money > account.balance {
        fmt.Println("你输入的金额不正确")
        return
    }
​
    account.balance -= money
    fmt.Println("取款成功~~")
​
}
​
//查询余额
func (account *account) Query(pwd string) {
​
    //看下输入的密码是否正确
    if pwd != account.pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
​
    fmt.Printf("你的账号为=%v 余额=%v \n", account.accountNo, account.balance)
​
}
​

main/main.go

package main
​
import (
    "fmt"
    "go_code/chapter11/encapexercise/model"
)
​
func main() {
    //创建一个account变量
    account := model.NewAccount("jzh11111", "000", 40)
    if account != nil {
        fmt.Println("创建成功=", account)
    } else {
        fmt.Println("创建失败")
    }
}

说明:在代码基础上增加如下功能:

通过 SetXxx 的方法给 Account 的字段赋值 通过 GetXxx 方法获取字段的值,在 main 函数中测试

11.3 面向对象编程三大特性-继承

11.3.1 看一个问题,引出继承的必要性

一个小问题,看个学生考试系统的程序 extends01.go,提出代码复用的问

image.png

走一下代码

package main
​
import (
    "fmt"
)
​
//编写一个学生考试系统
//小学生
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: "mary",
        Age:  20,
    }
    graduate.testing()
    graduate.SetScore(90)
    graduate.ShowInfo()
}

对上面代码的小结

  1. Pupil 和 Graduate 两个结构体的字段和方法几乎,但是我们却写了相同的代码, 代码复用性不 强
  2. 出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展
  3. 解决方法-通过继承方式来解决

11.3.2 继承基本介绍和示意图

继承可以解决代码复用,让我们的编程更加靠近人类思维。

当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的 Student),在该结构体中定义这些相同的属性和方法

其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 Student 匿名结构体即可。 [画 出示意图]

image.png

也就是说:在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访 问匿名结构体的字段和方法,从而实现了继承特性

11.3.3 嵌套匿名结构体的基本语法

type Goods stuct{
    Name string
    Price int
}
​
type Book struct{
    Goods //这里就是嵌套匿名结构体Goods
    Writer string
}

11.3.4 快速入门案例

案例

我们对 extends01.go 改进,使用嵌套匿名结构体的方式来实现继承特性,请大家注意体会这样编程的好处

image.png

11.3.5 继承给编程带来的便利

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了

11.3.6 继承的深入讨论

  1. 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法, 都可以使用。【举例说明】

    image.png

  2. 匿名结构体字段访问可以简化,如图

    image.png对上面的代码小结

    1. 当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name
    2. 编译器会先看 b 对应的类型有没有 Name, 如果有,则直接调用 B 类型的 Name 字段
    3. 如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用,如果没有 继续查找..如果都找不到就报错
  3. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问 匿名结构体的字段和方法,可以通过匿名结构体名来区分【举例说明】

    image.png

  4. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身 没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。【举例说明】

    image.png

  5. 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合 的结构体的字段或方法时,必须带上结构体的名字

    image.png

  6. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

    image.png

11.3.7 课堂练习

结构体的匿名字段是基本数据类型,如何访问, 下面代码输出什么

image.png

说明

  1. 如果一个结构体有int类型的匿名字段,就不能第二个
  2. 如果需要有多个int的字段,则必须给int字段指定名字

11.3.8 面向对象编程-多重继承

多重继承说明

如一个 struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方 法,从而实现了多重继承

案例演示

通过一个案例来说明多重继承使用

image.png

多重继承细节说明

  1. 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来 区分

    image.png

  2. 为了保证代码的简洁性,建议尽量不使用多重继承

11.4 接口(interface)

11.4.1 基本介绍

按顺序,我们应该讲解多态,但是在讲解多态前,我们需要讲解接口(interface),因为在 Golang 中 多态 特性主要是通过接口来体现的

11.4.2 为什么有接口

image.png

11.4.3 接口快速入门

这样的设计需求在 Golang 编程中也是会大量存在的,我曾经说过,一个程序就是一个世界,在现实世 界存在的情况,在程序中也会出现。我们用程序来模拟一下前面的应用场景

package main
​
import (
    "fmt"
)
​
//声明/定义一个接口
type Usb interface {
    //声明了两个没有实现的方法
    Start()
    Stop()
}
​
//声明/定义一个接口
type Usb2 interface {
    //声明了两个没有实现的方法
    Start()
    Stop()
    Test()
}
​
type Phone struct {
}
​
//让Phone 实现 Usb接口的方法
func (p Phone) Start() {
    fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
    fmt.Println("手机停止工作。。。")
}
​
type Camera struct {
}
​
//让Camera 实现   Usb接口的方法
func (c Camera) Start() {
    fmt.Println("相机开始工作~~~。。。")
}
func (c Camera) Stop() {
    fmt.Println("相机停止工作。。。")
}
​
//计算机
type Computer struct {
}
​
//编写一个方法Working 方法,接收一个Usb接口类型变量
//只要是实现了 Usb接口 (所谓实现Usb接口,就是指实现了 Usb接口声明所有方法)
func (c Computer) Working(usb Usb) {
​
    //通过usb接口变量来调用Start和Stop方法
    usb.Start()
    usb.Stop()
}
​
func main() {
​
    //测试
    //先创建结构体变量
    computer := Computer{}
    phone := Phone{}
    camera := Camera{}
​
    //关键点
    computer.Working(phone)
    computer.Working(camera) //
}
​

说明: 上面的代码就是一个接口编程的快速入门案例。

11.4.4 接口概念的再说明

interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个 自定义类型(比如结构体 Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。

11.4.5 基本语法

image.png

小结说明:

  1. 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的 多态和高内聚低偶合的思想。
  2. Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个 变量就实现这个接口。因此,Golang 中没有 implement 这样的关键字

11.4.6 接口使用的应用场景

image.png

11.4.7 注意事项和细节

  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)

    image.png

  2. 接口中所有的方法都没有方法体,即都是没有实现的方法

  3. 在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现 了该接口

  4. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型

  5. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型

    image.png

  6. 一个自定义类型可以实现多个接口

    image.png

  7. Golang 接口中不能有任何变量

    image.png

  8. 一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必 须将 B,C 接口的方法也全部实现。

    image.png

  9. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil

  10. 空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量 赋给空接口

    image.png

    image.png

11.4.8 课堂练习

image.png

image.png

11.4.9 接口编程的最佳实践

实现对 Hero 结构体切片的排序: sort.Sort(data Interface)

package main

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) {
	//交换
	// temp := hs[i]
	// hs[i] = hs[j]
	// hs[j] = temp
	//下面的一句话等价于三句话
	hs[i], hs[j] = hs[j], hs[i]
}

//1.声明Student结构体
type Student struct {
	Name  string
	Age   int
	Score float64
}

//将Student的切片,安Score从大到小排序!!

func main() {

	//先定义一个数组/切片
	var intSlice = []int{0, -1, 10, 7, 90}
	//要求对 intSlice切片进行排序
	//1. 冒泡排序...
	//2. 也可以使用系统提供的方法 
	sort.Ints(intSlice)
	fmt.Println(intSlice)

	//请大家对结构体切片进行排序
	//1. 冒泡排序...
	//2. 也可以使用系统提供的方法

	//测试看看我们是否可以对结构体切片进行排序
	var heroes HeroSlice
	for i := 0; i < 10; i++ {
		hero := Hero{
			Name: fmt.Sprintf("英雄|%d", rand.Intn(100)),
			Age:  rand.Intn(100),
		}
		//将 hero append到 heroes切片
		heroes = append(heroes, hero)
	}

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

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

接口编程的课后练习

//1.声明 Student 结构体
type Student struct{
    Name string
    Age int
    Score float64
}
//将 Student 的切片

11.4.10 实现接口 vs 继承

大家听到现在,可能会对实现接口和继承比较迷茫了, 这个问题,那么他们究竟有什么区别呢

image.png

代码说明:

image.png

image.png

对上面代码的小结

  1. 当 A 结构体继承了 B 结构体,那么 A 结构就自动的继承了 B 结构体的字段和方法,并且可以直 接使用
  2. 当 A 结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我 们可以认为:实现接口是对继承机制的补充

实现接口可以看作是对 继承的一种补充

image.png

  • 接口和继承解决的解决的问题不同

    • 继承的价值主要在于:解决代码的复用性和可维护性
    • 接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
  • 接口比继承更加灵活 Person Student BirdAble LittleMonkey

    • 接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系
  • 接口在一定程度上实现代码解耦

11.5 面向对象编程-多态

11.5.1 基本介绍

变量(实例)具有多种形态。面向对象的第三大特征,在 Go 语言,多态特征是通过接口实现的。可 以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。

11.5.2 快速入门

在前面的 Usb 接口案例,Usb usb ,既可以接收手机变量,又可以接收相机变量,就体现了 Usb 接 口 多态特性。[点明]

image.png

11.5.3 接口体现多态的两种形式

多态参数

在前面的 Usb 接口案例,Usb usb ,即可以接收手机变量,又可以接收相机变量,就体现了 Usb 接 口 多态。

多态数组

演示一个案例:给 Usb 数组中,存放 Phone 结构体 和 Camera 结构体变量

案例说明:

package main
​
import (
    "fmt"
)
​
//声明/定义一个接口
type Usb interface {
    //声明了两个没有实现的方法
    Start()
    Stop()
}
​
type Phone struct {
    name string
}
​
//让Phone 实现 Usb接口的方法
func (p Phone) Start() {
    fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
    fmt.Println("手机停止工作。。。")
}
​
type Camera struct {
    name string
}
​
//让Camera 实现   Usb接口的方法
func (c Camera) Start() {
    fmt.Println("相机开始工作。。。")
}
func (c Camera) Stop() {
    fmt.Println("相机停止工作。。。")
}
​
func main() {
    //定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
    //这里就体现出多态数组
    var usbArr [3]Usb
    usbArr[0] = Phone{"vivo"}
    usbArr[1] = Phone{"小米"}
    usbArr[2] = Camera{"尼康"}
​
    fmt.Println(usbArr)
​
}

11.6 类型断言

11.6.1 由一个具体的需要,引出了类型断言.

image.png

11.6.2 基本介绍

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言, 具体的如下:

image.png

对上面代码的说明:

在进行类型断言时,如果类型不匹配,就会报 panic, 因此进行类型断言时,要确保原来的空接口 指向的就是断言的类型

如何在进行断言时,带上检测机制,如果成功就 ok,否则也不要报 panic

image.png

11.6.3 类型断言的最佳实践 1

在前面的 Usb 接口案例做改进:

给 Phone 结构体增加一个特有的方法 call(), 当 Usb 接口接收的是 Phone 变量时,还需要调用 call 方法,

走代码

package main
​
import (
    "fmt"
)
​
//声明/定义一个接口
type Usb interface {
    //声明了两个没有实现的方法
    Start()
    Stop()
}
​
type Phone struct {
    name string
}
​
//让Phone 实现 Usb接口的方法
func (p Phone) Start() {
    fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
    fmt.Println("手机停止工作。。。")
}
​
func (p Phone) Call() {
    fmt.Println("手机 在打电话..")
}
​
type Camera struct {
    name string
}
​
//让Camera 实现   Usb接口的方法
func (c Camera) Start() {
    fmt.Println("相机开始工作。。。")
}
func (c Camera) Stop() {
    fmt.Println("相机停止工作。。。")
}
​
type Computer struct {
}
​
func (computer Computer) Working(usb Usb) {
    usb.Start()
    //如果usb是指向Phone结构体变量,则还需要调用Call方法
    //类型断言..[注意体会!!!]
    if phone, ok := usb.(Phone); ok {
        phone.Call()
    }
    usb.Stop()
}
​
func main() {
    //定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
    //这里就体现出多态数组
    var usbArr [3]Usb
    usbArr[0] = Phone{"vivo"}
    usbArr[1] = Phone{"小米"}
    usbArr[2] = Camera{"尼康"}
​
    //遍历usbArr
    //Phone还有一个特有的方法call(),请遍历Usb数组,如果是Phone变量,
    //除了调用Usb 接口声明的方法外,还需要调用Phone 特有方法 call. =》类型断言
    var computer Computer
    for _, v := range usbArr {
        computer.Working(v)
        fmt.Println()
    }
    //fmt.Println(usbArr)
}

11.6.4 类型断言的最佳实践 2

写一函数,循环判断传入参数的类型:

package main
import (
    "fmt"
)
​
//编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items... interface{}) {
    for index, x := range items {
     index ++
        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, stu1, stu2)
​
}

11.6.5 类型断言的最佳实践 3

在前面代码的基础上,增加判断 Student 类型和 *Student 类型

image.png