Go学习笔记(10)-面向对象编程(下)

Go学习笔记(10)

面向对象编程(下)

面向对象编程思想-抽象

抽象的介绍

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

代码实现

package main

import (
	"fmt"
)

//定义一个结构体 Account
type Account struct {
    AccountNo string
    Pwd string
    Balance float64
}

//方法
//1. 存款
func (account *Account) Deposite(money float64, pwd string) {
    //看下输入的密码是否正确
    if pwd != account.Pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
    
    //看看存款金额是否正确
    if money <= 0 {
        fmt.Println("你输入的金额不正确")
        return
    }
    
    account.Balance += money
	fmt.Println("存款成功~~")
}

//取款
func (account *Account) WithDraw(money float64, pwd string) {
    //看下输入的密码是否正确
    if pwd != account.Pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
    
    //看看取款金额是否正确
    if money <= 0 || money > account.Balance {
        fmt.Println("你输入的金额不正确")
        return
    }
    
    account.Balance -= money
    fmt.Println("取款成功~~")
}

//查询余额

func (account *Account) Query(pwd string) {
    //看下输入的密码是否正确
    if pwd != account.Pwd {
        fmt.Println("你输入的密码不正确")
        return
    }
	fmt.Printf("你的账号为=%v 余额=%v \n", account.AccountNo, account.Balance)
}

func main() {
    //测试一把
    account := Account{
        AccountNo : "gs1111111",
        Pwd : "666666",
        Balance : 100.0,
	}
    
    //这里可以做的更加灵活,就是让用户通过控制台来输入命令...
    //菜单....
    account.Query("666666")
    account.Deposite(200.0, "666666")
    account.Query("666666")
    account.WithDraw(150.0, "666666")
    account.Query("666666")
}

对上面代码的要求

  1. 同学们自己可以独立完成
  2. 增加一个控制台的菜单,可以让用户动态的输入命令和选项
func main(){

	account := Account{
		AccountNo: "gs111111",
		Pwd: "66666",
		Balance: 100.0,
	}

	var c string
	var pwd string
	var money float64

	for {

		fmt.Println("输入q查询、d存款、w取款、e退出")
		fmt.Scanln(&c)
		if c == "e" {
			break
		}else if c == "q" {
			fmt.Println("请输入密码")
			fmt.Scanln(&pwd)
			account.Query(pwd)
		}else if c == "d" {
			fmt.Println("请输入密码")
			fmt.Scanln(&pwd)
			fmt.Println("请输入存入的金额")
			fmt.Scanln(&money)
			account.Deposite(money,pwd)
		}else if c == "w" {
			fmt.Println("请输入密码")
			fmt.Scanln(&pwd)
			fmt.Println("请输入取出的金额")
			fmt.Scanln(&money)
			account.WithDraw(money,pwd)
		}else {
			fmt.Println("输入的操作符有误,请重试")
		}
	}

	fmt.Println()
}

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

基本介绍

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

封装介绍

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

封装的理解和好处

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

如何体现封装

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

封装的实现步骤

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

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

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

    func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {

    //加入数据验证的业务逻辑

    var.字段 = 参数

    }

  4. 提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值

    func (var 结构体类型名) GetXxx() {

    ​ return var.age;

    }

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

快速入门案例

看一个案例

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

代码实现

model/person.go

main/main.go

课堂练习(学员先做)

要求

  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 函数中测试

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

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

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

走一下代码

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. 解决方法-通过继承方式来解决

继承基本介绍和示意图

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

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

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

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

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

type Goods struct {

Name string

Price int

}

type Book struct {

Goods //这里就是嵌套匿名结构体 Goods

Writer string

}

快速入门案例

案例

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

代码实现

package main
import (
	"fmt"
)

//编写一个学生考试系统
type Student struct {
    Name string
    Age int
    Score int
}

//将 Pupil 和 Graduate 共有的方法也绑定到 *Student
func (stu *Student) ShowInfo() {
    fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n", stu.Name, stu.Age, stu.Score)
}

