


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

package mainimport "fmt"type Account struct {Name stringPwd stringBalance 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")}

对上面代码的要求
Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不一 样,下面我们一一为同学们进行详细的讲解 Golang 的三大特性是如何实现的。
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其 它包只有通过被授权的操作(方法),才能对字段进行操作

将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)
给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值
func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {
//加入数据验证的业务逻辑
var.字段 = 参数
}
提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值
func (var 结构体类型名) GetXxx() {
return var.age;
}
看一个案例
请大家看一个程序(person.go),不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验 证。设计: model 包(person.go) main 包(main.go 调用 Person 结构体)
代码实现
model/person.go
package modelimport "fmt"type person struct {Name stringage 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 mainimport ("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())
}
model
package accountsimport "fmt"type Account struct{username stringbalance intpwd 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 += moneyfmt.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 -= moneyfmt.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 mainimport ("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.Accountaccount1.SetUsername("田毅")account1.SetBalance(10000)account1.SetPwd("888888")var account2 accounts.Accountaccount2.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 factoryimport "fmt"type account struct {accountNo stringpwd stringbalance 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 += moneyfmt.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 -= moneyfmt.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函数中测试
一个小问题,看个学生考试系统的程序 extends01.go,提出代码复用的问题

走一下代码
package mainimport ("fmt"
)//编写一个学生考试系统
//小学生
type Pupil struct {Name stringAge intScore 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 stringAge intScore 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()
}
继承可以解决代码复用,让我们的编程更加靠近人类思维。 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的
Student),在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 Student 匿名结构体即可。 [画 出示意图]
也就是说:在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访 问匿名结构体的字段和方法,从而实现了继承特性。
type Goods struct {
Name string Price int
}
type Book struct {
Goods //这里就是嵌套匿名结构体
Goods Writer string
}
案例
我们对 extends01.go 改进,使用嵌套匿名结构体的方式来实现继承特性,请大家注意体会这样编程的好处
代码实现
package mainimport ("fmt"
)//编写一个学生考试系统
type Student struct {Name stringAge intScore 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 = 8pupil.testing()pupil.Student.SetScore(70)pupil.Student.ShowInfo()graduate := &Graduate{}graduate.Student.Name = "mary~"graduate.Student.Age = 28graduate.testing()graduate.Student.SetScore(90)graduate.Student.ShowInfo()
}
结构体可以使用嵌套匿名结构体所有的字段和方法,
即:首字母大写或者小写的字段、方法, 都可以使用。【举例说明】

匿名结构体字段访问可以简化,如图






结构体的匿名字段是基本数据类型,如何访问, 下面代码输出什么

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

多重继承细节说明

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

这样的设计需求在 Golang 编程中也是会大量存在的,我曾经说过,一个程序就是一个世界,在现实世
界存在的情况,在程序中也会出现。我们用程序来模拟一下前面的应用场景。
代码实现
package mainimport ("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)
}
说明: 上面的代码就是一个接口编程的快速入门案例。
interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个
自定义类型(比如结构体 Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)

















实现对 Hero 结构体切片的排序: sort.Sort(data Interface)
package mainimport ("fmt""math/rand""sort"
)//1.声明 Hero 结构体
type Hero struct {Name stringAge 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 stringAge intScore 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].Agest[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 HeroSlicefor 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.Sortsort.Sort(heroes)fmt.Println("-----------排序后------------")//看看排序后的顺序for _, v := range heroes {fmt.Println(v)}i := 10j := 20i, j = j, ifmt.Println("i=", i, "j=", j) // i=20 j = 10var students StudentSlicefor 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 从大到小排序!!
大家听到现在,可能会对实现接口和继承比较迷茫了, 这个问题,那么他们究竟有什么区别呢





继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
接口比继承更加灵活,继承是满足 is - a的关系,而接口只需满足 like - a的关系。
变量(实例)具有多种形态。面向对象的第三大特征,在 Go 语言,多态特征是通过接口实现的。可 以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
在前面的 Usb 接口案例,Usb usb ,既可以接收手机变量,又可以接收相机变量,就体现了 Usb 接 口 多态特性。[点明]



案例说明:
package mainimport ("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]UsbusbArr[0] = Phone{"vivo"}usbArr[1] = Phone{"小米"}usbArr[2] = Camera{"尼康"}fmt.Println(usbArr)usbArr[0].Start()usbArr[0].Stop()
}

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言,

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

给 Phone结构体增加一个特有的方法 call(), 当 Usb 接口接收的是 Phone 变量时,还需要调用 call 方法,
走代码:
package mainimport ("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]UsbusbArr[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 Computerfor _,v:= range usbArr{computer.Working(v)fmt.Println()}fmt.Println(usbArr)
}
写一函数,循环判断传入参数的类型:


在前面代码的基础上,增加判断 Student类型和 *Student 类型
package mainimport ("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.1var n2 float64 = 2.3var n3 int32 = 30var name string = "tom"address := "北京"n4 := 300n5 :=Student{}n6:=&Student{}TypeJudge(n1, n2, n3, name, address, n4,n5,n6)
}