Go第 11 章 :面向对象编程(下)

news2024/11/18 14:45:58

Go第 11 章 :面向对象编程(下)

11.1 VSCode 的使用

11.1.1 VSCode 使用技巧和经验

请添加图片描述
请添加图片描述
请添加图片描述

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

11.2.1 抽象的介绍

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

11.2.2 代码实现

package main

import "fmt"

type Account struct {
	Name    string
	Pwd     string
	Balance float64
}

func (a *Account) Despoit(money float64, pwd string) {
	lable1:
	if a.Pwd != pwd {
		fmt.Println("password error!")
		fmt.Println("请重新输入正确的密码!")
		fmt.Scanln(&pwd)
		goto lable1
	}
	lable2:
	if money < 0 {
		fmt.Println("input money is error!")
		fmt.Println("请重新输入正确的金额!")
		fmt.Scanln(&money)
		goto lable2
	}
	a.Balance += money
}

func (a *Account) WithDraw(money float64, pwd string) {
	lable1:
	if a.Pwd != pwd {
		fmt.Println("password error!")
		fmt.Println("请重新输入正确的密码!")
		fmt.Scanln(&pwd)
		goto lable1
	}
	lable2:
	if money < 0 || money > a.Balance {
		fmt.Println("input money is error!")
		fmt.Println("请重新输入正确的金额!")
		fmt.Scanln(&money)
		goto lable2
	}
	a.Balance -= money
}
func (a *Account)Query(name string,pwd string){
		lable1:
		if a.Name != name{
			fmt.Println("users name is error!")
			fmt.Println("请重新输入正确的用户名!")
			fmt.Scanln(&name)
			goto lable1
		}
		lable2:
		if a.Pwd != pwd {
			fmt.Println("password error!")
			fmt.Println("请重新输入正确的密码!")
			fmt.Scanln(&pwd)
			goto lable2
		}
	fmt.Println("账户:",a.Name,"的余额是:",a.Balance)
}
func main () {
	a := Account{
		Name:"田毅",
		Pwd:"888888",
		Balance: 100.0,
	}
	b := Account{
		Name:"杨璐羽",
		Pwd:"666666",
		Balance:100.0,
	}
	a.Query("田毅","888888")
	a.WithDraw(50,"888888")
	a.Despoit(1000000,"888888")
	a.Query("田毅","888888")
	b.Query("杨璐羽","666666")
	b.WithDraw(50,"666666")
	b.Despoit(100000,"666666")
	b.Query("杨璐","6666666")

}

请添加图片描述
对上面代码的要求

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

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

11.3.1 基本介绍

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

11.3.2 封装介绍

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

11.3.3 封装的理解和好处

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

11.3.4 如何体现封装

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

11.3.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.3.6 快速入门案例

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

 代码实现
model/person.go

package model

import "fmt"

type person struct {
	Name string
	age  int //其它包不能直接使用
	sal  float64
}

func NewPerson(name string) *person {
	return &person{
		Name: name,
	}
}

func (p *person) SetAge(age int) {
	if age > 0 && age < 150 {
		p.age = age
	} else {
		fmt.Println("年龄范围不正确")
	}
}
func (p *person) GetAge() int {
	return p.age
}
func (p *person) SetSal(sal float64) {
	if sal >= 3000 && sal <= 30000 {
		p.sal = sal
	} else {
		fmt.Println("薪水范围不正确")
	}
}
func (p *person) GetSal() float64 {
	return p.sal
}

main/main.go

package main

import (
	"fmt"
	model "go_code/go_code/chapter11/model"
)

func main() {
	p:=model.NewPerson("tianyi")
	p.SetAge(18)
	p.SetSal(5000)
	fmt.Println(p)
	fmt.Println(p.Name,"ag=",p.GetAge(),"sal=",p.GetSal())
}

11.3.7 课堂练习(学员先做)

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

model

package accounts

import "fmt"