func (stu *Student) SetScore(score int) {
    //业务判断
    stu.Score = score
}

//小学生
type Pupil struct {
	Student //嵌入了 Student 匿名结构体
}

//显示他的成绩
//这时 Pupil 结构体特有的方法,保留
func (p *Pupil) testing() {
	fmt.Println("小学生正在考试中.....")
}

//大学生, 研究生。。
//大学生
type Graduate struct {
	Student //嵌入了 Student 匿名结构体
}

//显示他的成绩
//这时 Graduate 结构体特有的方法,保留
func (p *Graduate) testing() {
	fmt.Println("大学生正在考试中.....")
}

//代码冗余.. 高中生....

func main() {
    //当我们对结构体嵌入了匿名结构体使用方法会发生变化
    pupil := &Pupil{}
    pupil.Student.Name = "tom~"
    pupil.Student.Age = 8
    pupil.testing()
    pupil.Student.SetScore(70)
    pupil.Student.ShowInfo()
    	
    graduate := &Graduate{}
    graduate.Student.Name = "mary~"
    graduate.Student.Age = 28
    graduate.testing()
    graduate.Student.SetScore(90)
    graduate.Student.ShowInfo()
}

继承给编程带来的便利

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

继承的深入讨论

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

对上面的代码小结

(1) 当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name

(2) 编译器会先看 b 对应的类型有没有 Name, 如果有,则直接调用 B 类型的 Name 字段

(3) 如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用,如果没有继续查找..如果都找不到就报错.

  1. 结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分【举例说明】

  1. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。【举例说明】
  1. 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字

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

课堂练习

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

说明

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

面向对象编程-多重继承

多重继承说明

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

案例演示

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

多重继承细节说明

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

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

接口(interface)

基本介绍

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

为什么有接口

接口快速入门

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

代码实现

package main

import (
	"fmt"
)

//声明/定义一个接口
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. Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang 中没有 implement 这样的关键字

接口使用的应用场景

注意事项和细节

  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
  1. 接口中所有的方法都没有方法体,即都是没有实现的方法。
  2. 在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
  3. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
  4. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。

  1. 一个自定义类型可以实现多个接口
  1. Golang 接口中不能有任何变量

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

  1. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
  2. 空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口。

课堂练习

接口编程的最佳实践

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

package main

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

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

//2.声明一个 Hero 结构体切片类型
type HeroSlice []Hero

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

//Less 方法就是决定你使用什么标准进行排序
//1. 按 Hero 的年龄从小到大排序!!
func (hs HeroSlice) Less(i, j int) bool {
    return hs[i].Age < hs[j].Age
    //修改成对 Name 排序
    //return hs[i].Name < hs[j].Name
}

func (hs HeroSlice) Swap(i, j int) {
    //交换
    // temp := hs[i]
    // hs[i] = hs[j]
    // hs[j] = temp
    //下面的一句话等价于三句话
    hs[i], hs[j] = hs[j], hs[i]
}

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

//将 Student 的切片,安 Score 从大到小排序!!
func main() {
    //先定义一个数组/切片
    var intSlice = []int{0, -1, 10, 7, 90}
    //要求对 intSlice 切片进行排序
    //1. 冒泡排序...
    //2. 也可以使用系统提供的方法
    sort.Ints(intSlice)
    fmt.Println(intSlice)
    //请大家对结构体切片进行排序
    //1. 冒泡排序...
    //2. 也可以使用系统提供的方法
    //测试看看我们是否可以对结构体切片进行排序
    var heroes HeroSlice
    for i := 0; i < 10 ; i++ {
        hero := Hero{
        Name : fmt.Sprintf("英雄|%d", rand.Intn(100)),
        Age : rand.Intn(100),
    	}
        //将 hero append 到 heroes 切片
        heroes = append(heroes, hero)
	}
    
    //看看排序前的顺序
    for _ , v := range heroes {
    	fmt.Println(v)
	}
	//调用 sort.Sort
    sort.Sort(heroes)
	fmt.Println("-----------排序后------------")
    //看看排序后的顺序
    for _ , v := range heroes {
    	fmt.Println(v)
    }
    
    i := 10
    j := 20
    i, j = j, i
    fmt.Println("i=", i, "j=", j) // i=20 j = 10
}

