Go语言学习篇03
Golang 面向对象
1)利用变量、数组、map集合管理养猫问题,数据类型单一,不利于数据的管理和维护
2)因为猫的名字、年龄、颜色,都是属于一只猫的,属性类型不同
3)如果我们希望对猫的属性(名字、年龄、颜色…)进行操作(绑定方法),也不好处理
4)于是使用结构体
技术来管理
什么是面向对象编程?
1)面向对象编程 简称 OOP(Object Oriented Programming)
2)Golang没有类(class),Go语言的结构体(struct)与其它编程语言的类(class)有同等的地位
3)Golang去掉了传统的OOP语言的方法重载、构造函数、析构函数、隐藏的this指针等等
4)面向接口编程—>把接口用到了极致
5)Golang仍有面向对象编程的继承、多态和封装的特性
结构体的使用
1)结构体又称实例/对象
2)结构体对数据进行统一管理
入门
package main
import "fmt"
//这是结构体
type Cat struct {
Name string
Age int
Color string
Hobby string
}
func main() {
//cat01是结构体变量(实例)是具体的
var cat01 Cat
cat01.Name = "小白"
cat01.Age = 3
cat01.Color = "白色"
cat01.Hobby = "吃鱼"
fmt.Println(cat01)
}
结果
{小白 3 白色 吃鱼}
结论
1)结构体是自定义的数据类型,代表一类事物
2)cat01是结构体变量(实例)是具体的,实际的,代表一个具体变量
结构体的内存图
结构体的声明
type 结构体名称 struct {
field01 type
field02 type
}
结构体变量的4种创建方法
代码
package main
import (
"chapter09/object01"
"fmt"
)
func main() {
//方式1
var cat1 = object01.Cat{Name: "小白", Age: 8, Color: "白色", Hobby: "钓鱼"}
fmt.Println("cat1=", cat1)
//方式2
var cat2 object01.Cat
cat2.Name = "小黑"
cat2.Age = 10
cat2.Color = "黑色"
cat2.Hobby = "捉老鼠"
fmt.Println("cat2=", cat2)
//方式3.1---指针法
var cat3 *object01.Cat= new(object01.Cat)
(*cat3).Name = "Tom"
(*cat3).Age = 7
(*cat3).Color = "blue"
(*cat3).Hobby = "戏耍老鼠"
fmt.Println("cat3=", *cat3)
//方式3.2
//Go语言开发者 为了程序员方便 底层会对 cat4.Name = "Tom" 进行处理
//会给 cat4 加上取值运算符 (*cat4).Name = "Tom"
var cat4 *object01.Cat= new(object01.Cat)
cat4.Name = "Jerry"
cat4.Age = 6
cat4.Color = "灰色"
cat4.Hobby = "偷东西"
fmt.Println("cat4=",*cat4)
//方式4.1
var cat5 *object01.Cat = &object01.Cat{Name: "二哈", Age: 12, Color: "黑白色", Hobby: "犯傻"}
fmt.Println("cat5=", *cat5)
//方式4.2
var cat6 *object01.Cat = &object01.Cat{}
//两种赋值方式是等价的
(*cat6).Name = "name"
cat6.Name = "卡卡罗特"
(*cat6).Age = 28
cat6.Color = "金黄色"
(*cat6).Hobby = "打架斗殴"
fmt.Println("cat6=", *cat6)
}
结果
cat1= {小白 8 白色 钓鱼}
cat2= {小黑 10 黑色 捉老鼠}
cat3= {Tom 7 blue 戏耍老鼠}
cat4= {Jerry 6 灰色 偷东西}
cat5= {二哈 12 黑白色 犯傻}
cat6= {卡卡罗特 28 金黄色 打架斗殴}
结论
1)第3、4种方式返回的是 结构体指针
2)结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,
比如:(*Person).Name = “Carter”
3)但Go做了简化,也支持 结构体指针.字段名
比如:Person.Name = “Carter”,更加符合程序员使用习惯,Go编译器底层对 Person.Name 做了转化(*Person).Name
struct类型的内存分配机制
代码
package main
import "fmt"
type Dog struct {
Name string
Age int
Sex string
}
func main() {
var dog01 Dog = Dog{Name: "二哈", Age: 18, Sex: "公"}
//内存中:默认进行值拷贝
var dog02 Dog = dog01
dog02.Name = "金毛"
fmt.Println("dog01=", dog01)
fmt.Println("dog02=", dog02)
//指针运算
var dog03 *Dog = &Dog{Name: "二哈", Age: 18, Sex: "公"}
var dog04 = dog03
dog04.Name = "金毛"
fmt.Println("dog03=", *dog03)
fmt.Println("dog04=", *dog04)
var dog05 = new(Dog)
dog05.Name = "大虾"
(*dog05).Sex = "母"
var dog06 *Dog = dog05
dog06.Age = 6
fmt.Println("dog05=", *dog05)
fmt.Println("dog06=", *dog06)
}
结果
//原始数据
dog01= {二哈 18 公}
dog02= {金毛 18 公}
dog03= {金毛 18 公}
dog04= {金毛 18 公}
dog05= {大虾 6 母}
dog06= {大虾 6 母}
内存分布
问题
fmt.Println(*Person.Name)//有问题,后缀>单目
括号别单带,算术要移位;
关系看位置,逻辑是其次;
赋值有多少,逗号说了算!
结构体的注意事项和使用细节
1)结构体的所有字段在内存中是**连续的
2)结构体
使用户单独定义的类型,和其它类型进行转换时需要有完全
相同的字段(名字、个数、类型)
3)struct每个字段上,可以写一个标签tag,tag可以通过反射机制获取,常见的使用场景:序列化
、反序列化
结构体互转
package main
import (
"fmt"
)
type Dog struct {
Name string
Age int
Sex string
}
type Cat struct {
Name string
Age int
Sex string
}
type Fish Cat
func main() {
var dog01 Dog = Dog{Name: "二哈", Age: 18, Sex: "公"}
var cat01 Cat
var fish01 Fish
cat01 = Cat(dog01)
fish01 = Fish(cat01)
fmt.Println("dog01=", dog01)
fmt.Println("cat01=", cat01)
fmt.Println("fish01=", fish01)
}
代码
dog01= {二哈 18 公}
cat01= {二哈 18 公}
fish01= {二哈 18 公}
结构体重tag的妙用
1)tag可以给结构体变量 序列化、反序列化
2)结构体中的字段首字母大写
,可以被别的包使用
3)Go服务器传送字符串data给浏览器
4)浏览器接收的data就是结构体变量,但是其中的字段首字母大写
5)通过tag标签 进行 序列化的同时解决首字母大写问题
解决方案一:
将Cat结构体字段小写化???
json . Marshal 的包就无法使用 Cat ,就会返回空字符串
解决方案二
使用tag标签解决,通过反射机制获取
代码
package main
import (
"encoding/json"
"fmt"
)
//使用tag标签
type Cat struct {
Name string `json:"name"`
Sex string `json:"sex"`
Age int `json:"age"`
}
func main() {
var cat Cat = Cat{Name: "苏妲己", Sex: "女", Age: 1000}
//结构体序列化---Marshal
bytes, err := json.Marshal(cat)
if err != nil {
fmt.Println("Json处理错误,", err)
}
fmt.Println("序列化后的字符串:", string(bytes))
}
结果
序列化后的字符串: {"name":"苏妲己","sex":"女","age":1000}
Golang方法篇
方法介绍
Golang中方法是:作用在指定数据类型上的(即:和指定的数据类型绑定
),因此自定义类型,都可以有方法,而不仅仅是 struct
函数和方法有何区别?
-
函数:
- 大家都可以使用
-
方法:
- 指定的数据类型才能使用,和指定类型绑定的
代码
package main
import "fmt"
type Cat struct {
Name string
Sex string
}
//Cat数据类型独有的方法---只能被Cat类型的变量调用
func (cat Cat) print() {
fmt.Println("cat=", cat)
}
func main() {
var cat Cat = Cat{"Carter", "母"}
//调用方法
cat.print()
}
结果
cat= {Carter 母}
结论
1)print 方法和 Cat类型绑定
2)print 方法只能通过Cat 类型变量来调用,而不能直接调用,也不能使用其它的类型变量来调用
方法入门案例
代码
package main
import "fmt"
type Cat struct {
Name string
Sex string
}
//Cat数据类型独有的方法
func (cat Cat) speak() {
fmt.Printf("%v是一只好猫\n", cat.Name)
}
func (cat Cat) Calculation(n int) int{
sum := (1 + n) * n / 2
fmt.Printf("1+...+%v的和,sum=%v\n", n, sum)
return sum
}
func main() {
var cat Cat = Cat{"星猫", "公"}
//调用方法
cat.speak()
sum := cat.Calculation(100)
fmt.Println(sum)
}
结果
星猫是一只好猫
1+...+100的和,sum=5050
5050
内存分析
结论
1)在通过一个变量去调用方法时,其调用机制和函数一样
2)不一样的地方是,变量调用方法时,该变量本身也会作为一个参数传递到方法中(变量是值类型,就进行值拷贝;变量是引用类型,就进行地址拷贝)
3)更多的情况,为了考虑效率,函数一般传指针
方法声明语法
func (recevier type) methodName (参数列表) (返回值列表) {
方法体
return 返回值
}
方法指针入门案例
1)(*cat).Name 等价于 cat.Name,底层Go编译器自动加上 *cat
2) (&cat).speak() 等价于 cat.speak,底层Go编译器自动加上 &cat
代码
package main
import "fmt"
type Cat struct {
Name string
Sex string
}
func (cat *Cat) speak() {
(*cat).Name = "小三"
}
func main() {
var cat Cat = Cat{"Lisa", "母"}
fmt.Println("修改前cat=", cat)
(&cat).speak()
fmt.Println("修改后cat=", cat)
}
结果
修改前cat= {Lisa 母}
修改后cat= {小三 母}
代码
package main
import "fmt"
//int类型
type integer int
func (i *integer) update () {
*i += 1
}
func main() {
var x integer = 10
(&x).update()
fmt.Println("x=", x)
}
结果
x= 11
重写String方法
代码
package main
import "fmt"
type Cat struct {
Name string
Sex string
}
func (cat *Cat) String() string {
str := fmt.Sprintf("Name=%v Sex=%v\n", (*cat).Name, (*cat).Sex)
return str
}
func main() {
var cat Cat = Cat{"Lisa", "母"}
fmt.Println(&cat)
}
结果
Name=Lisa Sex=母
课后练习
代码
package main
import "fmt"
//结构体
type Cat struct {
Name string
Sex string
}
//重写String方法
func (cat *Cat) String() string {
str := fmt.Sprintf("Name=%v Sex=%v\n", (*cat).Name, (*cat).Sex)
return str
}
//九九乘法表方法
func (cat Cat) multiplicationTable(n int) {
for i := 1; i <= n; i++ {
for j := 1; j <= i; j++ {
fmt.Printf(" %vx%v=%v", j, i, i * j)
}
fmt.Println()
}
}
//数组交换方法
func (cat Cat) changeArr (intArr [3][3]int) {
outsideLength := len(intArr)
fmt.Println("交换前")
for i := 0; i < outsideLength; i++ {
insideLength := len(intArr[i])
for j := 0; j < insideLength; j++ {
fmt.Printf("\t%v", intArr[i][j])
}
fmt.Println()
}
intArr02 := intArr
fmt.Println("交换后")
for i := 0; i < outsideLength; i++ {
insideLength := len(intArr02[i])
for j := 0; j < insideLength; j++ {
if i==j {
intArr02[i][j] = intArr[i][j]
} else {
intArr02[i][j] = intArr[j][i]
}
fmt.Printf("\t%v", intArr02[i][j])
}
fmt.Println()
}
}
func main() {
//1、定义结构体变量
var cat Cat = Cat{"Lisa", "母"}
//2、调用重写的String方法
fmt.Println(&cat)
//3、调用打印九九乘法表
cat.multiplicationTable(9)
fmt.Println()
//4、调用数组交换
intArr := [3][3]int{{1, 2, 3},{4, 5, 6},{7, 8, 9}}
cat.changeArr(intArr)
}
结果
Name=Lisa Sex=母
1x1=1
1x2=2 2x2=4
1x3=3 2x3=6 3x3=9
1x4=4 2x4=8 3x4=12 4x4=16
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
交换前
1 2 3
4 5 6
7 8 9
交换后
1 4 7
2 5 8
3 6 9
函数和方法的区别
1)调用方式不同
2)对于普通函数,就收者为值类型时,不能将指针类型的数据直接传递,反之亦然
3)对于方法(如struct的方法),接受者为值类型,可以直接用指针的变量调用方法,反之亦然
4)方法可以指针变量调用、也可以值类型变量调用
5)方法不管调用形式如何,真正决定的是看方法绑定的类型
代码
package main
import "fmt"
type Person struct {
Name string
}
//值传递绑定
func (person Person) findOne () {
//进行Name修改
person.Name = "值类型"
}
//指针绑定
func (person *Person) findMore () {
//进行Name修改
person.Name = "引用类型"
}
func main() {
//值类型变量
var person Person = Person{Name:"Carter"}
person.findOne()
fmt.Println(person.Name)
//引用类型变量
var person02 *Person = &Person{Name: "Carter"}
//仍然是一个值传递,因为findOne方法绑定的变量是值绑定
person02.findOne()
fmt.Println(person02.Name)
//值类型
var person03 Person = Person{Name: "Carter"}
person03.findMore()
fmt.Println(person03.Name)
}
结果
Carter --- (person Person) findOne 未修改
Carter --- (person Person) findOne 未修改
引用类型 --- (person *Person) findMore 修改
面向对象编程实例
步骤
1)声明结构体,确定结构体名
2)编写结构体字段
3)编写结构体方法
案例
1)编写一个Student结构体,包含name、gender、age、id、score字段
2)结构体中声明say方法
Golang工厂模式
说明
Golang的结构体没有构造函数,通常可以使用工厂模式来解决这个问题
工厂模式==构造函数
type student struct {
name string
}
问题
构造体首字母小写,如何使其它包访问
结构体小写
工厂模式
model包
package model
//定义一个结构体,student首字母小写私有化
type student struct {
Name string
Score float64
}
//工厂模式
func NewStudent(name string, score float64) *student {
//返回一个student实例
return &student{
Name: name,
Score: score,
}
}
main包
package main
import (
"factory/model"
"fmt"
)
func main() {
var student01 = model.NewStudent("廖述幸", 99.9)
fmt.Printf("student01=%v type=%T\n", *student01, student01)
}
结果
student01={廖述幸 99.9} type=*model.student
结构体和变量小写
model包
package model
//定义一个结构体,student私有化
type student struct {
name string
score float64
}
//工厂模式
func NewStudent(name string, score float64) *student {
//返回一个student实例
return &student{
name: name,
score: score,
}
}
func (s *student) GetScore () float64 {
return s.score
}
func (s *student) GetName () string {
return s.name
}
main包
package main
import (
"factory/model"
"fmt"
)
func main() {
var student01 = model.NewStudent("廖述幸", 99.9)
fmt.Printf("student01=%v type=%T\n",
*student01, student01)
fmt.Printf("name=%v sorce=%v\n",
student01.GetScore(), student01.GetScore())
}
结果
student01={廖述幸 99.9} type=*model.student
name=99.9 sorce=99.9
面向对象编程思想 - 抽象
抽象介绍
抽象:是一种编程思想,将物质的属性、行为/方法提取出来
面向对象编程三大特征
封装
-
封装介绍
- 封装(encapsulation)就是吧抽象出的
字段和对字段的操作(方法)
封装在一起,数据被保护在内部, - 程序的其它包只能通过被授权的操作(方法),才能对字段进行操作
- 封装(encapsulation)就是吧抽象出的
-
封装的理解和好处
- 隐藏实现细节
- 方法可以对数据进行验证,保证安全合理
Age输入,进行判定是否 > 0 …
-
如何体现封装
- 对结构体中的属性进行封装
- 通过方法,包实现封装
-
封装的步骤
- 将结构体、字段(属性)的首字母小写
- 提供 Set,Get 方法(手写)
案例
1、Person不能随便查看人的年龄、工资等隐私,并对输入的年龄进行合理验证
model包
package model
import (
"fmt"
)
type person struct{
Name string //其它包可以访问
age int //其他包不能访问
salary float64 //其他包不能访问
}
//写一个工厂模式的函数,等价于构造函数
func Person (name string) *person {
return &person{
Name: name,
}
}
//为了访问 age 和 salary,我们编写 get和set方法
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) SetSalary (salary float64) {
if salary >= 300 && salary <= 3000000 {
p.salary = salary
} else {
fmt.Println("薪水输入不正确...")
}
}
//获取薪水的方法
func (p *person) GetSalary () float64 {
return p.salary
}
//等价于重写toString方法
func (p *person) String() string {
return fmt.Sprintf("姓名:%v\n年龄:%v\n薪水:%v\n", p.Name, p.age, p.salary)
}
main包
package main
import (
"encapsulation/model"
"fmt"
)
func main() {
var person = model.Person("廖述幸")
//只能通过方法来给age赋值
person.SetAge(18)
//只能通过方法来给salary赋值
person.SetSalary(3000)
fmt.Println(person)
fmt.Println("年龄=", person.GetAge())
fmt.Println("薪水=", person.GetSalary())
}
结果
姓名:廖述幸
年龄:18
薪水:3000
年龄= 18
薪水= 3000
继承
为什么需要继承(inheritance)?
-
代码复用性强
-
学生考试系统:
- 各种各样的同学的考试进行管理
- 小学生、中学生、高中生、大学生…
传统代码
package main
import (
"fmt"
)
//1、小学生
type Student struct {
Name string
Age int
Score float64
}
//小学生考试
func (stu *Student) testing() {
fmt.Println("小学生正在考试...")
}
func (stu *Student) SetScore (score float64) {
if score >= 0 && score <= 100 {
stu.Score = score
} else {
fmt.Println("学生成绩输入有误...")
}
}
func (stu *Student) String() string {
return fmt.Sprintf("姓名:%v\n年龄:%v\n成绩:%v\n", stu.Name, stu.Age, stu.Score)
}
//2、大学生
type Graduate Student
func (graduate *Graduate) testing() {
fmt.Println("大学生正在考试...")
}
func (graduate *Graduate) SetScore (score float64) {
if score >= 0 && score <= 100 {
graduate.Score = score
} else {
fmt.Println("学生成绩输入有误...")
}
}
func (graduate *Graduate) ShowGraduateInfo() string {
return fmt.Sprintf("姓名:%v\n年龄:%v\n成绩:%v\n", graduate.Name, graduate.Age, graduate.Score)
}
func main() {
//小学生
var student *Student = &Student{
Name: "廖述幸",
Age: 10,
}
student.testing()
student.SetScore(-1)
fmt.Println(student)
//大学生
var graduate = &Graduate{
Name: "廖述幸",
Age: 10,
}
graduate.testing()
graduate.SetScore(89)
fmt.Println(graduate.ShowGraduateInfo())
}
结果
小学生正在考试...
学生成绩输入有误...
姓名:廖述幸
年龄:10
成绩:0
大学生正在考试...
姓名:廖述幸
年龄:10
成绩:89
结论
1)有几种学生就定义几种结构体,定义几种基本相同的方法
2)代码冗余
3)如果加方法,需要加两处
4)不利于代码的维护,功能的延伸
继承的使用
1)嵌入匿名结构体
2)当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的方法和属性
3)基本语法
type Student struct {
Name string
Age int
}
type Graduate struct {
Student //匿名结构体
Major string
}
代码
package main
import (
"fmt"
)
//公有的属性
type Student struct {
Name string
Age int
Score float64
}
//共有的方法
//方法1
func (stu *Student) SetScore (score float64) {
if score >= 0 && score <= 100 {
stu.Score = score
} else {
fmt.Println("学生成绩输入有误...")
}
}
//方法2
func (stu *Student) String() string {
return fmt.Sprintf("姓名:%v\n年龄:%v\n成绩:%v\n", stu.Name, stu.Age, stu.Score)
}
//小学生
type Pupil struct {
Student //匿名结构体
}
func (pupil *Pupil) testing01 () {
fmt.Println("小学生正在考试...")
}
//大学生
type Graduate struct {
Student //匿名结构体
}
func (graduate *Graduate) testing02 () {
fmt.Println("大学生正在考试...")
}
func main() {
//小学生
var pupil *Pupil = &Pupil{
Student{
Name: "Carter",
Age: 12,
},
}
pupil.testing01()
pupil.Student.SetScore(99)
fmt.Println(pupil)
//大学生
var graduate = &Graduate{}
graduate.Student.Name = "廖述幸"
graduate.Student.Age = 18
graduate.testing02()
graduate.Student.SetScore(89)
fmt.Println(graduate)
}
结果
小学生正在考试...
姓名:Carter
年龄:12
成绩:99
大学生正在考试...
姓名:廖述幸
年龄:18
成绩:89
结论
1)代码复用性高
2)代码可维护性强,功能可拓展性高
继承的详细说明
1)结构体可以使用嵌套匿名结构体所有的方法和字段(包括首字母小写的)
2)匿名结构体字段访问可以简化
3)先找本地结构体
有没有字段,然后找嵌入的匿名结构体
中有没有这个字段
4)有名结构体不能简写
5)就近访问原则
共用部分
//公有的属性
type Student struct {
Name string
Age int
Score float64
}
//共有的方法
//方法1
func (stu *Student) SetScore (score float64) {
fmt.Println("执行Student的SetScore方法...")
stu.Score = score
}
//方法2
func (stu *Student) String() string {
return fmt.Sprintf("姓名:%v\n年龄:%v\n成绩:%v\n", stu.Name, stu.Age, stu.Score)
}
如果调用Student中的SetScore方法,则会输出 “执行Student的SetScore方法…”
小学生部分
//小学生
type Pupil struct {
Student //匿名结构体
}
//小学生独有的方法
func (pupil *Pupil) testing01 () {
fmt.Println("小学生正在考试...")
}
小学生执行
//小学生
var pupil *Pupil = &Pupil{}
pupil.Student.Name = "Carter"
pupil.Student.Age = 9
pupil.testing01()
pupil.Student.SetScore(99) //没有使用简写
fmt.Println(pupil)
结果
小学生正在考试...
执行Student的SetScore方法...
姓名:Carter
年龄:9
成绩:99
大学生部分
//大学生
type Graduate struct {
Student //匿名结构体
Age string
}
func (graduate *Graduate) SetScore(score float64) {
fmt.Println("执行Graduate的SetScore方法...")
graduate.Score = score
}
func (graduate *Graduate) testing02 () {
fmt.Println("大学生正在考试...")
}
- Student匿名结构体
- Age int
- SetScore方法
- Graduate
- Age string
- SetScore方法
大学生调用
//大学生
var graduate = &Graduate{}
graduate.Name = "廖述幸"
graduate.Age = "18" //就近原则
graduate.testing02()
//使用简写--->就近原则调用
graduate.SetScore(89)
fmt.Println(graduate)
结果
大学生正在考试... --->Graduate下的testing02方法
执行Graduate的SetScore方法... --->Graduate下的SetScore方法
姓名:廖述幸 --->Student下的Name string字段
年龄:18 --->Graduate下的Age string字段
成绩:89 --->Student下的Score float64字段
接口
-
接口(interface)
- 电脑的USB接口===>It 插入接口===>计算机开始识别
- 接口它不关心你插的是什么
- 一个接口搞定所有设备
-
Golang就是面向接口编程
- Golang中 多态的特性主要是通过接口来体现的
代码
package main
import (
"fmt"
)
//声明/定义一个接口
type Usb interface {
//声明了两个没有实现的方法
Start()
Stop()
}
type Phone struct {
}
type Camera struct {
}
type Computer struct {
}
//让Phone实现 usb接口方法
func (phone Phone) Start() {
fmt.Println("手机开始工作...")
}
func (phone Phone) Stop() {
fmt.Println("手机停止工作...")
}
//让Camera实现 usb接口方法
func (camera Camera) Start() {
fmt.Println("相机开始工作...")
}
func (camera Camera) Stop() {
fmt.Println("相机停止工作...")
}
//编写一个电脑Working方法,接收一个Usb接口类型变量
//只要实现了 Usb接口 (所谓实现Usb接口,就是指实现了 Usb接口声明的所有方法)
func (computer Computer) Working (usb Usb) {
//通过Usb接口变量来调用Start()和Stop()方法
usb.Start()
usb.Stop()
}
func main() {
//声明3种结构体变量
var computer = Computer{}
var phone = Phone{}
var camera = Camera{}
//关键点
computer.Working(phone)
computer.Working(camera)
}
结果
手机开始工作...
手机停止工作...
相机开始工作...
相机停止工作...
接口入门
基本介绍
1)interface类型可以定义一组方法,但是这些不需要实现。
2)interface不包含任何变量
3)到某个自定义类型(比如结构退Phone)使用的时候,再根据具体情况吧这些方法写出来。
4)必须实现接口的所有方法,只能多不能少
基本语法
type 接口名 interface {
method01 (参数列表) 返回值列表
method02 (参数列表) 返回值列表
...
}
小结说明
1)接口里的所有方法都没有方法体,即接口的方法都没有实现的方法。接口体现了程序设计的多态和高内聚低耦合
的思想
2)Gloang中的接口,不需要显式的实现。只要一个变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。
3)因此Golang中没有implement
这样的关键字
A implement B接口 //Java中的接口显式实现
注意:
Go语言中没有implement,只要变量含有接口的所有方法即可实现
接口注意事项
1)接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
代码
package main
import (
"fmt"
)
//A接口
type AInterface interface {
Say()
}
//结构体变量
type Student struct {
Name string
}
func (stu Student) Say () {
fmt.Printf("你好,我叫%v\n", stu.Name)
}
func main() {
//A接口
var a AInterface
//Student结构体变量
var student Student = Student{Name:"Carter"}
//A接口指定Student结构体变量
a = student
//进行Say方法调用
a.Say()
}
结果
你好,我叫Carter
2)接口中所有的方法都是没有实现的,即没有方法体
3)Golang中一个自定义变量实现A接口的所有方法,称这个变量实现了A接口
4)自定义类型只有实现A接口,才能将该自定义类型的实例(变量)赋给接口类型
5)只要是自定义类型都可以实现接口,不仅仅是结构体类型
代码
//A接口
type AInterface interface {
Say()
}
type integer int
func (i integer) Say () {
fmt.Printf("你好,我叫%v\n", i)
}
func main() {
var i integer
var a AInterface = i
a.Say()
}
结果
你好,我叫0
6)一个自定义类型可以实现多个接口
代码
package main
import (
"fmt"
)
//A接口
type AInterface interface {
Say()
}
//B接口
type BInterface interface {
Hello()
}
//自定义类型
type Carter struct {
}
func (carter Carter) Say() {
fmt.Println("A接口中的Say方法...")
}
func (carter Carter) Hello() {
fmt.Println("B接口中的Hello方法...")
}
func main() {
//同时实现A、B两个接口
var carter Carter
var a AInterface = carter
var b BInterface = carter
a.Say()
b.Hello()
}
结果
A接口中的Say方法...
B接口中的Hello方法...
7)接口中不可有任何变量
8)一个A接口可以继承多个别的接口,实现A接口的同时,也实现别的接口的方法
9)interface类型默认是一个指针**(引用类型)**,传参是传地址,如果没有对Interface初始化就使用,那么会输出nil
10)空接口 interface{ }没有任何方法,所以所有类型都实现了空接口,可以接收任何数据类型
代码
func main() {
var t interface {}
var num = 8.8
t = num //可以接收任何数据类型
fmt.Println(t)
}
结果
8.8
11)任何情况下都不能有重复的方法名
接口的最佳实践
- 实现对Student结构体切片的排序:
sort.Sort(data Interface)
- 参数
- 名为Interface的接口
- 要求
- 按照Student的成绩升序排序
- Student结构体自定义的切片类型,实现Interface接口的所有方法
- 思路
- 定义结构体Student
- 定义该结构体的切片类型
- 实现Interface接口
代码实现
package main
import (
"fmt"
"math/rand"
"sort"
"time"
)
type Student struct {
Name string
Sex string
Score float64
}
//定义切片
type StuSlice []Student
//实现Sort排序函数参数列表的的Interface接口的方法
func (stu StuSlice) Len() int {
return len(stu)
}
//按成绩升序
func (stu StuSlice) Less(i, j int) bool {
return stu[i].Score < stu[j].Score
}
//交换的方法
func (stu StuSlice) Swap(i, j int) {
temp := stu[i]
stu[i] = stu[j]
stu[j] = temp
}
func main() {
var stuSlice StuSlice
//随机种子
rand.Seed(time.Now().Unix())
for i := 0; i < 10; i++ {
student := Student{
Name: fmt.Sprintf("学生%d号", i),
Sex: "男",
Score: float64(rand.Intn(100) + 1),
}
stuSlice = append(stuSlice, student)
}
//修改前
fmt.Println("----------排序前----------")
for _, value := range stuSlice {
fmt.Println(value)
}
fmt.Println("----------按学生成绩排序后----------")
//进行排序
sort.Sort(stuSlice)
for _, value := range stuSlice {
fmt.Println(value)
}
}
结果
----------排序前----------
{学生0号 男 33}
{学生1号 男 30}
{学生2号 男 29}
{学生3号 男 18}
{学生4号 男 43}
{学生5号 男 100}
{学生6号 男 87}
{学生7号 男 33}
{学生8号 男 62}
{学生9号 男 42}
----------按学生成绩排序后----------
{学生3号 男 18}
{学生2号 男 29}
{学生1号 男 30}
{学生0号 男 33}
{学生7号 男 33}
{学生9号 男 42}
{学生4号 男 43}
{学生8号 男 62}
{学生6号 男 87}
{学生5号 男 100}
结论
temp := stu[i]
stu[i] = stu[j]
stu[j] = temp
---等价于---
stu[i], stu[j] = stu[j], stu[i]
接口 VS 继承 1
1)实现接口是对继承机制的补充
2)不破坏继承的情况下,进行拓展,使用接口
3)B继承A,B拥有并可以直接使用A字段+方法
接口 VS 继承 2
-
继承的价值
- 可复用性
- 可维护性
- 可拓展性
-
接口的价值
- 设计
- 规范
- 灵活
- 代码解耦
-
继承必须满足 is - a 的关系,而接口只需要满足 like - a 的关系(松散的关系)
多态
基本介绍
变量(实例)具有多种形态。面向对象的第三大特征,在Golang中,多态的特征是通过接口实现的.
可以按照统一的接口调用不同的实现。这时接口变量就呈现不同的形态。
快速入门
1)多态参数
//声明/定义一个接口
type Usb interface {
//声明了两个没有实现的方法
Start()
Stop()
}
type Phone struct {
}
type Camera struct {
}
type Computer struct {
}
//让Phone实现 usb接口方法
func (phone Phone) Start() {
fmt.Println("手机开始工作...")
}
func (phone Phone) Stop() {
fmt.Println("手机停止工作...")
}
//让Camera实现 usb接口方法
func (camera Camera) Start() {
fmt.Println("相机开始工作...")
}
func (camera Camera) Stop() {
fmt.Println("相机停止工作...")
}
//编写一个电脑Working方法,接收一个Usb接口类型变量
//只要实现了 Usb接口 (所谓实现Usb接口,就是指实现了 Usb接口声明的所有方法)
func (computer Computer) Working (usb Usb) {
//通过Usb接口变量来调用Start()和Stop()方法
usb.Start()
usb.Stop()
}
func main() {
//声明3种结构体变量
var computer = Computer{}
var phone = Phone{}
var camera = Camera{}
//统一的接口调用不同的实现,这就是接口变量呈现不同的状态
computer.Working(phone)
computer.Working(camera)
}
多态数组
1)一般数组:只能存放一种数据类型
2)多态数组:通过接口实现存放不同种类的数据类型
package main
import (
"fmt"
)
//声明/定义一个接口
type Usb interface {
//声明了两个没有实现的方法
Start()
Stop()
}
type Phone struct {
Name string
}
type Camera struct {
Name string
}
//让Phone实现 usb接口方法
func (phone Phone) Start() {
fmt.Println("手机开始工作...")
}
func (phone Phone) Stop() {
fmt.Println("手机停止工作...")
}
func (phone Phone) Test() {
fmt.Println("Test()方法实现...")
}
//让Camera实现 usb接口方法
func (camera Camera) Start() {
fmt.Println("相机开始工作...")
}
func (camera Camera) Stop() {
fmt.Println("相机停止工作...")
}
func (camera Camera) Test() {
fmt.Println("Test()方法实现...")
}
func main() {
//数组:只能存放一种数据类型
//多态数组:通过接口实现,存放不同种类的数据类型
var usbArr [2]Usb
usbArr[0] = Phone{Name:"手机"}
usbArr[1] = Camera{Name:"相机"}
fmt.Println(usbArr)
}
接口的问题
1)Phone和Camera都实现了Usb的方法,所以可以通过Computer进行方法调用
2)如果Phone增加了一个Call ( ) 方法,并且需要Computer进行调用,但是Camera又没有Call ( )方法,这会导致Camera实例无法导入
//编写一个电脑Working方法,接收一个Usb接口类型变量
//只要实现了 Usb接口 (所谓实现Usb接口,就是指实现了 Usb接口声明的所有方法)
func (computer Computer) Working (usb Usb) {
//通过Usb接口变量来调用Start()和Stop()方法
usb.Start()
usb.Stop()
}
func main() {
//声明3种结构体变量
var computer = Computer{}
var phone = Phone{}
var camera = Camera{}
//统一的接口调用不同的实现,这就是接口变量呈现不同的状态
computer.Working(phone)
computer.Working(camera)
}
多态的特征
1)多态参数
在前面的Usb接口案例,Usb usb, 即可以接收手机变量,又可以接收相机变量,就体现了Usb接口的多态
2)多态数组
给Usb数组中,存放Phone结构体和Camera结构体变量,Phone还有一个特有的Call ( )方法
请遍历Usb数组,如果是Phone变量,除了调用Usb接口声明的方法,还需要调用Phone特有的Call ( )方法
问题
- 需要一种判断,判断传进来的是Phone还是Camera实例
解决方案
- 类型断言
assert 断言
基本介绍
1)在进行断言时,如果类型不匹配,就会报panic,因此进行断言时,要确保原来的空接口指向的就是要断言的类型
var x interface{}
var a = 132.45
x = a
b := x.(float64)
fmt.Printf("y 的类型是 %T 值是%v", b, b)
断言实例0
代码
var x interface{}
var num01 = 132.45
x = num01
num02, flag:= x.(float32)
//待检测的
if flag {
fmt.Println("转换成功...")
fmt.Printf("y 的类型是 %T 值是%v", num02, num02)
} else {
fmt.Println("转换失败...")
}
fmt.Println("仍继续执行断言后的代码...")
结果
转换失败...
仍继续执行断言后的代码...
断言实例1
- Phone有自己特有的方法Call()
- Camera有自己特有的方法Photos()
- 计算机进行识别
代码
package main
import "fmt"
type Usb interface{
Start()
Stop()
}
type Phone struct {}
func (phone Phone) Start() {
fmt.Println("手机开始工作...")
}
func (phone Phone) Call() {
fmt.Println("手机正在打电话...")
}
func (phone Phone) Stop() {
fmt.Println("手机结束工作...")
}
type Camera struct {}
func (camera Camera) Start() {
fmt.Println("相机开始工作...")
}
func (camera Camera) Photos() {
fmt.Println("相机正在拍照...")
}
func (camera Camera) Stop() {
fmt.Println("相机结束工作...")
}
type Computer struct {}
func (computer Computer) Working(usb Usb) {
usb.Start()
//断言
if phone, flag :=usb.(Phone); flag {
phone.Call()
}
//断言
if camera, flag :=usb.(Camera); flag {
camera.Photos()
}
usb.Stop()
}
func main() {
var computer Computer
var usb [3]Usb = [3]Usb{Phone{}, Camera{}, Phone{}}
for _, value := range usb {
computer.Working(value)
fmt.Println()
}
}
结果
手机开始工作...
手机正在打电话...
手机结束工作...
相机开始工作...
相机正在拍照...
相机结束工作...
手机开始工作...
手机正在打电话...
手机结束工作...
断言实例2
- 一个可以接收任何参数的函数,对参数进行类型判定
代码
package main
import (
"fmt"
)
//自定义Student类型
type Student struct {}
//编写一个可以接收任何参数的函数
func TypeJudge(items ...interface{}) {
for index, item := range items {
switch item.(type) {
case bool:
fmt.Printf("第%v个参数是bool类型,值是%v\n", index, item)
case float32:
fmt.Printf("第%v个参数是float32类型,值是%v\n", index, item)
case float64:
fmt.Printf("第%v个参数是float64类型,值是%v\n", index, item)
case int, int8, int16, int32, int64:
fmt.Printf("第%v个参数是整数类型,值是%v\n", index, item)
case string:
fmt.Printf("第%v个参数是string类型,值是%v\n", index, item)
case Student:
fmt.Printf("第%v个参数是Student类型,值是%v\n", index, item)
case *Student:
fmt.Printf("第%v个参数是Student指针类型,值是%v\n", index, item)
default:
fmt.Printf("第%v个参数类型不确定,值是%v\n", index, item)
}
}
}
func main() {
var n1 float32 = 1.1
var n2 = 12.345
var n3 = "Carter"
var student01 Student
var student02 *Student
TypeJudge(n1, n2, n3, student01, student02)
}
结果
第0个参数是float32类型,值是1.1
第1个参数是float64类型,值是12.345
第2个参数是string类型,值是Carter
第3个参数是Student类型,值是{}
第4个参数是Student指针类型,值是<nil>