【golang学习总结】11 golang面向对象编程

本文介绍SpringBoot相关内容。和【跨考菌】一起加油吧~

在这里插入图片描述

如果你有收获,记得帮博主一键三连哦😊


1 结构体

1.1 golang面向对象编程说明

  1. Golang 也支持面向对象编程(OOP), 但是和传统的面向对象编程有区别, 并不是纯粹的面向对
    象语言。 所以我们说 Golang 支持面向对象编程特性是比较准确的。
  2. Golang 没有类(class), Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位, 你可以理解 Golang 是基于 struct 来实现 OOP 特性的。
  3. Golang 面向对象编程非常简洁, 去掉了传统 OOP 语言的继承、 方法重载、 构造函数和析构函
    数、 隐藏的 this 指针等等
  4. Golang 仍然有面向对象编程的继承, 封装和多态的特性, 只是实现的方式和其它 OOP 语言不
    一样, 比如继承 : Golang 没有 extends 关键字, 继承是通过匿名字段来实现。
  5. Golang 面向对象(OOP)很优雅, OOP 本身就是语言类型系统(type system)的一部分, 通过接口
    (interface)关联, 耦合性低, 也非常灵活。 后面同学们会充分体会到这个特点。 也就是说在 Golang 中面向接口编程是非常重要的特性。

1.2 结构体和结构体变量的关系图

在这里插入图片描述
说明:

  1. 将一类事物的特性提取出来(比如猫类), 形成一个新的数据类型, 就是一个结构体。
  2. 通过这个结构体, 我们可以创建多个变量(实例/对象)
  3. 事物可以猫类, 也可以是 Person , Fish 或是某个工具类。 。 。

1.3 简单案例

在这里插入图片描述

1.4 结构体和结构体变量的区别和联系

  1. 结构体是自定义的数据类型, 代表一类事物.
  2. 结构体变量(实例)是具体的, 实际的, 代表一个具体变量

1.5 结构体内存布局

在这里插入图片描述

1.6 如何声明结构体

  • 基本语法
type 结构体名称 struct {
	field1 type
	field2 type
}
  • 举例:
type Student struct {
	Name string //字段
	Age int //字段
	Score float32
}

1.7 字段/属性

1.7.1 基本介绍

  1. 从概念或叫法上看: 结构体字段 = 属性 = field (即授课中, 统一叫字段)
  2. 字段是结构体的一个组成部分, 一般是基本数据类型、 数组,也可是引用类型。 比如我们前面定
    义猫结构体 的 Name string 就是属性

注意事项和细节说明

  1. 字段声明语法同变量, 示例: 字段名 字段类型
  2. 字段的类型可以为: 基本类型、 数组或引用类型
  3. 在创建一个结构体变量后, 如果没有给字段赋值, 都对应一个零值(默认值), 规则同前面讲的
    一样:
    布尔类型是 false , 数值是 0 , 字符串是 “”。
    数组类型的默认值和它的元素类型相关, 比如 score [3]int 则为[0, 0, 0]
    指针, slice, 和 map 的零值都是 nil , 即还没有分配空间。
    案例演示:
package main
import (
	"fmt"
)

//如果结构体的字段类型是: 指针,slice,和map的零值都是 nil ,即还没有分配空间
//如果需要使用这样的字段,需要先make,才能使用.

type Person struct{
	Name string
	Age int
	Scores [5]float64
	ptr *int //指针 
	slice []int //切片
	map1 map[string]string //map
}

type Monster struct{
	Name string
	Age int
}


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

	//使用map, 一定要先make
	p1.map1 = make(map[string]string)
	p1.map1["key1"] = "tom~" 
	fmt.Println(p1)
  1. 不同结构体变量的字段是独立, 互不影响, 一个结构体变量字段的更改, 不影响另外一个, 结构体是值类型!!!
    案例:
    在这里插入图片描述
    在这里插入图片描述

1.8 创建结构体变量和访问结构体字段

方式 1-直接声明
案例演示: var person Person

方式 2-{}
案例演示: var person Person = Person{}
在这里插入图片描述

方式 3-&
案例: var person *Person = new (Person)
在这里插入图片描述
方式 4-{}
案例: var person *Person = &Person{}
在这里插入图片描述
说明:

  1. 第 3 种和第 4 种方式返回的是 结构体指针。
  2. 结构体指针访问字段的标准方式应该是: (*结构体指针).字段名 , 比如 (*person).Name = "tom"
  3. 但 go 做了一个简化, 也支持 结构体指针.字段名, 比如 person.Name = “tom”。 更加符合程序员
    使用的习惯, go 编译器底层 *对 person.Name 做了转化 (person).Name。