type Account struct{
	username string
	balance int
	pwd string
}

func (a *Account) SetUsername (n string){
	if !(len(n)>=6&&len(n)<=10){
		fmt.Println("用户名格式错误!请输入6-10位的用户名:")
		fmt.Scanln(&n)
	}
	a.username=n
}

func (a *Account) GetUsername() string {
	return a.username
}

func (a *Account) SetBalance (b int){
	if b<20{
		fmt.Println(a.username,"的余额小于20!请存入大于20的金额:")
		fmt.Scanln(&b)
	}
	a.balance=b
}

func (a *Account) GetBalance() int {
	return a.balance
}

func (a *Account) SetPwd (p string){
	if len(p)!=6{
		fmt.Println(a.username,"的密码格式错误!请输入6位用户密码:")
		fmt.Scanln(&p)
	}
	a.pwd=p
}

func (a *Account) GetPwd() string {
	return a.pwd
}

func (a *Account) Deposite(money int, pwd string) {
	for i := 0; i < 3; i++ {
		if a.pwd != pwd {
			fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")
			fmt.Scanln(&pwd)
		}
		break
	}
	if money <= 0 {
		fmt.Println("您输入的金额数不正确!请重新输入:")
		fmt.Scanln(&money)
	}
	a.balance += money
	fmt.Println("存款成功")
}
func (a *Account) WithDraw(money int, pwd string) {
	for i := 0; i < 3; i++ {
		if a.pwd != pwd {
			fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")
			fmt.Scanln(&pwd)
		}
		break
	}
	if money <= 0 || money > a.balance {
		fmt.Println("您输入的金额数不正确!请重新输入:")
		fmt.Scanln(&money)
	}
	a.balance -= money
	fmt.Println("取款成功")
}

func (a *Account) Query(pwd string) {
	for i := 0; i < 3; i++ {
		if a.pwd != pwd {
			fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")
			fmt.Scanln(&pwd)
		}
		break
	}
	fmt.Printf("你的账号为=%v 余额=%v \n", a.username, a.balance)
}

main

package main

import (
	"fmt"
	accounts "go_code/go_code/chapter11/account"
	model "go_code/go_code/chapter11/model"
)

func main01() {
	p:=model.NewPerson("tianyi")
	p.SetAge(18)
	p.SetSal(5000)
	fmt.Println(p)
	fmt.Println(p.Name,"ag=",p.GetAge(),"sal=",p.GetSal())
}
func main(){
	var account1 accounts.Account
	account1.SetUsername("田毅")
	account1.SetBalance(10000)
	account1.SetPwd("888888")

	var account2 accounts.Account
	account2.SetUsername("小羊")
	account2.SetBalance(10)
	account2.SetPwd("8888888")

	fmt.Println(account1)
	fmt.Println(account2)
	fmt.Printf("用户:%v 余额:%v 密码:%v\n",account1.GetUsername(),account1.GetBalance(),account1.GetPwd())
	fmt.Printf("用户:%v 余额:%v 密码:%v\n",account2.GetUsername(),account2.GetBalance(),account2.GetPwd())

}
工厂模式函数的方法

model

package factory

import "fmt"

type account struct {
	accountNo string
	pwd       string
	balance   float64
}

