Go面向对象编程的三大特性
1.基本介绍
Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样。
2.面向对象编程思想-抽象
抽象:
在定义一个结构体类型时,实际上就是把一类事物共有的属性(字段)和行为(方法)提取出来形成一个物理模型。
案例演示:
type Account struct {
AccountNo string
Pwd string
Balance float64
}
//方法
func (account *Account) Withdraw(money float64) {
if money <= 0 || money > account.Balance {
fmt.Println("输入的金额数不正确")
}
account.Balance -= money
fmt.Println("取款成功")
}
func (account *Account) Deposite(money float64) {
if money <= 0 {
fmt.Println("输入的金额数不正确")
}
account.Balance += money
fmt.Println("存款成功")
}
func (account *Account) Query() {
fmt.Printf("账号:%v的余额为%v\n", account.AccountNo, account.Balance)
}
func main() {
account := Account{
AccountNo: "0123x",
Pwd: "888888",
Balance: 100.0,
}
var (
accountno string
pwd string
money float64
mode int
)
label:
for {
fmt.Println("请输入账号:")
fmt.Scanln(&accountno)
if accountno == "n" {
fmt.Println("退出程序...")
break
}
for i := 0; i < 3; i++ {
fmt.Println("请输入密码:")
fmt.Scanln(&pwd)
if pwd != account.Pwd {
fmt.Println("密码输入错误")
} else {
break
}
if i == 2 {
fmt.Println("密码输错三次今日机会已用完...")
break label
}
}
here:
for {
fmt.Println("输入办理义务选项:0查询,1存款,2取款")
fmt.Scanln(&mode)
switch mode {
case 0:
account.Query()
break here
case 1:
fmt.Println("请输入金额:")
fmt.Scanln(&money)
account.Deposite(money)
break here
case 2:
fmt.Println("请输入取出金额:")
fmt.Scanln(&money)
account.Withdraw(money)
break here
default:
fmt.Println("模式选择有误,请重新输入")
}
}
}
}
3.封装
封装(encapsulation)就是把抽象出的字段和对字段操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作。
封装实现的具体步骤:
1)将结构体、字段/属性的首字母小写(不能导出,其它包不能使用,类似private但本包能还是能使用的)
2)给结构体所在包提供一个工厂模式函数,首字母大写。类似一个构造函数
3)提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判定并赋值
func (结构体名 结构体类型)SetXxx(参数列表)(返回值列表){
//加入数据验证的业务逻辑
结构体名.字段 = 参数
}
4)提供一个首字母大写的Get方法(类似其它语言的public),用于获取字段/属性的值
func (结构体名 结构体类型)GetXxx(参数列表)(返回值列表){
return 结构体名.字段
}
案例演示:
要求不能随便查看人的年龄和工资等隐私,并对输入的年龄进行合理的验证。采用封装的思想完成设计,设计要求:model包中建立一个person.go,在main包中main.go能调用person.go中的person结构体。
model包/person.go程序:
package model
import "fmt"
type person struct {
Name string
age int //字段名首字母小写不能被其他包直接访问
salary float64
}
// 构造一个工厂模式函数,类似构造函数,访问person结构体
func NewPerson(name string) *person {
return &person{
Name: name,
}
}
// 为了能在其它包访问age 和salary 编写一对SetXXX()和GetXxx()方法
func (p *person) SetAge(age int) {
if age > 0 && age < 150 {
p.age = age
} else {
fmt.Println("输入年龄超过范围")
p.age = -1
}
}
func (p *person) GetAge() int {
return p.age
}
func (p *person) SetSalary(salary float64) {
if salary > 3000 && salary < 30000 {
p.salary = salary
} else {
fmt.Println("薪水超范围不正确")
p.salary = -1
}
}
func (p *person) GetSalary() float64 {
return p.salary
}
main包main.go程序:
package main
import (
"fmt"
"go_code/chapter11/encapsulate02/model"
)
func main() {
p := model.NewPerson("tom")
p.SetAge(21)
p.SetSalary(15000)
fmt.Println(p) //&{tom 21 15000}
fmt.Println(*p) //{tom 21 15000}
fmt.Println(p.Name, "age=", p.GetAge(), "salary=", p.GetSalary())
//tom age= 21 salary= 15000
}
4.继承
为防止代码冗余\复 用,更利于代码的维护和功能拓展,需要继承方式。
1.基本介绍和示意图:
1)当多个结构体存在相同的属性/字段和 方法时,可以从这些结构体中抽象出新的结构体,该结构体中定义这些相同的字段和方法。
2)其他结构体不再需要重新定义这些相同的字段和方法,只需要嵌套一个包含这些相同字段和方法的匿名结构体即可。
3)即在Golang中,如果一个结构体嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现继承特性。
4)与其他OOP语言不同的是,在Golang中弱化了继承的概念。例如在Java中继承使用关键字extend实现,划分父类、子类的概念。在Golang中没有这些概念,简单的通过结构体后跟匿名结构的方式完成继承的特性。
图例说明
2.嵌套匿名结构体的基本语法
type Goods struct{
Name string
Price float64
}
type Book struct{
Goods //此处就是嵌套匿名结构体Goods
Writer string
}
案例演示:
//共有属性结构体
type Student struct {
Name string
Age int
Score float64
}
func (stu *Student) ShowInfo() {
fmt.Printf("学生名:%v,年龄为:%v,成绩:%v\n", stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetAge(age int) {
if age < 8 || age > 35 {
fmt.Println("年龄超过范围")
return
}
stu.Age = age
}
func (stu *Student) SetScore(score float64) {
if score < 0 || score > 100 {
fmt.Println("分数超过范围")
return
}
stu.Score = score
}
type Pupil struct {
Student
}
//特有方法
func (p *Pupil) testing() {
fmt.Println("小学生正在考试中")
}
type Undergraduate struct {
Student
Married bool //特有属性/字段
}
//特有方法
func (Undergraduate *Undergraduate) SetMarried(judge bool) {
if judge {
Undergraduate.Married = judge
}
}
func main() {
pupil := &Pupil{}
pupil.Student.Name = "tom"
pupil.Student.Age = 9
pupil.testing() //小学生正在考试中
pupil.Student.SetScore(91)
pupil.Student.SetAge(10)
pupil.Student.ShowInfo() //学生名:tom,年龄为:10,成绩:91
fmt.Println()
undergraduate := &Undergraduate{}
undergraduate.Student.Name = "marry"
undergraduate.Student.Age = 20
undergraduate.Student.SetScore(92)
undergraduate.Student.SetAge(21)
undergraduate.Student.ShowInfo() //学生名:marry,年龄为:21,成绩:92
undergraduate.SetMarried(true)
fmt.Printf("%v是否结婚:%v\n", undergraduate.Name, undergraduate.Married)
// marry是否结婚:true
}
3.继承的细节
1)结构体可以使用嵌套匿名结构体所有的字段和方法,即无论首字母是否大写的字段和方法都可以使用。
案例演示:
type A struct {
Name string
age int
}
func (a *A) Sayok() { //类似public
fmt.Println("A Sayok,", a.Name)
}
func (a *A) hello() { //类似private
fmt.Println("A hello,", a.Name)
}
type B struct {
A
}
func main() {
var b B
b.A.Name = "marry"
b.A.age = 18
b.A.Sayok() //A Sayok, marry
b.A.hello() // A hello, marry
fmt.Println(b) //{{marry 18}}
}
2)匿名结构体字段访问可以简化,省略匿名结构体进行访问。
var b B
b.A.Name = "marry"
b.A.age = 18
b.A.Sayok() //A Sayok, marry
b.A.hello() // A hello, marry
fmt.Println(b) //{{marry 18}}
// 写法等价于
b.Name = "tom"
b.age = 19
b.Sayok() //A Sayok, tom
b.hello() // A hello, tom
fmt.Println(b) //{{tom 19}}
结合上述案例,结构体含有匿名结构体的访问机制:
(1)当直接通过结构体变量b访问字段或方法时,例如b.Name其流程如下:
(2)编译器先在结构体变量b中查询有无对应的Name字段,如果有则直接访B类型的Name字段
(3)如果在B类型中没有Name字段,则访问B类型中嵌入的匿名结构体A有没有声明Name字段,若有就调用,若没有就继续查找,如果最后都没有找到Name字段就报错。
3)当结构体和匿名结构体有相同的字段、方法时,编译器采用就近原则访问,即优先先访问结构体自身的字段和方法,再访问匿名结构体内的字段和方法。若指定访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。
type A struct {
Name string
age int
}
func (a *A) Sayok() { //类似public
fmt.Println("A Sayok,", a.Name)
}
func (a *A) hello() { //类似private
fmt.Printf("A hello,%q\n", a.Name)
}
type B struct {
A
Name string
}
func (b *B) Sayok() {
fmt.Println("B Sayok,", b.Name)
}
func main() {
var b B
b.Name = "Alex"
b.age = 20
b.Sayok() //B Sayok, Alex
b.hello() //A hello,""
fmt.Println(b) //{{ 20} Alex}
//访问匿名结构体的字段
var b1 B
b1.A.Name = "Scoot"
b1.age = 21
b1.hello() //A hello,"Scoot"
fmt.Println(b1) // {{Scoot 21} }
4)当结构体嵌入 多个匿名结构体,这些匿名结构体含有相同的字段或方法(同时结构体本身不存在这些字段或方法时),在访问这些字段或方法时,就必须明确指定匿名结构体的名字,否则编译报错。
type A struct {
Name string
age int
}
type B struct {
Name string
Score float64
}
type C struct{
A
B
}
func main() {
var c C
//c.Name = "tom" //报错 指代模糊
c.A.Name = "tom" //须明确匿名结构
}
5)如果一个结构体嵌套了一个有变量名的结构体,这种模式就是组合。如果是组合关系,那么在访问组合结构体的的字段或方法时,必须带上结构体的名字。
type A struct {
Name string
age int
}
type D struct{
a A
age int
}
func main() {
var d D
//d.Name = "tom" //报错 D内没有相关字段或方法
d.a.Name = "tom"
//存在有名结构体时,访问有名结构体字段或方法时,须明确匿名结构的名字 例如 d.a.Name
d.age = 18 //操作允许,其访问机制还是先访问结构体本身
}
6)嵌套匿名结构体后,也可以在创建结构体变量(实例)时直接指定各个匿名结构体字段的值。
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Address string
}
type Tv struct {
Goods
Brand
}
type Tv1 struct {
*Goods
*Brand
}
func main() {
tv1 := Tv{Goods{"电视机类型1", 1600}, Brand{"夏普", "东京"}}
tv2 := Tv{
Goods{
Name: "电视机类型2",
Price: 1799,
},
Brand{
Name: "长虹",
Address: "长安",
},
}
fmt.Println(tv1) //{{电视机类型1 1600} {夏普 东京}}
fmt.Println(tv2) //{{电视机类型2 1799} {长虹 长安}}
tv3 := Tv1{&Goods{"电视机类型3", 1888}, &Brand{"海尔", "北京"}} //地址符不可省略
tv4 := Tv1{
&Goods{
Name: "电视机类型4",
Price: 1999,
},
&Brand{
Name: "Htc",
Address: "首尔",
},
}
fmt.Printf("%v\n", tv3) //{0xc000004078 0xc0000503e0}
fmt.Printf("%v\n", tv4) //{0xc000004090 0xc000050400}
fmt.Println("tv3", *tv3.Goods, *tv3.Brand) //tv3 {电视机类型3 1888} {海尔 北京}
fmt.Println("tv4", tv4.Goods, *tv4.Brand) //tv4 &{电视机类型4 1999} {Htc 首尔}
}
7)结构体内也允许匿名字段是基本数据类型,但为了不是逻辑紊乱不能重复使用同一类型的基本数据匿名字段。
type Monster struct {
Name string
Age int
}
type E struct {
Monster
int
n int
}
func main() {
//结构体内也允许匿名字段是基本数据类型,
// 但为了不是逻辑紊乱不能重复使用同一类型的基本数据匿名字段。
// 若需要多个int的字段,则必须给int字段指定名字。
e := E{Monster{"牛魔王", 188}, 20, 22}
fmt.Println("e=", e) //e= {{牛魔王 188} 20 22}
}
小结:
结构体内包含匿名结构体实现继承特性;包含带名结构体则形成组合(调用带名结构体的字段和方法时须明确结构体名);结构体内含有匿名基本类型字段时,本质上就是其本身的一种属性/字段,但需要注意的是,同一类型的匿名字段不可多次使用,若需要多次定义相同类型的字段,则直接使用基础的定义字段方式即可(例如 n1 int )。
4.多重继承
一个结构体内含有多个匿名结构体,那么这个结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现多重继承。
1)如果嵌入的结构体们含有相同的字段名或者方法名,则在访问时需要通过匿名结构体类型名来区分。(参考上述3.继承继承细节第四点)
2)为了保证代码的简洁性,建议不要使用多重继承,防止逻辑紊乱,继承关系复杂化。
5.接口
在介绍多态前需要;了解接口(interface),在GOlang中多态特性主要是通过接口来实现的,并且在Go语言面向对象编程中面向接口编程占据着重要地位。
入门案例:
// 声明/定义一个借口
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接口类型
func (c Computer) Working(usb Usb) {
usb.Start()
usb.stop()
}
func main() {
// 测试
phone := Phone{}
camera := Camera{}
computer := Computer{}
//关键点
//Phone和Camera结构体都具有Start()和stop()方法,故两者是满足Usb接口类型的接口
computer.Working(phone)
computer.Working(camera)
}
Output:
手机开始工作
手机停止工作
相机开始工作
相机停止工作
1.接口基本介绍
interface
类型可以定义一组方法,但这些方法不需要实现。并且在golang中接口interface不能包含任何变量。到某个自定义类型(比如上述结构体Phone)要使用的时候,再根据结构体具体情况详细写出全部的方法(只有实现了全部的方法的变量才能实现接口)。
2.接口基本语法
type 接口名 interface{
method1(参数列表)返回值列表
method2(参数列表)返回值列表
}
实现接口所有方法的变量类型才具备该接口特性,即变量需要具有:
func(t 自定义类型) method1(参数列表)返回值列表{
//方法实现
}
func(t 自定义类型) method2(参数列表)返回值列表{
//方法实现
}
小结说明:
1)接口里的所有方法都不允许有方法体,即接口的方法是没有实现的方法,最多只有方法名(参数列表)返回值列表。interface
接口体现了程序设计的多态和高内聚低耦合的思想
2)Golang中的接口,相比其他OOP语言不需要显示实现,因此Golang中没有implement这样的关键字。Golang的接口的实现依赖于方法的实现;即:只要一个变量含有一种接口的所有方法,那么这个变量就实现了这种接口。
3.接口注意事项和细节
1)接口本身不能创建实例,但是可以指向实现了该接口的自定义类型的变量(实例);即一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型。
type Ainterface interface {
say()
}
type Stu struct {
Name string
Age int
}
func (stu Stu) say() {
fmt.Println("stu Say() ok")
}
func main() {
var (
a Ainterface
stu Stu //结构体变量,实现了say()方法,实现了Ainterface
)
a = stu
a.say() //stu Say() ok
}
2)接口中所有的方法都没有方法体,即都是没有实现的方法。
3)在Golang中,一个自定义类型需要将某个接口的所有方法都实现了,才能称该自定义类型实现了该接口。
4)只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
type integer int
func (i integer) say() {
fmt.Println("integer say i = ", i)
}
func main() {
var (
b Ainterface
//自定义变量i,实现了say()方法,实现了Ainterface
i integer
)
b = i
b.say()//integer say i = 0
}
5)一个自定义类型可以实现多个接口
type Ainterface interface {
say()
}
type Binterface interface {
Hello()
}
type Monster struct {
}
func (m Monster) Hello() {
fmt.Println("Monster hello() ~~")
}
func (m Monster) say() {
fmt.Println("Monster sau() ~~")
}
func main() {
// 自定义结构体Moster 实现了say()、 Hello()方法
// Monster变量m实现了Ainterface 和 Binterface
var (
a1 Ainterface
b1 Binterface
m Monster
)
a1 = m
b1 = m
a1.say()
b1.Hello()
}
6)与其他OOP语言不同之处,Golang接口中不能定义任何变量。
type Ainterface interface {
Name string //报错
Test01()
Test02()
}
7)一个接口(比如A接口)可以继承多个别的接口(比如B、C接口),这时如果要实现A接口,也必须将B,C接口也全部实现。
type Binterface interface {
test01()
}
type Cinterface interface {
test02()
}
type Ainterface interface {
Binterface
Cinterface
test03()
}
// 若需要实现Ainterface接口,则需将Binterface和Cinterface所以方法都实现
type Stu struct {
Name string
Age int
}
func (stu Stu) test01() {
fmt.Println("test01()....")
}
func (stu Stu) test02() {
fmt.Println("test02()....")
}
func (stu Stu) test03() {
fmt.Println("test03()....")
}
func main() {
stu := Stu{"tom", 18}
stu.test01() //test01()....
stu.test02() //test02()....
stu.test03() //test03()....
}
8)interface
类型默认是一个指针(** ),如果没有对interface初始化就是使用,那么会输出nil
。
9)空接口interface{}没有任何方法,所以所用类型都实现了空接口**,即可以将任何一个变量都赋给空接口。
// 空接口
type T interface {
}
func main() {
f1 := 8.8
n1 := 10
// 空接口使用方法1:
var t1 T = f1
fmt.Println(t1) //8.8
// 空接口使用方法2:
var t2 interface{} = n1
fmt.Println(t2) //10
}
4.接口实践案例
使用系统自带的sort.Sort(data interface)
进行排序,要求data interface
满足:
案例演示
// 实现对Hero结构体切片的排序:sort.Sort(data Interface)
type Hero struct {
Name string
Age int
}
type HeroSlice []Hero
func (hs HeroSlice) Len() int {
return len(hs)
}
// Less方法决定使用什么标准进行排序
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) {
hs[i], hs[j] = hs[j], hs[i]
/*等价于
temp := hs[i]
hs[i] = hs[j]
hs[j] = temp*/
}
func main() {
heroslice := make(HeroSlice, 0)
for i := 0; i < 10; i++ {
hero := Hero{
Name: fmt.Sprintf("英雄%d", rand.Intn(100)),
Age: rand.Intn(100) + 10,
}
heroslice = append(heroslice, hero)
}
// 未排序前
for i, v := range heroslice {
fmt.Printf("no.%d,%v\n", i, v)
}
// fmt.Println(heroslice)
sort.Sort(heroslice)
fmt.Println("------------")
// 排序后
// fmt.Println(heroslice)
for i, v := range heroslice {
fmt.Printf("no.%d,%v\n", i, v)
}
var intslice = []int{7, -1, 3, 4}
sort.Ints(intslice)
fmt.Println(intslice) //[-1 3 4 7]
}
5.接口VS继承
接口,可以在不破坏继承特性的基础上扩展功能。
1)当A结构体继承了B结构体,那么A结构体就自动继承了B结构体的字段和方法,并且可以直接使用
2)当A结构体需要扩展功能,同时不希望破坏原有的继承特性,则可以通过实现某个接口的即可。因此实现接口是对继承机制的补充。
案例演示:
type Monkey struct {
Name string
}
func (m *Monkey) Clambing() {
fmt.Println(m.Name, "天生会爬树")
}
// 声明一个接口 接口是对继承的补充
type BirdAble interface {
Flying()
}
type FishAble interface {
Swimming()
}
type LittelMonkey struct {
Monkey //继承
}
func (LM *LittelMonkey) Flying() {
fmt.Println(LM.Name, "通过修行掌握了风行术")
}
func (LM *LittelMonkey) Swimming() {
fmt.Println(LM.Name, "通过修行掌握了水遁术")
}
type GetAbility struct {
}
// 编写一个方法study,接收一个BirdAble接口类型
func (gb GetAbility) Study(ba BirdAble) {
ba.Flying()
}
// 编写一个方法study1,接收一个FishAble接口类型
func (gb GetAbility) Study1(fa FishAble) {
fa.Swimming()
}
func main() {
monkey := LittelMonkey{
Monkey{
Name: "悟空",
},
}
// 方法
//标准写法:(&monkey).Monkey.Clambing()等价于(&monkey).Clambing()
// (&monkey).Clambing() 等价于下者
monkey.Clambing() //悟空 天生会爬树
monkey.Flying() //悟空 通过修行掌握了风行术
monkey.Swimming() //悟空 通过修行掌握了水遁术
// 接口的应用
// 实际上实现接口的是*LittleMonkey类型
getAbility := GetAbility{}
getAbility.Study(&monkey) //悟空 通过修行掌握了风行术
getAbility.Study1(&monkey) //悟空 通过修行掌握了水遁术
}
3)接口与继承解决问题的侧重点:
(1)继承的价值主要在于:解决代码复用性和可维护性
(2)接口的价值主要在于:设计,设计好各种规范(方法),让其他自定义类型去实现这些方法
(3)接口比继承更灵活,继承是满足is-a
的关系,即需要明确继承对象;而接口只需满足like-a
的关系,即只需实现了所有相关方法就实现了相应的接口。
(4)接口在一定程度上实现了代码的解耦。
6.多态
多态:变量(实例)具有多种形态。在Go语言中,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现,这时接口变量就呈现不同的形态。
1.快速入门案例
在“5.接口”章节的快速入门案例(Usb接口案例)中,func (c Computer) Working(usb Usb) {}
方法的参数列表usb Usb
既可以接收手机变量phone,又可以接收相机变量camera,就体现了Usb接口的多态特性。
2.接口体现多态
1)多态参数
在前面Usb接口案例中,参数列表(usb Usb)
,可以接收多种不同类型的变量(Phone 、Camera两种结构体的变量),就体现了Usb接口的多态。
2)多态数组
在基础数组里只允许存放同一类型的数据;但在使用接口后,可以存放实现了该接口的任何自定义类型变量。
案例说明:
// 声明/定义一个借口
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("相机停止工作")
}
type Computer struct {
}
// 编写一个方法Working,接收一个Usb接口类型
func (c Computer) Working(usb Usb) { //usb 既能接收phone也能接收camera 多态特性
usb.Start()
usb.stop()
}
func main() {
// 多态参数
phone := Phone{}
camera := Camera{}
computer := Computer{}
//关键点
//Phone和Camera结构体都具有Start()和stop()方法,故两者是满足Usb接口类型的接口
computer.Working(phone)
computer.Working(camera)
// 多态数组
var usbArr [3]Usb
fmt.Println(usbArr) //[<nil> <nil> <nil>]
usbArr[0] = Phone{"小米"}
usbArr[1] = Phone{"华为"}
usbArr[2] = Phone{"尼康"}
fmt.Println(usbArr) //[{小米} {华为} {尼康}]
}
3.类型断言
assertion
类型断言:将一个接口变量赋给自定义类型变量的操作。
入门案例演示:
在这里插入代码片type Point struct {
x int
y int
}
func main() {
var a interface{}
var point Point = Point{1, 2}
a = point //将Point变量传入给空接口,空接口能接收任何变量
// 如何将变量a赋给一个Point变量?
var b Point
// b = a//报错,类型不一致,需要断言assertion操作
b = a.(Point)
fmt.Println(b) //{1 2}
}
案例中b = a.(Point)
就是一个类型断言操作,表示判断a
接口变量是否指向Point
类型的变量,如果是a
就转成Point类型(返回一个Point类型的变量)并赋值给b变量,否则就报错。
1)基本介绍
类型断言作用在于:由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言指向具体的转换类型。
案例2:
var f float32 = 3.3
var c interface{} = f
c1 := c.(float32)
fmt.Printf("c1的类型是%T,值是%v", c1, c1) // c1的类型是float32,值是3.3
注意:在进行类型断言时要确保类型匹配,即确保原来的空接口指向的就是断言的类型;若类型不匹配则会报panic。
基于上述,引出在进行断言时,带上检测机制。即断言成功就ok,断言不匹配也不要报panic,程序能继续执行。
案例:
var x float32 = 3.3
var d interface{} = x
y, flag := d.(float64)
if flag { //简洁写法 if y,flag:=d.(float64);flag
fmt.Println("convert success")
fmt.Printf("y的类型是%T,值是%v\n", y, y)
} else {
fmt.Println("convert fail")
}
fmt.Println("程序继续执行")
Output:
convert fail
程序继续执行
2)类型断言的实践案例
案例一:
在前面Usb接口案例改进:给Pheon结构体增加一个特有的方法Call(),当Usb接口接收的是Phone变量时,还需要调用Call()方法。
// 声明/定义一个借口
type Usb interface {
// 声明了两个没有实现的方法
Start()
stop()
}
type Phone struct {
Name string
}
// 让phone实现Usb接口的方法
func (p Phone) Start() {
fmt.Println("手机开始工作")
}
func (p Phone) stop() {
fmt.Println("手机停止工作")
}
// Phone结构体独有的方法
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 {
}
// 编写一个方法Working,接收一个Usb接口类型
func (c Computer) Working(usb Usb) { //usb 既能接收phone也能接收camera 多态特性
usb.Start()
usb.stop()
// 写法二:将类型断言放入方法内(不需重新声明一个接口变量,有利于代码统一管理)
if phone, flag := usb.(Phone); flag {
phone.Call()
}//简写可为:
//if _, flag := usb.(Phone); flag {
// usb.(Phone).Call()
//}
}
func main() {
// 多态参数
phone := Phone{}
camera := Camera{}
computer := Computer{}
//关键点
//Phone和Camera结构体都具有Start()和stop()方法,故两者是满足Usb接口类型的接口
computer.Working(phone)
computer.Working(camera)
// 多态数组
var usbArr [3]Usb
fmt.Println(usbArr) //[<nil> <nil> <nil>]
usbArr[0] = Phone{"小米"}
usbArr[1] = Phone{"华为"}
usbArr[2] = Camera{"尼康"}
fmt.Println(usbArr) //[{小米} {华为} {尼康}]
// 遍历USBArr
//如果是Phone变量除了调用接口声明的方法,还需调用Phone特有的方法Call()
// 使用类型断言实现
// var a Usb
for _, v := range usbArr {
computer.Working(v)
// // 写法一:定义一个新的接口变量用于执行类型断言
// a = v
// if v1, flag := a.(Phone); flag {
// v1.Call()
// }
fmt.Println()
}
}
案例二:
能够实现循环判断传入参数的类型
type Student struct {
Name string
Age int
Id string
}
// 编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items ...interface{}) {
for index, x := range items {
switch x.(type) {//这里type是关键字,固定写法
case bool:
fmt.Printf("第%v个参数是bool类型,值是%v\n", index+1, x)
case int, int32, int64:
fmt.Printf("第%v个参数是整数类型,值是%v\n", index+1, x)
case float32, float64:
fmt.Printf("第%v个参数是浮点数类型,值是%v\n", index+1, x)
case string:
fmt.Printf("第%v个参数是string类型,值是%v\n", index+1, x)
case nil:
fmt.Printf("第%v个参数是nil类型,值是%v\n", index+1, x)
case Student:
fmt.Printf("第%v个参数是Student类型,值是%v\n", index+1, x)
case *Student:
fmt.Printf("第%v个参数是*Student类型,值是%v\n", index+1, x)
default:
fmt.Printf("第%v个参数是类型不确定,值是%v\n", index+1, x)
}
}
}
func main() {
var n1 int = 1
var n2 int64 = 64
var n3 float64 = 15.5
var name string = "tom"
address := "上海"
var nil1 interface{}
stu1 := Student{
Name: "marry",
Age: 18,
Id: "0123x",
}
stu2 := &Student{"jim", 19, "0124x"}
TypeJudge(n1, n2, n3, name, address, nil1, stu1, stu2)
}