1.9 struct类型的内存分配机制

  • 先来看一个思考题
    在这里插入图片描述
  • 基本说明
    在这里插入图片描述
  • 结构体在内存中示意图
    在这里插入图片描述
  • 看下面的代码,分析原因
    在这里插入图片描述
    在这里插入图片描述
    上述代码内存分析:
    在这里插入图片描述
  • 分析下面代码错误原因
    在这里插入图片描述

1.10 结构体使用细节

  1. 结构体的所有字段在内存中是连续的
  2. 结构体是用户单独定义的类型, 和其它类型进行转换时需要有完全相同的字段(名字、 个数和类
    型)
    在这里插入图片描述
  3. 结构体进行 type 重新定义(相当于取别名), Golang 认为是新的数据类型, 但是相互间可以强转
    在这里插入图片描述
  4. struct 的每个字段上, 可以写上一个 tag, 该 tag 可以通过反射机制获取, 常见的使用场景就是序
    列化和反序列化。
  • 序列化的使用场景:

在这里插入图片描述
在这里插入图片描述

2 方法

2.1 基本介绍

在某些情况下, 我们要需要声明(定义)方法比如 Person 结构体:除了有一些字段外( 年龄, 姓名…),Person 结构体还有一些行为,比如:可以说话、 跑步…,通过学习, 还可以做算术题。 这时就要用方法才能完成。
Golang 中的方法是作用在指定的数据类型上的(即: 和指定的数据类型绑定), 因此自定义类型,都可以有方法, 而不仅仅是 struct。

2.2 方法的声明和调用

在这里插入图片描述
语法说明:

  1. func (a A) test() {} 表示 A 结构体有一方法, 方法名为 test
  2. (a A) 体现 test 方法是和 A 类型绑定的

举例说明:
在这里插入图片描述
总结:

  1. test 方法和 Person 类型绑定

  2. test 方法只能通过 Person 类型的变量来调用, 而不能直接调用, 也不能使用其它类型变量来调

    在这里插入图片描述

  3. func (p Person) test() {}… p 表示哪个 Person 变量调用, 这个 p 就是它的副本, 这点和函数传参非
    常相似。

  4. p 这个名字, 有程序员指定, 不是固定, 比如修改成 person 也是可以
    在这里插入图片描述

2.3 方法入门

  1. 给 Person 结构体添加 speak 方法,输出 xxx 是一个好人
    在这里插入图片描述

  2. 给 Person 结构体添加 jisuan 方法,可以计算从 1+…+1000 的结果, 说明方法体内可以函数一样,
    进行各种运算
    在这里插入图片描述

  3. 给 Person 结构体 jisuan2 方法,该方法可以接收一个数 n, 计算从 1+…+n 的结果
    在这里插入图片描述

  4. 给 Person 结构体添加 getSum 方法,可以计算两个数的和, 并返回结果
    在这里插入图片描述

  5. 方法的调用
    在这里插入图片描述

2.4 方法的声明/定义

func (recevier type) methodName(参数列表) (返回值列表){
	方法体
	return 返回值
}
  1. 参数列表: 表示方法输入
  2. recevier type : 表示这个方法和 type 这个类型进行绑定, 或者说该方法作用于 type 类型
  3. receiver type : type 可以是结构体, 也可以其它的自定义类型
  4. receiver : 就是 type 类型的一个变量(实例), 比如 : Person 结构体 的一个变量(实例)
  5. 返回值列表: 表示返回的值, 可以多个
  6. 方法主体: 表示为了实现某一功能代码块
  7. return 语句不是必须的

2.5 使用细节

  1. 结构体类型是值类型, 在方法调用中, 遵守值类型的传递机制, 是值拷贝传递方式
  2. 如程序员希望在方法中, 修改结构体变量的值, 可以通过结构体指针的方式来处理
    在这里插入图片描述
  3. Golang 中的方法作用在指定的数据类型上的(即: 和指定的数据类型绑定), 因此自定义类型,
    都可以有方法, 而不仅仅是 struct, 比如 int , float32 等都可以有方法
    在这里插入图片描述
  4. 方法的访问范围控制的规则, 和函数一样。 方法名首字母小写, 只能在本包访问, 方法首字母
    大写, 可以在本包和其它包访问。 [讲解]
  5. 如果一个类型实现了 String()这个方法, 那么 fmt.Println 默认会调用这个变量的 String()进行输出
    在这里插入图片描述

2.6 方法和函数的区别