func NweAccount(accountNo string, pwd string, balance float64) *account {
	if !(len(accountNo) >= 6 && len(accountNo) <= 10) {
		fmt.Println("用户名格式错误!请输入6-10位的用户名:")
		fmt.Scanln(&accountNo)
	}
	if balance < 20 {
		fmt.Println(accountNo, "的余额小于20!请存入大于20的金额:")
		fmt.Scanln(&balance)
	}
	if len(pwd) != 6 {
		fmt.Println(accountNo, "的密码格式错误!请输入6位用户密码:")
		fmt.Scanln(&pwd)
	}
	return &account{
		accountNo: accountNo,
		pwd:       pwd,
		balance:   balance,
	}
}
func (a *account) Deposite(money float64, pwd string) {
	for i := 0; i < 3; i++ {
		if a.pwd != pwd {
			fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")
			fmt.Scanln(&pwd)
		}
		break
	}
	if money <= 0 {
		fmt.Println("您输入的金额数不正确!请重新输入:")
		fmt.Scanln(&money)
	}
	a.balance += money
	fmt.Println("存款成功")
}
func (a *account) WithDraw(money float64, pwd string) {
	for i := 0; i < 3; i++ {
		if a.pwd != pwd {
			fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")
			fmt.Scanln(&pwd)
		}
		break
	}
	if money <= 0 || money > a.balance {
		fmt.Println("您输入的金额数不正确!请重新输入:")
		fmt.Scanln(&money)
	}
	a.balance -= money
	fmt.Println("取款成功")
}

func (a *account) Query(pwd string) {
	for i := 0; i < 3; i++ {
		if a.pwd != pwd {
			fmt.Println("输入的用户密码不正确!您一共有三次机会,请重新输入:")
			fmt.Scanln(&pwd)
		}
		break
	}
	fmt.Printf("你的账号为=%v 余额=%v \n", a.accountNo, a.balance)
}

func (a *account) GetBalance() float64 {
	return a.balance
}

func (a *account) GetPwd() string {
	return a.pwd
}

func (a *account) GetUsername() string {
	return a.accountNo
}

main

func main() {
	account1:=factory.NweAccount("tianyi","000",10)
	account2:=factory.NweAccount("yangluyu","666666",10000)
	fmt.Printf("用户:%v 余额:%v 密码:%v\n", account1.GetUsername(), account1.GetBalance(), account1.GetPwd())
	fmt.Printf("用户:%v 余额:%v 密码:%v\n", account2.GetUsername(), account2.GetBalance(), account2.GetPwd())
}

 说明:在老师的代码基础上增加如下功能: 通过 SetXxx 的方法给 Account 的字段赋值 通过 GetXxx方法获取字段的值。(同学们自己完成) 在 main函数中测试

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

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

一个小问题,看个学生考试系统的程序 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. 解决方法-通过继承方式来解决

11.4.2 继承基本介绍和示意图

继承可以解决代码复用,让我们的编程更加靠近人类思维。 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的
Student),在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 Student 匿名结构体即可。 [画 出示意图]

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

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

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

11.4.4 快速入门案例

 案例
我们对 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()
}

11.4.5 继承给编程带来的便利

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

11.4.6 继承的深入讨论

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

  2. 匿名结构体字段访问可以简化,如图
    请添加图片描述

对上面的代码小结
  • (1) 当我们直接通过 b 访问字段或方法时,其执行流程如下比如 b.Name
  • (2) 编译器会先看 b 对应的类型有没有 Name, 如果有,则直接调用 B 类型的 Name 字段
  • (3) 如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用,如果没有继续查找…如果都找不到就报错.
  1. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问
    匿名结构体的字段和方法,可以通过匿名结构体名来区分【举例说明】
    请添加图片描述
  2. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身 没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。【举例说明】
    请添加图片描述
  3. 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合 的结构体的字段或方法时,必须带上结构体的名字
    请添加图片描述
  4. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
    请添加图片描述
    请添加图片描述

11.4.7 课堂练习

结构体的匿名字段是基本数据类型,如何访问, 下面代码输出什么
请添加图片描述
说明

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

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

多重继承说明
如一个 struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方 法,从而实现了多重继承。
请添加图片描述
多重继承细节说明

  1. 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来 区分。【案例演示】
    请添加图片描述
2) 为了保证代码的简洁性,建议大家尽量不使用多重继承

11.5 接口(interface)

11.5.1 基本介绍

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

11.5.2 为什么有接口

请添加图片描述

11.5.3 接口快速入门

这样的设计需求在 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)
}

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

