*本文笔记参考: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)封装的优点

  1. 隐藏实现细节;

  2. 可以对数据进行验证,保证安全合理。

(2)如何体现封装

  1. 对结构体中的属性进行封装;

  2. 通过方法、包,实现封装。

(3)封装的实现步骤

  1. 将结构体、字段(属性)的首字母小写(不能导出了,其他包不能使用,类似private);

  2. 给结构体所在包提供一个工厂模式的函数,首字母大写,类似构造函数;

  3. 提供一个首字母大写的Set方法(类似其他语言的public),用于对属性判断并赋值;

  4. 提供一个首字母大写的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(参数列表) 返回值列表{
    //方法实现
}
  1. 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低耦合的思想;

  2. 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()
}
//输出
悟空 生来会爬树
悟空 学会了飞行
  1. 实现接口是对继承机制的补充;

    当A结构体继承了B结构体,A结构体自动继承B结构体的字段和方法,并可以直接使用;

    当A结构体需要扩展功能,同时不希望破坏继承关系时,可以通过接口来实现。

  2. 接口和继承解决的问题不同;

    继承的价值在于:解决代码的复用性和可维护性;

    接口的价值在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。

  3. 接口比继承更灵活;

  4. 接口在一定程度上实现代码解耦。

2、多态

变量(实例)具有多种形态。多态是通过接口实现的。

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}