1) 调用方式不一样
函数的调用方式: 函数名(实参列表)
方法的调用方式: 变量.方法名(实参列表)
2) 对于普通函数, 接收者为值类型时, 不能将指针类型的数据直接传递, 反之亦然
在这里插入图片描述
3) 对于方法(如 struct 的方法) , 接收者为值类型时, 可以直接用指针类型的变量调用方法, 反
过来同样也可以
在这里插入图片描述
在这里插入图片描述
总结:

  1. 不管调用形式如何, 真正决定是值拷贝还是地址拷贝, 看这个方法是和哪个类型绑定.
  2. *如果是和值类型, 比如 (p Person) , 则是值拷贝, 如果和指针类型, 比如是 (p Person) 则
    是地址拷贝。

3 工厂模式

3.1 说明

Golang 的结构体没有构造函数, 通常可以使用工厂模式来解决这个问题。

3.2 先看一个需求

在这里插入图片描述

3.3 工厂模式如何解决这个问题呢?

使用工厂模式实现跨包创建结构体实例(变量)的案例:
如果 model 包的 结构体变量首字母大写, 引入后, 直接使用, 没有问题
在这里插入图片描述
如果 model 包的 结构体变量首字母小写, 引入后, 不能直接使用, 可以工厂模式解决, 看代码:
student.go
在这里插入图片描述
main.go
在这里插入图片描述

3.4 思考

如果 model 包的 student 的结构体的字段 Score 改成 score, 我们还能正常访问
吗? 又应该如何解决这个问题呢?
在这里插入图片描述
在这里插入图片描述

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

4.1 封装的好处

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

如何体现封装呢?

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

4.2 封装实现步骤

  1. 将结构体、 字段(属性)的首字母小写(不能导出了, 其它包不能使用, 类似 private)
  2. 给结构体所在包提供一个工厂模式的函数, 首字母大写。 类似一个构造函数
  3. 提供一个首字母大写的 Set 方法(类似其它语言的 public), 用于对属性判断并赋值
func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {
	//加入数据验证的业务逻辑
	var.字段 = 参数
} 
  1. 提供一个首字母大写的 Get 方法(类似其它语言的 public), 用于获取属性的值
func (var 结构体类型名) GetXxx() {
	return var.age;
}

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

4.3 简单案例

请大家看一个程序(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,
	}
}

//为了访问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/main.go

package main
import (
	"fmt"
	"test/chapter11/encapsulate/model"
)

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())
	
}

4.4 案例2

  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"
	"test/chapter11/encapexercise/model"
)

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

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

5.1 为什么需要继承呢?

先看一个例子:
看个学生考试系统的程序 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
}

//给 *Student 增加一个方法,那么 Pupil 和 Graduate都可以使用该方法
func (stu *Student) GetSum(n1 int, n2 int) int {
	return n1 + n2
}

//小学生
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()
	fmt.Println("res=", pupil.Student.GetSum(1, 2))


	graduate := &Graduate{}
	graduate.Student.Name = "mary~"
	graduate.Student.Age = 28
	graduate.testing() 
	graduate.Student.SetScore(90)
	graduate.Student.ShowInfo()
	fmt.Println("res=", graduate.Student.GetSum(10, 20))
}

对上面代码的小结

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

5.2 继承的示意图

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

5.3 嵌套匿名构造体的基本语法

在这里插入图片描述

5.4 使用案例

对上述冗余的代码利用继承来改进:

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
}

//给 *Student 增加一个方法,那么 Pupil 和 Graduate都可以使用该方法
func (stu *Student) GetSum(n1 int, n2 int) int {
	return n1 + n2
}

//小学生
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()
	fmt.Println("res=", pupil.Student.GetSum(1, 2))


	graduate := &Graduate{}
	graduate.Student.Name = "mary~"
	graduate.Student.Age = 28
	graduate.testing() 
	graduate.Student.SetScore(90)
	graduate.Student.ShowInfo()
	fmt.Println("res=", graduate.Student.GetSum(10, 20))
}

发现,继承给编程带来下面的便利:

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

5.5 继承的深入

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

  2. 匿名结构体字段访问可以简化, 如图
    在这里插入图片描述
    对上面的代码小结
    (1) 当我们直接通过 b 访问字段或方法时, 其执行流程如下比如 b.Name
    (2) 编译器会先看 b 对应的类型有没有 Name, 如果有, 则直接调用 B 类型的 Name 字段
    (3) 如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段, 如果有就调用,如果没有
    继续查找…如果都找不到就报错.

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

    在这里插入图片描述

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

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

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

在这里插入图片描述
在这里插入图片描述

