本文介绍SpringBoot相关内容。和【跨考菌】一起加油吧~
如果你有收获,记得帮博主一键三连哦😊
1 结构体
1.1 golang面向对象编程说明
- Golang 也支持面向对象编程(OOP), 但是和传统的面向对象编程有区别, 并不是纯粹的面向对
象语言。 所以我们说 Golang 支持面向对象编程特性是比较准确的。 - Golang 没有类(class), Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位, 你可以理解 Golang 是基于 struct 来实现 OOP 特性的。
- Golang 面向对象编程非常简洁, 去掉了传统 OOP 语言的继承、 方法重载、 构造函数和析构函
数、 隐藏的 this 指针等等 - Golang 仍然有面向对象编程的继承, 封装和多态的特性, 只是实现的方式和其它 OOP 语言不
一样, 比如继承 : Golang 没有 extends 关键字, 继承是通过匿名字段来实现。 - Golang 面向对象(OOP)很优雅, OOP 本身就是语言类型系统(type system)的一部分, 通过接口
(interface)关联, 耦合性低, 也非常灵活。 后面同学们会充分体会到这个特点。 也就是说在 Golang 中面向接口编程是非常重要的特性。
1.2 结构体和结构体变量的关系图
说明:
- 将一类事物的特性提取出来(比如猫类), 形成一个新的数据类型, 就是一个结构体。
- 通过这个结构体, 我们可以创建多个变量(实例/对象)
- 事物可以猫类, 也可以是 Person , Fish 或是某个工具类。 。 。
1.3 简单案例
1.4 结构体和结构体变量的区别和联系
- 结构体是自定义的数据类型, 代表一类事物.
- 结构体变量(实例)是具体的, 实际的, 代表一个具体变量
1.5 结构体内存布局
1.6 如何声明结构体
- 基本语法
type 结构体名称 struct {
field1 type
field2 type
}
- 举例:
type Student struct {
Name string //字段
Age int //字段
Score float32
}
1.7 字段/属性
1.7.1 基本介绍
- 从概念或叫法上看: 结构体字段 = 属性 = field (即授课中, 统一叫字段)
- 字段是结构体的一个组成部分, 一般是基本数据类型、 数组,也可是引用类型。 比如我们前面定
义猫结构体 的 Name string 就是属性
注意事项和细节说明
- 字段声明语法同变量, 示例:
字段名 字段类型
- 字段的类型可以为: 基本类型、 数组或引用类型
- 在创建一个结构体变量后, 如果没有给字段赋值, 都对应一个零值(默认值), 规则同前面讲的
一样:
布尔类型是 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.8 创建结构体变量和访问结构体字段
方式 1-直接声明
案例演示: var person Person
方式 2-{}
案例演示: var person Person = Person{}
方式 3-&
案例: var person *Person = new (Person)
方式 4-{}
案例: var person *Person = &Person{}
说明:
- 第 3 种和第 4 种方式返回的是 结构体指针。
- 结构体指针访问字段的标准方式应该是:
(*结构体指针).字段名
, 比如(*person).Name = "tom"
- 但 go 做了一个简化, 也支持 结构体指针.字段名, 比如 person.Name = “tom”。 更加符合程序员
使用的习惯, go 编译器底层 *对 person.Name 做了转化 (person).Name。
1.9 struct类型的内存分配机制
- 先来看一个思考题
- 基本说明
- 结构体在内存中示意图
- 看下面的代码,分析原因
上述代码内存分析:
- 分析下面代码错误原因
1.10 结构体使用细节
- 结构体的所有字段在内存中是连续的
- 结构体是用户单独定义的类型, 和其它类型进行转换时需要有完全相同的字段(名字、 个数和类
型)
- 结构体进行 type 重新定义(相当于取别名), Golang 认为是新的数据类型, 但是相互间可以强转
- struct 的每个字段上, 可以写上一个 tag, 该 tag 可以通过反射机制获取, 常见的使用场景就是序
列化和反序列化。
- 序列化的使用场景:
2 方法
2.1 基本介绍
在某些情况下, 我们要需要声明(定义)方法。 比如 Person 结构体:除了有一些字段外( 年龄, 姓名…),Person 结构体还有一些行为,比如:可以说话、 跑步…,通过学习, 还可以做算术题。 这时就要用方法才能完成。
Golang 中的方法是作用在指定的数据类型上的(即: 和指定的数据类型绑定), 因此自定义类型,都可以有方法, 而不仅仅是 struct。
2.2 方法的声明和调用
语法说明:
- func (a A) test() {} 表示 A 结构体有一方法, 方法名为 test
- (a A) 体现 test 方法是和 A 类型绑定的
举例说明:
总结:
-
test 方法和 Person 类型绑定
-
test 方法只能通过 Person 类型的变量来调用, 而不能直接调用, 也不能使用其它类型变量来调
用
-
func (p Person) test() {}… p 表示哪个 Person 变量调用, 这个 p 就是它的副本, 这点和函数传参非
常相似。 -
p 这个名字, 有程序员指定, 不是固定, 比如修改成 person 也是可以
2.3 方法入门
-
给 Person 结构体添加 speak 方法,输出 xxx 是一个好人
-
给 Person 结构体添加 jisuan 方法,可以计算从 1+…+1000 的结果, 说明方法体内可以函数一样,
进行各种运算
-
给 Person 结构体 jisuan2 方法,该方法可以接收一个数 n, 计算从 1+…+n 的结果
-
给 Person 结构体添加 getSum 方法,可以计算两个数的和, 并返回结果
-
方法的调用
2.4 方法的声明/定义
func (recevier type) methodName(参数列表) (返回值列表){
方法体
return 返回值
}
- 参数列表: 表示方法输入
- recevier type : 表示这个方法和 type 这个类型进行绑定, 或者说该方法作用于 type 类型
- receiver type : type 可以是结构体, 也可以其它的自定义类型
- receiver : 就是 type 类型的一个变量(实例), 比如 : Person 结构体 的一个变量(实例)
- 返回值列表: 表示返回的值, 可以多个
- 方法主体: 表示为了实现某一功能代码块
- return 语句不是必须的
2.5 使用细节
- 结构体类型是值类型, 在方法调用中, 遵守值类型的传递机制, 是值拷贝传递方式
- 如程序员希望在方法中, 修改结构体变量的值, 可以通过结构体指针的方式来处理
- Golang 中的方法作用在指定的数据类型上的(即: 和指定的数据类型绑定), 因此自定义类型,
都可以有方法, 而不仅仅是 struct, 比如 int , float32 等都可以有方法
- 方法的访问范围控制的规则, 和函数一样。 方法名首字母小写, 只能在本包访问, 方法首字母
大写, 可以在本包和其它包访问。 [讲解] - 如果一个类型实现了 String()这个方法, 那么 fmt.Println 默认会调用这个变量的 String()进行输出
2.6 方法和函数的区别
1) 调用方式不一样
函数的调用方式: 函数名(实参列表)
方法的调用方式: 变量.方法名(实参列表)
2) 对于普通函数, 接收者为值类型时, 不能将指针类型的数据直接传递, 反之亦然
3) 对于方法(如 struct 的方法) , 接收者为值类型时, 可以直接用指针类型的变量调用方法, 反
过来同样也可以
总结:
- 不管调用形式如何, 真正决定是值拷贝还是地址拷贝, 看这个方法是和哪个类型绑定.
- *如果是和值类型, 比如 (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 封装的好处
- 隐藏实现细节
- 提可以对数据进行验证, 保证安全合理(Age)
如何体现封装呢?
- 对结构体中的属性进行封装
- 通过方法, 包 实现封装
4.2 封装实现步骤
- 将结构体、 字段(属性)的首字母小写(不能导出了, 其它包不能使用, 类似 private)
- 给结构体所在包提供一个工厂模式的函数, 首字母大写。 类似一个构造函数
- 提供一个首字母大写的 Set 方法(类似其它语言的 public), 用于对属性判断并赋值
func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {
//加入数据验证的业务逻辑
var.字段 = 参数
}
- 提供一个首字母大写的 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
- 创建程序,在 model 包中定义 Account 结构体: 在 main 函数中体会 Golang 的封装性。
- Account 结构体要求具有字段: 账号(长度在 6-10 之间) 、 余额(必须>20)、 密码(必须是六
- 通过 SetXxx 的方法给 Account 的字段赋值。
- 在 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))
}
对上面代码的小结
- Pupil 和 Graduate 两个结构体的字段和方法几乎, 但是我们却写了相同的代码, 代码复用性不
强 - 出现代码冗余, 而且代码不利于维护, 同时也不利于功能的扩展。
- 解决方法-通过继承方式来解决
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))
}
发现,继承给编程带来下面的便利:
- 代码的复用性提高了
- 代码的扩展性和维护性提高了
5.5 继承的深入
-
结构体可以使用嵌套匿名结构体所有的字段和方法, 即: 首字母大写或者小写的字段、 方法,
都可以使用。 【举例说明】
-
匿名结构体字段访问可以简化, 如图
对上面的代码小结
(1) 当我们直接通过 b 访问字段或方法时, 其执行流程如下比如 b.Name
(2) 编译器会先看 b 对应的类型有没有 Name, 如果有, 则直接调用 B 类型的 Name 字段
(3) 如果没有就去看 B 中嵌入的匿名结构体 A 有没有声明 Name 字段, 如果有就调用,如果没有
继续查找…如果都找不到就报错. -
当结构体和匿名结构体有相同的字段或者方法时, 编译器采用就近访问原则访问, 如希望访问
匿名结构体的字段和方法, 可以通过匿名结构体名来区分【举例说明】
-
结构体嵌入两个(或多个)匿名结构体, 如两个匿名结构体有相同的字段和方法(同时结构体本身
没有同名的字段和方法), 在访问时, 就必须明确指定匿名结构体名字, 否则编译报错。 【举例说明】
-
如果一个 struct 嵌套了一个有名结构体, 这种模式就是组合, 如果是组合关系, 那么在访问组合的结构体的字段或方法时, 必须带上结构体的名字
-
嵌套匿名结构体后, 也可以在创建结构体变量(实例)时, 直接指定各个匿名结构体字段的值
5.6 案例剖析
结构体的匿名字段是基本数据类型, 如何访问, 下面代码输出什么
说明
- 如果一个结构体有 int 类型的匿名字段, 就不能第二个。
- 如果需要有多个 int 的字段, 则必须给 int 字段指定名字
5 面向对象编程三大特性-多重继承
- 多重继承说明
如一个 struct 嵌套了多个匿名结构体, 那么该结构体可以直接访问嵌套的匿名结构体的字段和方
法, 从而实现了多重继承。 - 案例演示
通过一个案例来说明多重继承使用
多重继承细节说明
- 如嵌入的匿名结构体有相同的字段名或者方法名, 则在访问时, 需要通过匿名结构体类型名来
区分。 【案例演示】
- 为了保证代码的简洁性, 建议大家尽量不使用多重继承
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 接口基本语法
小结说明:
- 接口里的所有方法都没有方法体, 即接口的方法都是没有实现的方法。 接口体现了程序设计的
多态和高内聚低偶合的思想。 - Golang 中的接口, 不需要显式的实现。 只要一个变量, 含有接口类型中的所有方法, 那么这个
变量就实现这个接口。 因此, Golang 中没有 implement 这样的关键字
6.3 注意事项和细节
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
- 接口中所有的方法都没有方法体,即都是没有实现的方法。
- 在 Golang 中, 一个自定义类型需要将某个接口的所有方法都实现, 我们说这个自定义类型实现
了该接口。 - 一个自定义类型只有实现了某个接口, 才能将该自定义类型的实例(变量)赋给接口类型
- 只要是自定义数据类型, 就可以实现接口, 不仅仅是结构体类型。
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()
}
- interface 类型默认是一个指针(引用类型), 如果没有对 interface 初始化就使用, 那么会输出 nil
- 空接口 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()
}
代码说明:
- 当 A 结构体继承了 B 结构体, 那么 A 结构就自动的继承了 B 结构体的字段和方法, 并且可以直
接使用 - 当 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)
}