接口编程的课后练习

//1.声明 Student 结构体

type Student struct{

Name string

Age int

Score float64

}

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

实现接口 vs 继承

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

代码说明:

对上面代码的小结

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

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

​ 继承的价值主要在于:解决代码的复用性可维护性

​ 接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。

  • 接口比继承更加灵活 Person Student BirdAble LittleMonkey

​ 接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。

  • 接口在一定程度上实现代码解耦

面向对象编程-多态

基本介绍

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

快速入门

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

接口体现多态的两种形式

  • 多态参数

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

  • 多态数组

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

案例说明:

package main

import (
	"fmt"
)

//声明/定义一个接口
type Usb interface {
    //声明了两个没有实现的方法
    Start()
    Stop()
}

type Phone struct {
	name string
}

//让 Phone 实现 Usb 接口的方法
func (p Phone) Start() {
    fmt.Println("手机开始工作。。。")
}

func (p Phone) Stop() {
	fmt.Println("手机停止工作。。。")
}

type Camera struct {
	name string
}

//让 Camera 实现 Usb 接口的方法
func (c Camera) Start() {
	fmt.Println("相机开始工作。。。")
}

func (c Camera) Stop() {
    fmt.Println("相机停止工作。。。")
}

func main() {
    //定义一个 Usb 接口数组,可以存放 Phone 和 Camera 的结构体变量
    //这里就体现出多态数组
    var usbArr [3]Usb
    usbArr[0] = Phone{"vivo"}
    usbArr[1] = Phone{"小米"}
    usbArr[2] = Camera{"尼康"}
    fmt.Println(usbArr)
}

类型断言

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

基本介绍

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

  • 对上面代码的说明:

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

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

类型断言的最佳实践 1

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

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

package main

import (
	"fmt"
)

//声明/定义一个接口
type Usb interface {
    //声明了两个没有实现的方法
    Start()
    Stop()
}

type Phone struct {
	name string
}

//让 Phone 实现 Usb 接口的方法
func (p Phone) Start() {
	fmt.Println("手机开始工作。。。")
}

func (p Phone) Stop() {
	fmt.Println("手机停止工作。。。")
}

func (p Phone) Call() {
	fmt.Println("手机 在打电话..")
}

type Camera struct {
	name string
}

//让 Camera 实现 Usb 接口的方法
func (c Camera) Start() {
	fmt.Println("相机开始工作。。。")
}

func (c Camera) Stop() {
    fmt.Println("相机停止工作。。。")
}

type Computer struct {}


func (computer Computer) Working(usb Usb) {
    usb.Start()
    //如果 usb 是指向 Phone 结构体变量,则还需要调用 Call 方法
    //类型断言..[注意体会!!!]
    if phone, ok := usb.(Phone); ok {
    	phone.Call()
	}
	usb.Stop()
}

func main() {
    //定义一个 Usb 接口数组,可以存放 Phone 和 Camera 的结构体变量
    //这里就体现出多态数组
    var usbArr [3]Usb
    usbArr[0] = Phone{"vivo"}
    usbArr[1] = Phone{"小米"}
    usbArr[2] = Camera{"尼康"}
    //遍历 usbArr
    //Phone 还有一个特有的方法 call(),请遍历 Usb 数组,如果是 Phone 变量,
    //除了调用 Usb 接口声明的方法外,还需要调用 Phone 特有方法 call. =》类型断言
    var computer Computer
    for _, v := range usbArr{
    computer.Working(v)
    fmt.Println()
    }
    //fmt.Println(usbArr)
}

类型断言的最佳实践 2

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

类型断言的最佳实践 3 【学员自己完成】

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

end
  • 作者:AWhiteElephant(联系作者)
  • 发表时间:2022-05-26 15:55
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 评论