高内聚、低耦合 体现

11.5.4 接口概念的再说明

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

11.5.5 基本语法

请添加图片描述
请添加图片描述

11.5.6 接口使用的应用场景

请添加图片描述
请添加图片描述
请添加图片描述

请添加图片描述

11.5.7 注意事项和细节

  1. 接口本身不能创建实例,但是**可以指向一个实现了该接口的自定义类型的变量(**实例)
    请添加图片描述
    2)接口中所有的方法都没有方法体,即都是没有实现的方法。
    3)在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口
    4)一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
    5)只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
    请添加图片描述
  2. 一个自定义类型可以实现多个接口
    请添加图片描述
  3. Golang 接口中不能有任何变量
    请添加图片描述
  4. 一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必 须将 B,C 接口的方法也全部实现。
    请添加图片描述
    请添加图片描述
  5. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
10) 空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口。

请添加图片描述
请添加图片描述

11.5.8 课堂练习

请添加图片描述
请添加图片描述
请添加图片描述

11.5.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
}

//2,声明 StudentSlice切片
type StudentSlice []Student

//3、使用切片实现Interface接口的方法
func (st StudentSlice) Len() int {
	return len(st)
}
func (st StudentSlice) Less(i, j int) bool {
	return st[i].Score < st[j].Score && st[i].Age<st[j].Age
}
func (st StudentSlice) Swap(i, j int) {
	st[i], st[j] = st[j], st[i]
}

//将 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


	var students StudentSlice
	for i:=0;i<10;i++{
		student := Student{
			Name:  fmt.Sprintf("张|%d",rand.Intn(100)),
			Age:   rand.Intn(100),
			Score: float64(rand.Intn(100)),
		}
		students=append(students,student)
	}
	fmt.Println("-----------排序前------------")
	for _,v :=range students{
		fmt.Println(v)
	}
	sort.Sort(students)
	fmt.Println("-----------排序后------------")
	for _,v :=range students{
		fmt.Println(v)
	}

}

接口编程的课后练习
//1.声明 Student 结构体
type Student struct{
Name string Age int Score float64
}
//将 Student 的切片,安 Score 从大到小排序!!

11.5.10 实现接口 vs 继承

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

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

请添加图片描述

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

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

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

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

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

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

11.6 面向对象编程-多态

11.6.1 基本介绍

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

11.6.2 快速入门

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

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

请添加图片描述
请添加图片描述
案例说明:

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)
	usbArr[0].Start()
	usbArr[0].Stop()
}

11.7 类型断言

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

请添加图片描述

11.7.2 基本介绍

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言,
请添加图片描述

对上面代码的说明:

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

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

请添加图片描述

11.7.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 (c 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{"尼康"}
	//fmt.Println(usbArr)
	//usbArr[0].Start()
	//usbArr[0].Stop()
	//遍历 usbArr
	//Phone 还有一个特有的方法 call(),请遍历 Usb 数组,如果是 Phone 变量,
	//除了调用 Usb 接口声明的方法外,还需要调用 Phone 特有方法 call. =》类型断言
	var computer Computer
	for _,v:= range usbArr{
		computer.Working(v)
		fmt.Println()
	}
	fmt.Println(usbArr)
}

11.7.4 类型断言的最佳实践 2

写一函数,循环判断传入参数的类型:
请添加图片描述
请添加图片描述

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

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

package main

import (
	"fmt"
)

type Student struct {

}