5.6 案例剖析

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

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

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

  • 多重继承说明
    如一个 struct 嵌套了多个匿名结构体, 那么该结构体可以直接访问嵌套的匿名结构体的字段和方
    法, 从而实现了多重继承。
  • 案例演示
    通过一个案例来说明多重继承使用
    在这里插入图片描述
    多重继承细节说明
  1. 如嵌入的匿名结构体有相同的字段名或者方法名, 则在访问时, 需要通过匿名结构体类型名来
    区分。 【案例演示】
    在这里插入图片描述
  2. 为了保证代码的简洁性, 建议大家尽量不使用多重继承

6 接口

在 Golang 中 多态特性主要是通过接口来体现的。

6.1 接口入门案例

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)
}

6.2 接口基本语法

在这里插入图片描述

小结说明:

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

6.3 注意事项和细节

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

在这里插入图片描述
6) 一个自定义类型可以实现多个接口
在这里插入图片描述
7) Golang 接口中不能有任何变量
在这里插入图片描述
8) 一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口), 这时如果要实现 A 接口, 也必须将 B,C 接口的方法也全部实现。

package main
import (
	"fmt"
)

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

}
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)

6.4 案例

在这里插入图片描述

6.5 案例:实现对 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
}

7 实现接口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()

}

代码说明:

  1. 当 A 结构体继承了 B 结构体, 那么 A 结构就自动的继承了 B 结构体的字段和方法, 并且可以直
    接使用
  2. 当 A 结构体需要扩展功能, 同时不希望去破坏继承关系, 则可以去实现某个接口即可, 因此我
    们可以认为: 实现接口是对继承机制的补充.
    在这里插入图片描述
  • 接口和继承解决的解决的问题不同
    继承的价值主要在于: 解决代码的复用性和可维护性。
    接口的价值主要在于: 设计, 设计好各种规范(方法), 让其它自定义类型去实现这些方法。
  • 接口比继承更加灵活 Person Student BirdAble LittleMonkey
    接口比继承更加灵活, 继承是满足 is - a 的关系, 而接口只需满足 like - a 的关系。
  • 接口在一定程度上实现代码解耦

8 类型断言

在这里插入图片描述

8.1 基本介绍

类型断言, 由于接口是一般类型, 不知道具体类型, 如果要转成具体类型, 就需要使用类型断言,具体的如下:
在这里插入图片描述

  • 对上面代码的说明:
    在进行类型断言时, 如果类型不匹配, 就会报 panic, 因此进行类型断言时, 要确保原来的空接口指向的就是断言的类型.
  • 如何在进行断言时, 带上检测机制, 如果成功就 ok,否则也不要报 panic
    在这里插入图片描述

8.2 类型断言案例1

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

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

在这里插入图片描述

8.2 类型断言案例2

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

package main
import (
	"fmt"
)


//定义Student类型
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个参数是 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)
			case Student :
				fmt.Printf("第%v个参数是 Student 类型,值是%v\n", index, x)
			case *Student :
				fmt.Printf("第%v个参数是 *Student 类型,值是%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

	stu1 := Student{}
	stu2 := &Student{}

	TypeJudge(n1, n2, n3, name, address, n4, stu1, stu2)


}

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang 是一门支持面向接口编程的语言,这意味着在 Golang 中,我们可以通过定义接口来实现多态性。具体来说,我们可以定义一个接口类型,然后让多个不同的类型实现该接口,从而使它们可以被视为同一类型,从而实现多态性。 在 Golang 中,接口是一组方法的集合,这些方法定义了该接口所代表的对象应该具有的行为。任何类型只要实现了该接口中定义的所有方法,就可以被视为该接口类型的实例。 下面是一个简单的例子,展示了如何定义一个接口和实现该接口: ```go package main import "fmt" // 定义一个接口 type Animal interface { Speak() string } // 定义一个结构体类型 type Dog struct { Name string } // 实现 Animal 接口中的 Speak 方法 func (d Dog) Speak() string { return "Woof!" } func main() { // 创建一个 Dog 类型的实例 d := Dog{Name: "Fido"} // 将该实例赋值给 Animal 类型的变量 var a Animal = d // 调用 Animal 接口中的 Speak 方法 fmt.Println(a.Speak()) // 输出: Woof! } ``` 在上面的例子中,我们定义了一个 Animal 接口,它只有一个方法:Speak。然后我们定义了一个 Dog 结构体类型,并实现了 Animal 接口中的 Speak 方法。最后,我们创建了一个 Dog 类型的实例,并将其赋值给 Animal 类型的变量。由于 Dog 类型实现了 Animal 接口中的所有方法,所以它可以被视为 Animal 类型的实例,从而实现了多态性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值