*本文笔记参考:b站【尚硅谷】Golang入门到实战教程
案例:写一个简单的AMT程序,实现存取款和查询功能。
package main
import (
"fmt"
)
type Pay struct {
Account string
Pwd string
Money float64
}
func (pay *Pay) SaveMoney(num float64, pwd string) {
if pwd != pay.Pwd {
fmt.Println("密码输入错误")
} else {
pay.Money += num
fmt.Println("存款成功~, 您的余额为", pay.Money)
}
}
func (pay *Pay) TakeMoney(num float64, pwd string) {
if pwd != pay.Pwd {
fmt.Println("密码输入错误")
} else if num > pay.Money {
fmt.Println("余额不足!")
} else {
pay.Money -= num
fmt.Println("取款成功~, 您的余额为", pay.Money)
}
}
func (pay *Pay) SeeMoney(pwd string) {
if pwd != pay.Pwd {
fmt.Println("密码输入错误")
} else {
fmt.Println("余额为:", pay.Money)
}
}
func main() {
var pay = Pay{"nongye", "123456", 100}
var action int
var count float64
var pwdd string
Loop:
for {
fmt.Print("请输入要执行的操作:\n1:存款\n2:取款\n3:查询\n4:退出\n")
fmt.Scanln(&action)
switch action {
case 1:
fmt.Print("请输入要存的金额:")
fmt.Scanln(&count)
fmt.Print("请输入密码:")
fmt.Scanln(&pwdd)
pay.SaveMoney(count, pwdd)
case 2:
fmt.Print("请输入要取的金额:")
fmt.Scanln(&count)
fmt.Print("请输入密码:")
fmt.Scanln(&pwdd)
pay.TakeMoney(count, pwdd)
case 3:
fmt.Print("请输入密码:")
fmt.Scanln(&pwdd)
pay.SeeMoney(pwdd)
case 4:
break Loop
}
}
}
//输出
输入要执行的操作:
1:存款
2:取款
3:查询
4:退出
1
请输入要存的金额:200
请输入密码:123456
存款成功~, 您的余额为 300
请输入要执行的操作:
1:存款
2:取款
3:查询
4:退出
2
请输入要取的金额:50
请输入密码:123456
取款成功~, 您的余额为 250
请输入要执行的操作:
1:存款
2:取款
3:查询
4:退出
4
1、面向对象编程三大特性
1)封装
把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作。
(1)封装的优点
-
隐藏实现细节;
-
可以对数据进行验证,保证安全合理。
(2)如何体现封装
-
对结构体中的属性进行封装;
-
通过方法、包,实现封装。
(3)封装的实现步骤
-
将结构体、字段(属性)的首字母小写(不能导出了,其他包不能使用,类似private);
-
给结构体所在包提供一个工厂模式的函数,首字母大写,类似构造函数;
-
提供一个首字母大写的Set方法(类似其他语言的public),用于对属性判断并赋值;
-
提供一个首字母大写的Get方法(类似其他语言的public),用于获取属性的值。
//model/person.go
package model
import "fmt"
type person struct {
Name string
age int
salary int
}
//写一个工厂模式的函数,相当于构造函数
func NewPerson(name string) *person {
return &person{
Name: name,
}
}
//为了访问age和salary,编写一对Set***和Get***的方法
func (per *person) SetAge(age int) {
if age > 0 && age < 120 {
per.age = age
} else {
fmt.Println("年龄范围不正确")
}
}
func (per *person) GetAge() int {
return per.age
}
func (per *person) SetSalary(salary int) {
if salary > 0 {
per.salary = salary
} else {
fmt.Println("薪水范围不正确")
}
}
func (per *person) GetSalary() int {
return per.salary
}
//main/main.go
package main
import (
"code/test1/model"
"fmt"
)
func main() {
person := model.NewPerson("tom")
person.SetAge(18)
person.SetSalary(3000)
person.GetAge()
person.GetSalary()
fmt.Println(*person)
fmt.Println(person.Name, person.GetAge(), person.GetSalary())
}
//输出
{tom 18 3000}
tom 18 3000
(4)课堂练习
创建程序,在model包中定义Account结构体:在main函数中体会golang的封装性。1. Account结构体要求具有字段:账号(长度在6-10之间)、余额(必须>20)、密码(必须是6位);2. 通过Set***方法给Account的字段赋值。
//model/account.go
package model
import "fmt"
type account struct {
account string
balance int
pwd string
}
//写一个工厂模式的函数,相当于构造函数
func NewAccount(acc string, balance int, pwd string) *account {
if len(acc) < 6 || len(acc) > 10 {
fmt.Println("账号长度不合法")
return nil
}
if balance <= 20 {
fmt.Println("余额太少!")
return nil
}
if len(pwd) != 6 {
fmt.Println("密码长度不合法")
return nil
}
return &account{
account: acc,
balance: balance,
pwd: pwd,
}
}
//通过Set***给Account的字段赋值
func (acc *account) SetAccount(account string) {
if len(account) >= 6 && len(account) < 10 {
acc.account = account
} else {
fmt.Println("账号长度不合法")
}
}
func (acc *account) GetAccount() string {
return acc.account
}
func (acc *account) SetBalance(balance int) {
if balance > 20 {
acc.balance = balance
} else {
fmt.Println("余额太少!")
}
}
func (acc *account) GetBalance() int {
return acc.balance
}
func (acc *account) SetPwd(pwd string) {
if len(pwd) == 6 {
acc.pwd = pwd
} else {
fmt.Println("密码长度不合法")
}
}
func (acc *account) GetPwd() string {
return acc.pwd
}
//main/main.go
package main
import (
"code/test1/model"
"fmt"
)
func main() {
var acc = model.NewAccount("doudouhua", 50000, "127865")
fmt.Println(*acc)
acc.SetAccount("xiaolaohu")
acc.SetBalance(20000)
acc.SetPwd("123456")
fmt.Println(acc.GetAccount(), acc.GetBalance(), acc.GetPwd())
}
//输出
{doudouhua 50000 127865}
xiaolaohu 20000 123456
2)继承
继承可以解决代码复用的问题。
(1)继承的注意事项
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法,其他的结构体不需要重新定义这些属性和方法,只需嵌套一个匿名结构体即可。也就是说,在golang中,如果一个结构体嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的属性和方法,实现继承。
type Goods struct{
Name string
Price float64
}
type Book struct{
Goods //嵌套匿名结构体Goods
Writer string
}
func main() {
var book Book
book.Goods.Name = "golang"
book.Goods.Price = 76.5
book.Writer = "Tom"
fmt.Println(book)
}
//输出
{{golang 76.5} Tom}
book.Name = "golang" //等价于book.Goods.Name = "golang"
book.Price = 76.5 //等价于book.Goods.Price = 76.5
type Goods struct {
Name string
Price float64
}
type Book struct {
g Goods //嵌套有名结构体Goods
Writer string
}
func main() {
var book Book
book.g.Name = "golang" //访问Goods的Name方法时,需带上结构体的名字
book.g.Price = 76.5
book.Writer = "Tom"
fmt.Println(book)
}
type Goods struct {
Name string
Price float64
}
type Book struct {
Goods //嵌套匿名结构体Goods
Writer string
}
func main() {
book := Book{Goods{"tom", 76.5}, "jack"}
fmt.Println(book)
}
(2)多重继承
如果一个结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法。
为了保证代码的简洁性,尽量不要使用多重继承。
3)接口
(1)基本介绍
interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。等某个自定义类型要使用的时候,再根据具体情况把这些方法写出来。
(2)基本语法
type 接口名 interface{
method1(参数列表) 返回值列表
method2(参数列表) 返回值列表
...
}
func (t 自定义类型) method1(参数列表) 返回值列表{
//方法实现
}
func (t 自定义类型) method2(参数列表) 返回值列表{
//方法实现
}
-
接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低耦合的思想;
-
golang的接口不需要显示的实现。只要一个变量含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,golang中没有implement这样的关键字。
(3)接口的使用细节
package main
import (
"fmt"
)
type Ainterface interface {
say()
}
type stu struct {
}
func (s stu) say() {
fmt.Println("say hello!")
}
func main() {
var s stu //结构体变量,实现了say()方法,即实现了接口Ainterface
var a Ainterface = s
a.say()
}
package main
import (
"fmt"
)
type Ainterface interface {
say()
}
type integer int
func (s integer) say() {
fmt.Println("say hello!")
}
func main() {
var i integer //自定义数据类型integer,实现了say()方法,即实现了接口Ainterface
var a Ainterface = i
a.say()
}
package main
import (
"fmt"
)
type Ainterface interface {
say()
}
type Binterface interface {
hello()
}
type integer int
func (i integer) say() {
fmt.Println("say hello!")
}
func (i integer) hello() {
fmt.Println("hello hello!")
}
func main() {
//结构体变量,实现了say()方法和hello()方法,即实现了接口Ainterface和Binterface
var i integer
var a Ainterface = i
var b Binterface = i
a.say()
b.hello()
}
package main
import (
"fmt"
)
type Binterface interface {
test01()
}
type Cinterface interface {
test02()
}
type Ainterface interface {
Binterface
Cinterface
test03()
}
type Integer int
func (inter Integer) test01() {
fmt.Println("test01...")
}
func (inter Integer) test02() {
fmt.Println("test02...")
}
func (inter Integer) test03() {
fmt.Println("test03...")
}
func main() {
var i Integer
var a Ainterface = i
a.test01()
a.test02()
a.test03()
}
(4)接口的经典案例
sort.Sort(data interface)
package main
import (
"fmt"
"math/rand"
"sort"
"time"
)
//声明Hero结构体
type Hero struct {
Name string
Age int
}
//声明Hero结构体切片类型
type HeroSlice []Hero
//实现interface接口
func (hs HeroSlice) Len() int {
return len(hs)
}
func (hs HeroSlice) Less(i, j int) bool {
// return hs[i].Age < hs[j].Age //按照年龄排序
return hs[i].Name < hs[j].Name //按照姓名排序
}
func (hs HeroSlice) Swap(i, j int) {
hs[i], hs[j] = hs[j], hs[i]
}
func main() {
var heros HeroSlice
rand.Seed(time.Now().UnixNano())
for i := 0; i < 10; i++ {
hero := Hero{
Name: fmt.Sprintf("tom%d", rand.Intn(10)),
Age: rand.Intn(100),
}
heros = append(heros, hero)
}
for _, v := range heros {
fmt.Println(v)
}
sort.Sort(heros)
fmt.Println("排序后----------------")
for _, v := range heros {
fmt.Println(v)
}
}
//输出
{tom6 68}
{tom0 84}
{tom6 27}
{tom2 54}
{tom4 83}
{tom6 11}
{tom0 67}
{tom7 8}
{tom3 6}
{tom6 88}
排序后----------------
{tom0 67}
{tom0 84}
{tom2 54}
{tom3 6}
{tom4 83}
{tom6 11}
{tom6 68}
{tom6 27}
{tom6 88}
{tom7 8}
4)接口和继承的比较
package main
import (
"fmt"
)
//Monkey结构体
type Monkey struct {
Name string
}
func (this *Monkey) climbing() {
fmt.Println(this.Name, "生来会爬树")
}
//继承Monkey
type LittleMonkey struct {
Monkey
}
//实现接口,扩展LittleMonkey的功能
type BirdAble interface {
Flying()
}
func (this *LittleMonkey) Flying() {
fmt.Println(this.Name, "学会了飞行")
}
func main() {
// var monkey LittleMonkey
monkey := LittleMonkey{Monkey{"悟空"}}
monkey.climbing()
monkey.Flying()
}
//输出
悟空 生来会爬树
悟空 学会了飞行
-
实现接口是对继承机制的补充;
当A结构体继承了B结构体,A结构体自动继承B结构体的字段和方法,并可以直接使用;
当A结构体需要扩展功能,同时不希望破坏继承关系时,可以通过接口来实现。
-
接口和继承解决的问题不同;
继承的价值在于:解决代码的复用性和可维护性;
接口的价值在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
-
接口比继承更灵活;
-
接口在一定程度上实现代码解耦。
变量(实例)具有多种形态。多态是通过接口实现的。
1)多态参数
接收的变量不同,接口体现的形态不同。
2)多态数组
一个接口数组,可以存放结构体和结构体变量。
3、类型断言
1)基本使用
package main
import (
"fmt"
)
type Point struct {
x int
y int
}
func main() {
var a interface{} //空接口
point := Point{1, 2}
a = point //可以把任何类型的变量赋给空接口
b := a.(Point) //不可以直接把a赋给b,因为不能把接口作为Point类型变量使用;需要加上类型断言
fmt.Println(a, b) //{1 2} {1 2}
}
由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言。
b=a.(int)panic: interface conversion: interface {} is main.Point, not int
在进行断言时,带上检测机制,如果成功就ok;否则报错,但不panic:
func main() {
//带检测的类型断言
var a interface{} //空接口
point := Point{1, 2}
a = point //可以把任何类型的变量赋给空接口
b, ok := a.(int) //不可以直接把a赋给b,因为不能把接口作为Point类型变量使用;需要加上类型断言
if ok {
fmt.Println("convert success")
} else {
fmt.Println("convert fail")
}
fmt.Println(a, b)
}
//输出
convert fail
{1 2} 0
2)案例
(1)usb接口
定义一个Usb接口数组,可以存放Phone和Camera的结构体变量。Usb接口有Start()和Stop()两种方法,除了调用Usb接口声明的方法外,还需要调用Phone特有方法Call()。
package main
import (
"fmt"
)
type Usb interface {
Start()
Stop()
}
type Phone struct {
Name string
}
func (p Phone) Start() {
fmt.Println("手机开始工作...")
}
func (p Phone) Call() {
fmt.Println("手机打电话...")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作...")
}
type Camera struct {
Name string
}
func (p Camera) Start() {
fmt.Println("相机开始工作...")
}
func (p Camera) Stop() {
fmt.Println("相机停止工作...")
}
type Computer struct {
Name string
}
func (computer Computer) Working(usb Usb) {
usb.Start()
//如果usb指向Phone结构体变量,还需要调Call()方法
if phone, ok := usb.(Phone); ok {
phone.Call()
}
usb.Stop()
}
func main() {
var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"oppo"}
usbArr[2] = Camera{"sony"}
fmt.Println(usbArr)
var computer Computer
for _, v := range usbArr {
computer.Working(v)
fmt.Println("----------------------")
}
}
//输出
[{vivo} {oppo} {sony}]
手机开始工作...
手机打电话...
手机停止工作...
----------------------
手机开始工作...
手机打电话...
手机停止工作...
----------------------
相机开始工作...
相机停止工作...
----------------------
(2)写一个函数,循环判断传入参数的类型
//循环判断传入参数的类型
func TypeJudge(items ...interface{}) {
for index, v := range items {
switch v.(type) {
case bool:
fmt.Printf("第%v个参数是bool类型,值是%v\n", index, v)
case float32:
fmt.Printf("第%v个参数是float32类型,值是%v\n", index, v)
case float64:
fmt.Printf("第%v个参数是float64类型,值是%v\n", index, v)
case int, int32, int64:
fmt.Printf("第%v个参数是int类型,值是%v\n", index, v)
case string:
fmt.Printf("第%v个参数是string类型,值是%v\n", index, v)
case Student:
fmt.Printf("第%v个参数是Student类型,值是%v\n", index, v)
case *Student:
fmt.Printf("第%v个参数是*Student类型,值是%v\n", index, v)
default:
fmt.Printf("第%v个参数是bool,float,int,string以外的类型,值是%v\n", index, v)
}
}
}
type Student struct {
Name string
Age int
}
func main() {
n1 := 10
n2 := 10.5
n3 := "hello"
n4 := [3]int{1, 2, 3}
n5 := Student{"Tom", 13}
n6 := &Student{"Mary", 12}
TypeJudge(n1, n2, n3, n4, n5, n6)
}
//输出
第0个参数是int类型,值是10
第1个参数是float64类型,值是10.5
第2个参数是string类型,值是hello
第3个参数是bool,float,int,string以外的类型,值是[1 2 3]
第4个参数是Student类型,值是{Tom 13}
第5个参数是*Student类型,值是&{Mary 12}