func TypeJudge(items ...interface{}) {

	for index, x := range items {

		switch x.(type) {
		case bool:
			fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)
		case float32:
			fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)
		case float64:
			fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)
		case int, int32, int64:
			fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)
		case string:
			fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)
		case Student:
			fmt.Printf("第%v个参数是Student类型,值是%v \n", index, x)
		case *Student:
			fmt.Printf("第%v个参数是*Student类型,值是%v \n", index, x)
		default:
			fmt.Printf("第%vv个参数是 类型不确定,值是%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
	n5 :=Student{}
	n6:=&Student{}
	TypeJudge(n1, n2, n3, name, address, n4,n5,n6)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/149368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手把手教你图文并茂windows10安装VMware创建CentOS-7-x86_64运行linux系统

VMware是什么 VMWare (Virtual Machine ware)可以使你的计算机上同时运行几个系统、例如windows、DOS、LINUX等同时存在&#xff0c;可以将这些系统像程序似的随时切换&#xff0c;并且不会影响主系统&#xff0c;所有系统共享一个IP。 下载 VMware官网 安装 网上搜索一个序…

LeetCode栈和队列经典例题

本期博客给大家带来了几道经典栈和队列题&#xff0c;吃透它简直易如反掌~1.括号匹配问题题目地址&#xff1a;20. 有效的括号 - 力扣&#xff08;Leetcode&#xff09;解题思路&#xff1a;在这里我们创建一个栈&#xff0c;每次将字符入栈之前先对比栈顶元素是否相同&#xf…

蓝桥杯嵌入式之 LED 闪烁

这篇文章将详细为大家介绍如何实现 LED 闪烁。 我们使用的是 HAL 库。 文章目录前言一、STM32CubeMX配置:二、LED 原理图&#xff1a;三、LED闪烁 讲解&#xff1a;1. HAL_GPIO_WritePin 函数&#xff1a;用于操作 *GPIO* 电平。2.HAL_Delay函数&#xff1a;作为毫秒级延迟的函…

【消息队列】Centos7 虚拟机安装 RocketMQ 及启动控制台

文章目录前言目的注意点官网虚拟机1. 环境变量2. 安装并启动rocketmq3. 安装docker4. docker拉取并运行rocketmq-dashboard5. 关闭防火墙6. 宿主机查看控制台7. 关闭虚拟机的进程后记前言 目的 模拟在服务器上运行RocketMQ&#xff0c;并且有控制台的能力。以后本地window可以…

【自学C++】C++变量作用域

C变量作用域 C变量作用域教程 C 中的一个 变量 或 常量 在程序中都有一定的作用范围&#xff0c;我们称之为作用域。C 变量作用域可分为局部作用域和全局作用域。 C局部变量 在 函数 内部声明/定义的变量叫局部变量&#xff0c;局部变量的作用域仅限于函数内部。同时&#…

Linux应用编程---8.共享内存

Linux应用编程—8.共享内存 ​ 共享内存是进程之间通讯的方式。大概原理是先申请一块共享内存&#xff0c;然后通过“映射”&#xff0c;映射到进程中。进程中读写这块被映射过来的内存&#xff0c;共享内存也会随之改变&#xff0c;同理其它进程也能做相同的操作。所以&#…

dubbo源码实践-protocol层例子

1 概述本文提供了基于protocol层的一个客户端、服务端代码例子。从dubbo 2.7的架构图上可以看到protocol层上在Remoting层之上的&#xff0c;个人理解Protocol层不在讨论客户端和服务端的概念了&#xff0c;开始讨论服务提供者和服务消费者的概念了。参考上一篇dubbo源码实践-p…

基于ngxin一个http模板

1.初始化 static char * ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r);static ngx_command_t ngx_http_mytest_commands[] {{ngx_string("mytest"),NGX_HTTP_MAIN_CONF | N…

分布式基础篇2——分布式组件(谷粒商城)

一、SpringCloud Alibaba1、简介2、为什么使用3、版本选择4、依赖选择二、SpringCloud Alibaba 组件1、Nacos作为注册中心2、OpenFeign3、Nacos作为配置中心namespaceData IDGroup同时加载多个配置文件三、Spring Cloud1、GateWay简介三大核心部分网关的使用视频来源: 【Java项…

爬虫学习+实战

爬虫 概念&#xff1a; 网络爬虫&#xff1a;就是模拟客户端发送请求&#xff0c;获取响应数据&#xff0c;一种按照一定的规则&#xff0c;自动地抓取万维网上的信息的程序或者脚本 爬虫分类: 通用爬虫&#xff1a;抓取系统中重要的组成部分。抓取的是一整张页面数据聚焦爬…

I2C总线驱动

一. I2C背景知识 SOC芯片平台的外设分为&#xff1a; 一级外设&#xff1a;外设控制器集成在SOC芯片内部二级外设&#xff1a;外设控制器由另一块芯片负责&#xff0c;通过一些通讯总线与SOC芯片相连 Inter-Integrated Circuit&#xff1a; 字面意思是用于“集成电路之间”的…

SELECT COUNT(*) 会造成全表扫描?回去等通知吧

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/T…

CPU_并行(多线程)不同高性能旋转图片

并行(多线程)不同高性能旋转图片 代码 ImageStuff.h struct ImgProp {int Hpixels;int Vpixels;unsigned char HeaderInfo[54];unsigned long int Hbytes; };struct Pixel {unsigned char R;unsigned char G;unsigned char B; };unsigned char** CreateBlankBMP(); unsigned…

Java中>>,>>=,<<,<<=运算符

今天在刷LeetCode的时候遇到了一个运算符<<&#xff0c;对这个运算符的意思有点模糊&#xff0c;然后便开始面向百度学习&#xff0c;但是发现&#xff0c;很多篇帖子表达的意思太文章化&#xff0c;不够通俗易懂&#xff0c;于是打算写下这篇帖子&#xff0c;让大家能够…

工作笔记——微信支付开发相关知识整理

在最近的工作中&#xff0c;引入了微信小程序支付&#xff0c;在开发过程中积累和整理了一些技术知识&#xff0c;现将其整理如下 目录 一、概念认识 &#xff08;一&#xff09;术语介绍 &#xff08;二&#xff09;名词解释 &#xff08;四&#xff09;对接微信支付接口规…

Win10安卓子系统安装教程

Win10安卓子系统安装教程必要安装文件下载和安装子系统安装方法方法一&#xff1a;安装 WSA PacMan方法二&#xff1a;安装 APK安装程序必要安装文件下载和安装 win10安卓子系统和win11子系统的安装一样&#xff0c;都必须要安装适用于 Android ™的 Windows 子系统设置的软件…

Java设计模式中行为型模式是什么/模板方式又是什么,编程怎么运用

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 6.行为型模式 6.1 概述 6.1.1 特点 用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎么相互协作共同完成单个对象都无法单独完成任务涉…

分布式基础篇3——前端开发基础知识(谷粒商城)

前端技术对比一、ES61、简介2、什么是 JavaScript3、ES6新特性3.1 let3.2 const3.3 解构表达式3.4 字符串扩展3.5 函数优化3.6 对象优化3.7 map 和 reduce3.8 Promise3.9 模块化二、Vue1、MVVM 思想2、Vue 简介3、Vue 入门案例4、Vue 指令插值表达式v-text、v-htmlv-bindv-mode…

【CANN训练营第三季】基于昇腾PyTorch框架的模型训练调优

文章目录性能分析工具PyTorch Profiling性能分析工具CANN Profiling结业考核1、使用Pytorch实现LeNet网络的minist手写数字识别。2、采用课程中学习到的手工或者自动迁移方式&#xff0c;将上述脚本迁移到昇腾npu上&#xff0c;单机单卡&#xff0c;提供迁移脚本&#xff0c;突…

YOLOv5视觉AI库安装

打开YOLOv5开源仓库: https://github.com/ultralytics/yolov5/blob/master/README.zh-CN.md下载源码:安装 : pip install -r requirements.txt完成安装目标检测推理可通过PyTorch Hub加载YOLOv5检测模型检测图像并返回数据帧使用YOLOv5要先安装opencv-python和pandas库安装open…