Go语言学习篇03

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)就是吧抽象出的字段和对字段的操作(方法)封装在一起,数据被保护在内部,
    • 程序的其它包只能通过被授权的操作(方法),才能对字段进行操作
  • 封装的理解和好处

    • 隐藏实现细节
    • 方法可以对数据进行验证,保证安全合理

    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.11个参数是float64类型,值是12.3452个参数是string类型,值是Carter
第3个参数是Student类型,值是{
    
    }4个参数是Student指针类型,值是<nil>

猜你喜欢

转载自blog.csdn.net/IT_Carter/article/details/110297584