介绍

这个是在B站上看边看视频边做的笔记,这一章是Glang面向对象编程

这一章内容较多,内容有Go语言的结构体是什么,怎么声明;Golang方法的调用和声明;go语言面向对象实例,go语言工厂模式;golang面向对象的三大特性:继承、封装、多态,面向对象编程应用实例,golang的接口介绍,类型断言

配套视频自己去B站里面搜【go语言】,最高的播放量就是

里面的注释我写的可能不太对,欢迎大佬们指出╰(°▽°)╯

(八)、面向对象编程

1.看一个问题

在这里插入图片描述

2.使用现有技术解决

package mainfunc main() {/*张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太没有这只猫猫。*///1.使用变量处理// var cat1Name string = "小白"// var cat1Age int = 3// var cat1Color string = "白色"// var cat2Name string = "小花"// var cat2Age int = 100// var cat2Color string = "花色"//2.使用数组处理// var catNames [2]string = [...]string{"小白", "小花"}// var catAge [2]int = [...]int{3, 100}// var catColor [2]string = [...]string{"白色", "花色"}//3.使用map解决}


3.现有技术解决的缺点分析

  1. 使用变量或者数组来解决养猫的问题,不利于数据的管理和维护。因为名字,年龄,颜色都是属于一只猫,但是这里是分开保存。
  2. 如果我们希望对一只猫的属性(名字、年龄,颜色)进行操作(绑定方法), 也不好处理。
  3. 引出我们要讲解的技术–> 结构体


4.Golang 语言面向对象编程说明

  1. Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别并不是纯粹的面向对象语言。所以我们说Golang 支持面向对象编程特性是比较准确的。

  2. Golang 没有类(class),Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解Golang 是基于struct 来实现OOP 特性的。

  3. Golang 面向对象编程非常简洁,去掉了传统OOP 语言的继承方法重载构造函数和析构函数隐藏的this 指针等等

  4. Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP 语言不一样,比如继承:Golang 没有extends 关键字,继承是通过匿名字段来实现。

  5. Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在Golang 中面向接口编程是非常重要的特性。



一、结构体

在这里插入图片描述

对上图的说明

  1. 将一类事物的特性提取出来(比如猫类), 形成一个新的数据类型, 就是一个结构体。
  2. 通过这个结构体,我们可以创建多个变量(实例/对象)
  3. 事物可以猫类,也可以是Person , Fish 或是某个工具类。。。

在这里插入图片描述

package mainimport "fmt"type monster struct {Name  string   `json:"name"`Age   int      `json:"age"`Skill []string `json:"skill"`
}func main() {monster := monster{Name: "哈哈",Age:  100,}// monster.Skill = make([]string, 2, 6)monster.Skill = append(monster.Skill[:], "武器1")monster.Skill = append(monster.Skill[:], "武器2")monster.Skill = append(monster.Skill[:], "武器3")// monster.Skill[1] = "武器2"fmt.Println(monster)
}

输出

{哈哈 100 [武器1 武器2 武器3]}


1.快速入门

package mainimport "fmt"//定义一个Cat结构体,将Cat的各个字段/属性信息,放入到Cat结构体进行管理
type Cat struct {Name  string //字符串模式是空Age   int    //int类型默认为0Color string //字符串模式是空Hobby string
}func main() {/*张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太没有这只猫猫。*///使用struct结构体来完成案例var cat1 Cat //创建一个cat的变量fmt.Println(cat1)//给结构体赋值cat1.Name = "小白"cat1.Age = 3cat1.Color = "白色"fmt.Println("猫的信息如下:")fmt.Println("Nmae =", cat1.Name)fmt.Println("Age =", cat1.Age)fmt.Println("Color =", cat1.Color)
}

通过上面的案例和讲解我们可以看出:

  • 结构体和结构体变量(实例)的区别和联系
    1. 结构体是自定义的数据类型,代表一类事物.
    2. 结构体变量(实例)是具体的,实际的,代表一个具体变量


2.结构体变量(实例)在内存的布局(重要!)

在这里插入图片描述



3.如何声明结构体

type 结构体名称 struct {field1 typefield2 type
}
type Student struct {Name string //字段Age int     //字段Score float32
}


4.字段/属性

package mainimport "fmt"//指针,slice,和map 的零值都是nil ,即还没有分配空间。
//如果需要使用这样的字段,需要先make,才能使用type Person struct {
Name   string
Age    int
Scores [5]float64
ptr    *int              //指针
slice  []int             //切片
map1   map[string]string //map
}func main() {
//定义结构体
var p1 Person
fmt.Println(p1) //{ 0 [0 0 0 0 0] <nil> [] map[]}if p1.ptr == nil {fmt.Println("ptr为空")
}if p1.slice == nil {fmt.Println("slice为空")
}if p1.map1 == nil {fmt.Println("map1为空")
}//使用slice,一定要make
p1.slice = make([]int, 10)
p1.slice[0] = 100
fmt.Println(p1.slice) //[100 0 0 0 0 0 0 0 0 0]//使用map,一定要make
p1.map1 = make(map[string]string)
p1.map1["key1"] = "1"
fmt.Println(p1.map1) //map[key1:1]fmt.Println(p1) //{ 0 [0 0 0 0 0] <nil> [100 0 0 0 0 0 0 0 0 0] map[key1:1]}}
package mainimport "fmt"type Monster struct {
Name string
Age  int
}func main() {
//不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,
//不影响另外一个,结构体是值类型
var monster1 Monster
monster1.Name = "牛魔王"
monster1.Age = 500monster2 := monster1  //这里因为是值拷贝 结构体是值类型,默认为拷贝
monster2.Name = "红孩儿" //不会影响monster1fmt.Println("monster1=", monster1) //monster1= {牛魔王 500}
fmt.Println("monster2=", monster2) //monster2= {红孩儿 500}
}

在这里插入图片描述



5.创建结构体变量和访问结构体字段

	//方式1var p1 Personp1.Name = "tom"p1.Age = 23fmt.Println(p1)
	//方式2p2 := Person{"mary", 20}fmt.Println(p2)
	//方式3var p3 *Person = new(Person) //因为p3是一个指针,因此标准的给字段方式是赋值(*p3).Name = "smith"         //标准写法(*p3).Age = 30fmt.Println(*p3)
	//方式4//也可以直接给值//var person *Person = &Person{"jak", 30}var person *Person = &Person{}//因为person是一个指针,因此标准的访问字段的方法是//(*person).Name = "scott"(*person).Name = "scott" //标准写法person.Name = "scott"    //简化写法(*person).Age = 16 //标准写法person.Age = 16    //简化写法fmt.Println(*person)


6.struct 类型的内存分配机制

  • 看一个思考题

在这里插入图片描述

p2.Name = tom p1.Name = 小明
  • 看下面代码,并分析原因

在这里插入图片描述

原因:

在这里插入图片描述

  • 看下面代码,并分析原因

在这里插入图片描述



7.结构体使用注意事项和细节

package mainimport "fmt"//结构体
type Point struct { //声明一个结构体x int //声明类型y int
}type Rect struct { //声明一个结构体leftUp, reghtDown Point //把Point分别传入给leftUp和reghtDown
}type Rect2 struct {leftUp, reghtDown *Point //把Point的指针地分别传入给leftUp和reghtDown}func main() {//r1有4个int,在内存中是连续分布//打印地址r1 := Rect{Point{1, 2}, Point{3, 4}} //调用结构体Rect,并把Point{1, 2}传入给leftUp,Point{3, 4}出入给reghtDownfmt.Printf("r1.leftUp.x 地址=%p\nr1.leftUp.y 地址=%p\n", &r1.leftUp.x, &r1.leftUp.y)fmt.Printf("r1.reghtDown.x 地址=%p\nr1.reghtDown.y 地址=%p\n", &r1.reghtDown.x, &r1.reghtDown.y)fmt.Println()//r2有两个*Point类型, 这个两个*point类型的本身地址也是连续的,//但是他们指向的地址不一定是连续r2 := Rect2{&Point{1, 2}, &Point{3, 4}}//本身地址fmt.Printf("r2.leftUp 本身地址=%p\nr2.reghtDown 本身地址=%p\n", &r2.leftUp, &r2.reghtDown)//指向地址不一定连续,fmt.Printf("r2.leftUp 指向地址=%p\nr2.reghtDown 指向地址=%p\n", r2.leftUp, r2.reghtDown)
}
r1.leftUp.x 地址=0xc00000c240
r1.leftUp.y 地址=0xc00000c248
r1.reghtDown.x 地址=0xc00000c250
r1.reghtDown.y 地址=0xc00000c258r2.leftUp 本身地址=0xc00005a250
r2.reghtDown 本身地址=0xc00005a258
r2.leftUp 指向地址=0xc0000140c0
r2.reghtDown 指向地址=0xc0000140d0

在这里插入图片描述



package mainimport "fmt"type A struct {Num int
}type B struct {Num int
}//结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
func main() {var a A           //a的类型是结构体Avar b B           //b的类型是结构体Bfmt.Println(a, b) //{0} {0}a = A(b) //对b进行强制转换,转换为结构体A,之所以能进行强转,是因为结构体的类型字段是一致的(名字、格式、类型)fmt.Println(a)
}


  1. 结构体进行type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转

在这里插入图片描述



package mainimport ("encoding/json""fmt"
)type Monster struct {Name  string `json:"name"` //`json:"name"`就是struct结构体的标签,使用json进行序列化时会输出标签Age   int    `json:"age"`Skill string `json:"skill"`
}func main() {//1.创建一个Monster变量monster := Monster{"牛魔王", 500, "野蛮冲撞"}//2.将monster变量序列化为json格式字符串//内置函数json.Marshal函数中使用反射jsonMonster, err := json.Marshal(monster)if err != nil {fmt.Println("转换错误", err)}fmt.Println("jsonStr", jsonMonster) //jsonStr [123 34 110 ......... 158 34 125]//需要转换为string类型fmt.Println("jsonStr", string(jsonMonster)) // jsonStr {"name":"牛魔王","age":500,"skill":"野蛮冲撞"}
}


二、方法

1.基本介绍

在某些情况下,我们要需要声明(定义)方法。

比如Person 结构体:除了有一些字段外( 年龄,姓名…)

Person 结构体还有一些行为比如:可以说话、跑步…,通过学习,还可以做算术题。

这时就要用方法才能完成。



Golang 中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct。



2.方法的声明和调用

type A struct {Num int
}
func (a A) test() {fmt.Println(a.Num)
}
package mainimport "fmt"type Person struct {Name string
}//给Person类型绑定一个方法
func (p Person) test() { //把结构Person绑定给p,并调用test()函数,此时test()为方法fmt.Println("test() name =", p.Name) //由于绑定过Person结构体
}func main() {var p Person //声明p的类型p.test()     //调用方法   test() name=p.Name = "tom" //tom赋值给p.Namep.test()       //调用方法   test() name = tom}


3.方法快速入门

package mainimport "fmt"type Person struct {Name string
}func (p Person) speak() {fmt.Println(p.Name, "是一个好人")
}func main() {var p Person //必须先声明pp.Name = "tom"p.speak()
}
package mainimport "fmt"type Person struct {Name string
}//给Person 结构体添加jisuan 方法,可以计算从1+..+1000 的结果, 说明方法体内可以函数一样,进行各种运算
func (p Person) jisuan() {res := 0for i := 0; i <= 1000; i++ {res += i}fmt.Println(p.Name, "计算的结果是=", res)
}func main() {var p Person //必须先声明pp.Name = "tom"p.speak()p.jisuan() //tom 计算的结果是= 500500
}
type Person struct {Name string
}
//给Person 结构体jisuan2 方法,该方法可以接收一个数n,计算从1+..+n 的结果
func (p Person) jisuan2(n int) {res := 0for i := 0; i <= n; i++ {res += i}fmt.Println(p.Name, "计算的结果是=", res)
}
func main() {var p Person //必须先声明pp.Name = "tom"p.jisuan2(10) //tom 计算的结果是= 55
}
type Person struct {Name string
}//给Person 结构体添加getSum 方法,可以计算两个数的和,并返回结果
func (p Person) getSum(n1 int, n2 int) int {return n1 + n2
}func main() {var p Person //必须先声明pp.Name = "tom"res := p.getSum(10, 20)fmt.Println(res) //30
}


4.方法的调用和传参机制原理:(重要!)

package mainimport "fmt"//1) 声明一个结构体Circle, 字段为radius
//2) 声明一个方法area 和Circle 绑定,可以返回面积。
//3) 提示:画出area 执行过程+说明type Circle struct {radius float64
}func (c Circle) area() float64 {return 3.14 * c.radius * c.radius //圆面积=圆周率×半径×半径
}func main() {//创建一个Circle变量var c Circlec.radius = 4.0res := c.area()fmt.Println(res) //50.24
}


5.方法的声明(定义)

func (recevier type) methodName(参数列表) (返回值列表){方法体return 返回值
}
recevier typereceiver typereceiverreturn


6.方法的注意事项和细节

package mainimport "fmt"type Circle struct {radius float64
}//为了提高效率,通常我们方法和结构体的指针类型绑定
func (c *Circle) area2() float64 {//因为c是指针,因此我们标准的访问其字段的方式是(*c).redius//标准写法 return 3.14 * (*c).radius * (*c).radius //圆面积=圆周率×半径×半径return 3.14 * c.radius * c.radius
}func main() {//创建一个Circle变量var c Circlec.radius = 5.0res2 := (&c).area2() //标准写法fmt.Println(res2)//编译器底层做了优化(&c).area2() 等价c.area()//因为编译器会自动的给加上&cres1 := c.area2()fmt.Println(res1)}
package mainimport "fmt"/*
Golang 中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,
都可以有方法,而不仅仅是struct, 比如int , float32 等都可以有方法
*/type integer intfunc (i integer) print() {fmt.Println("i =", i)
}//编写一个方法, 可以改变i的值
func (i *integer) change() {*i = *i + 1
}func main() {var i integer = 10i.print() //i = 10i.change()fmt.Println("i =", i) //i = 11
}
package mainimport "fmt"type Student struct {Name stringAge  int
}func (stu *Student) String() string { //赋值Student的指针给stustr := fmt.Sprintf("Name=[%v] Age=[%v]", (*stu).Name, (*stu).Age)return str
}func main() {//定义一个Student变量stu := Student{Name: "tom",Age:  20,}fmt.Println(stu) //不使用指针输出,使用默认方式输出 {tom 20}fmt.Println(&stu) //使用地址进行输出 Name=[tom] Age=[20]
}


7.方法的练习题

  1. 编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10*8 的矩形,在main 方法中调用该方法。
package mainimport "fmt"//编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10*8 的矩形,
//在main 方法中调用该方法。type MethodUtils struct {
}//给MethodUtils编写方法
func (mu MethodUtils) Print() {for j := 0; j < 10; j++ {for i := 0; i < 8; i++ {fmt.Printf("*")}fmt.Println()}}func main() {var mu MethodUtilsmu.Print()
}
  1. 编写一个方法,提供m 和n 两个参数,方法中打印一个m*n 的矩形
package mainimport "fmt"//编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10*8 的矩形,
//在main 方法中调用该方法。type MethodUtils struct {
}//给MethodUtils编写方法
func (mu MethodUtils) Print(n1 int, n2 int) {for j := 0; j < n1; j++ {for i := 0; i < n2; i++ {fmt.Printf("*")}fmt.Println()}}func main() {var mu MethodUtilsmu.Print(8, 10)
}
  1. 编写一个方法算该矩形的面积(可以接收长len,和宽width), 将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。

    在这里插入图片描述

  2. 编写方法:判断一个数是奇数还是偶数

    在这里插入图片描述

  3. 根据行、列、字符打印对应行数和列数的字符,比如:行:3,列:2,字符*,则打印相应的效果

  4. 定义小小计算器结构体(Calcuator),实现加减乘除四个功能

    实现形式1:分四个方法完成

    在这里插入图片描述

    在这里插入图片描述

    实现形式2:用一个方法搞定

    在这里插入图片描述



练习题

在这里插入图片描述



8.方法和函数区别

package mainimport "fmt"type Person struct {Name string
}func test01(p Person) {fmt.Println(p.Name)
}func test02(p *Person) {fmt.Println(p.Name)
}func main() {p := Person{"tom"}test01(p)test02(&p)
}
(p Person)(p *Person)


三、面向对象编程应用实例

1.步骤

  1. 声明(定义)结构体,确定结构体名
  2. 编写结构体的字段
  3. 编写结构体的方法


2.案例1

package mainimport "fmt"/*
1) 编写一个Student 结构体,包含name、gender、age、id、score 字段,分别为string、string、int、int、float64 类型。
2) 结构体中声明一个say 方法,返回string 类型,方法返回信息中包含所有字段值。
3) 在main 方法中,创建Student 结构体实例(变量),并访问say 方法,并将调用结果打印输出。
*/type Student struct {name   stringgender stringage    intid     intscore  float64
}func (student *Student) say() string {infoStr := fmt.Sprintf("student信息 name=[%v] gender=[%v] age=[%v] id=[%v] score=[%v]",student.name, student.gender, student.age, student.id, student.score,)return infoStr
}func main() {//测试//创建一个Student实例变量var stu = Student{name:   "tom",gender: "male",age:    18,id:     1000,score:  99.98,}fmt.Println(stu.say())
}


3.案例2

  1. 编写一个Dog 结构体,包含name、age、weight 字段
  2. 结构体中声明一个say 方法,返回string 类型,方法返回信息中包含所有字段值。
  3. 在main 方法中,创建Dog 结构体实例(变量),并访问say 方法,将调用结果打印输出。


4.案例3

  1. 编程创建一个Box 结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终端获取

  2. 声明一个方法获取立方体的体积。

  3. 创建一个Box 结构体变量,打印给定尺寸的立方体的体积

    在这里插入图片描述

    在这里插入图片描述



5.案例4

  1. 一个景区根据游人的年龄收取不同价格的门票,比如年龄大于等于18,收费20 元,其它情况门票免费.

  2. 请编写Visitor 结构体,根据年龄段决定能够购买的门票价格并输出

    在这里插入图片描述

    在这里插入图片描述



四、创建结构体变量时指定字段值

package mainimport "fmt"type Stu struct {Name stringAge  int
}func main() {//方式1//在创建结构体变量时,就直接指定字段的值var stu1 = Stu{"小明", 19}stu2 := Stu{"小米", 20}//在创建结构体变量时,把字段名和字段值写在一起var stu3 = Stu{Name: "jack",Age:  18,}var stu4 = Stu{Name: "mary",Age:  21,}fmt.Println(stu1, stu2, stu3, stu4) //{小明 19} {小米 20} {jack 18} {mary 21}
}
package mainimport "fmt"type Stu struct {Name stringAge  int
}func main() {//方式2 返回结构体的指针类型(!! !var stu5 = &Stu{"小王", 29}stu6 := &Stu{"小花", 28}//在创建结构体指针变量时,把字段名和字段值写在一起,这种写法,就不依赖字段的定义顺序.var stu7 = &Stu{Name: "小李",Age:  39,}stu8 := &Stu{ Name: "小李",Age:  39,}fmt.Println(stu5, stu6, stu7, stu8) //&{小王 29} &{小花 28} &{小李 39} &{小李 39}//&表示是地址符,使用*对原值进行取值fmt.Println(*stu5, *stu6, *stu7, *stu8) //{小王 29} {小花 28} {小李 39} {小李 39}}


五、工厂模式

1.说明

Golang 的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。

package modeltype Student struct {Name string...
}

因为这里的Student 的首字母S 是大写的,如果我们想在其它包创建Student 的实例(比如main 包),引入model 包后,就可以直接创建Student 结构体的变量(实例)。

type student struct {....}


2.工厂模式案例

package modeltype Student struct {Name  stringScore float64
}
package mainimport ("demo/28demo/02/model""fmt"
)func main() {//创建要给Student实例var stu = model.Student{Name:  "小明",Score: 86.25,}fmt.Println(stu)
}
package modeltype student struct {Name  stringScore float64
}//因为student结构体首字母是小写,因此是只能在mode1使用
//我们通过工厂模式来解决func NewStudent(n string, s float64) *student { //定义一个公开的方法,使用一个方法来接收。并把值返回return &student{Name:  n,Score: s,}
}
package mainimport ("demo/28demo/02/model""fmt"
)func main() {//因为student结构体是首字母小写,我们可以通过工厂模式来解决var stu = model.NewStudent("tom", 88.8)fmt.Println(*stu)fmt.Printf("name=%v score=%v", stu.Name, stu.Score)
}


3.思考题

思考一下,如果model 包的student 的结构体的字段Score 改成score,我们还能正常访问吗?又应该如何解决这个问题呢?

package modeltype student struct {Name  stringscore float64
}//如果score字段首字母小写,则,在其它包不可以直接方法,我们可以提供一个方法
func (s *student) GetScore() float64 {return s.score
}
package mainimport ("demo/28demo/02/model""fmt"
)func main() {//因为student结构体是首字母小写,我们可以通过工厂模式来解决var stu = model.NewStudent("tom", 88.8)fmt.Println(*stu)fmt.Printf("name=%v score=%v", stu.Name, stu.GetScore())
}


六、面向对象编程思想-抽象

1.抽象的介绍

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

在这里插入图片描述

package mainimport "fmt"//定义一个结构体
type Account struct {AccountNo stringPwd       stringBalance   float64
}//方法
//1.存款
func (account *Account) Deposite(useraccount string, pwd string, money float64) {// if useraccount != account.AccountNo {// 	fmt.Println("你输入的账号不正确")// 	return// }// //看下输入的密码是否正确// if pwd != account.Pwd {// 	fmt.Println("你输入的密码不正确")// 	return// }if money <= 0 {fmt.Println("你输入的金额不正确")return}account.Balance += moneyfmt.Println("存款成功")fmt.Printf("你的账号为=%v,你的余额为=%v\n", account.AccountNo, account.Balance)}//2.取款
func (account *Account) WithDraw(useraccount string, pwd string, money float64) {// if useraccount != account.AccountNo {// 	fmt.Println("你输入的账号不正确")// 	return// }// if pwd != account.Pwd {// 	fmt.Println("你输入的密码不正确")// 	return// }if money <= 0 || money > account.Balance { //取款小于等于0,你要取款的钱大于你存的钱fmt.Println("你输入的金额不正确")return}account.Balance -= moneyfmt.Println("取款成功")fmt.Printf("你的账号为=%v,你的余额为=%v\n", account.AccountNo, account.Balance)}//3.查询余额
func (account *Account) Query(useraccount string, pwd string) {// if useraccount != account.AccountNo {// 	fmt.Println("你输入的账号不正确")// 	return// }// if pwd != account.Pwd {// 	fmt.Println("你输入的密码不正确")// 	return// }fmt.Printf("你的账号为=%v,你的余额为=%v\n", account.AccountNo, account.Balance)
}func main() {account := Account{AccountNo: "icbc",Pwd:       "123456",Balance:   100.0,}var user stringvar password stringvar input stringfmt.Println("请输入账号:")fmt.Scanln(&user) //用户输入fmt.Println("请输入密码:")fmt.Scanln(&password)if user != account.AccountNo || password != account.Pwd {fmt.Println("您输入的账号或密码有误")return} else {fmt.Printf("请输入您要办理的业务:\n1.存款\n2.取款\n3.查看余额\n")fmt.Scanln(&input)switch input {case "1", "存款", "1.存款":account.Deposite(user, password, 123)case "2", "取款", "2.取款":account.WithDraw(user, password, 12.66)case "3", "查看余额", "3.查看余额":account.Query(user, password)default:fmt.Println("输入有误,请重新输入")}fmt.Println()}
}


七、面向对象编程三大特性-封装

1.基本介绍

Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP 语言不一样,下面我们进行详细的讲解Golang 的三大特性是如何实现的。



2.封装介绍

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作

在这里插入图片描述



3.封装的理解和好处

  1. 隐藏实现细节
  2. 提可以对数据进行验证,保证安全合理(Age)


4.如何体现封装

  1. 对结构体中的属性进行封装
  2. 通过方法,包实现封装


5.封装的实现步骤

func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {//加入数据验证的业务逻辑var.字段= 参数
}
func (var 结构体类型名) GetXxx() {return var.age;
}
  • 特别说明:在Golang 开发中并没有特别强调封装,这点并不像Java. 所以提醒学过java 的朋友,不用总是用java 的语法特性来看待Golang, Golang 本身对面向对象的特性做了简化的.


6.快速入门案例

package modelimport "fmt"type person struct {Name stringage  int     //其他包不能直接访问 年龄sal  float64 //其他包不能直接访问 薪水
}//写一个工厂模式的函数,相当于构造函数
func NwePerson(name string) *person { //让小写的结构体可以被其他包引用return &person{Name: name,}
}//为了访问age和sal 我们编写一 对SetXxx的方法和Getxx的方法
func (p *person) SetAge(age int) { //使用指针赋值给结构体中的年龄 存入值if age > 0 && age < 150 {p.age = age} else {fmt.Println("年龄范围不正确")}
}func (p *person) GetAge() int { //Get查看这个值 ,直接返回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 { //Get查看sal这个值并直接返回给GetSal()return p.sal
}
package mainimport ("demo/29demo/model""fmt"
)func main() {p := model.NwePerson("小明") //声明model并把小明赋值给结构体person的namefmt.Println(*p)            //使用*取原值进行输出p.SetAge(18) //set存入age值p.SetSal(5000)fmt.Println(*p)fmt.Printf("name=%v age=%v sal=%v", p.Name, p.GetAge(), p.GetSal()) //调用方法并输出
}


7.练习

package modelimport "fmt"type account struct {user    string  //长度在6-10之间pwd     string  //必须是6位balance float64 //必须>20
}//小写的结构体可以被其他包引用
func NewAccount(user string, pwd string, balance float64) *account {fmt.Println(len(user))if len(user) < 6 || len(user) > 10 {fmt.Println("输入的范围有误,长度必须在6-10之间")return nil}if len(pwd) != 6 {fmt.Println("输入的范围有误,长度必须是6位")return nil}if balance < 20 {fmt.Println("输入的范围有误,余额(必须>20)")return nil}return &account{user:    user,pwd:     pwd,balance: balance,}
}// func (a *account) SetUser(user string) {length := len(user)if 6 <= length && length <= 10 {a.user = user} else {fmt.Println("输入的范围有误,长度必须在6-10之间")}
}func (a *account) SetPwd(pwd string) {length := len(pwd)if length == 6 {a.pwd = pwd} else {fmt.Println("输入的范围有误,长度必须是6位")}
}func (a *account) SetBalance(balance float64) {if balance >= 20 {a.balance = balance} else {fmt.Println("输入的范围有误,余额(必须>20)")}
}func (a *account) GetUser() string {return a.user
}func (a *account) GetPwd() string {return a.pwd
}func (a *account) GetBalance() float64 {return a.balance
}
package mainimport ("demo/29demo/01/model""fmt"
)/*
1) 创建程序,在`model` 包中定义`Account`结构体:在`main` 函数中体会Golang 的封装性。
2) `Account` 结构体要求具有字段:账号(长度在6-10 之间)、余额(必须>20)、密码(必须是六
3) 通过SetXxx 的方法给Account 的字段赋值。
4) 在main 函数中测试
*/func main() {account := model.NewAccount("小红帽", "123456", 123)if account != nil {fmt.Println("创建成功", *account)} else {fmt.Println("创建失败")}account.SetUser("小明")fmt.Println(*account)fmt.Println(account.GetUser())
}


八、面向对象编程三大特性-继承

1.引出继承的必要性

一个小问题,看个学生考试系统的程序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) SteScore(score int) {p.Score = score
}func (p *Pupil) Tesing() {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) SteScore(score int) {p.Score = score
}func (p *Graduate) Tesing() {fmt.Println("大学生正在考试中")
}func main() {//测试var pupil = &Pupil{Name: "tom",Age:  10,}pupil.Tesing()pupil.SteScore(89)pupil.ShowInfo()var graduate = &Graduate{Name: "mary",Age:  20,}graduate.Tesing()graduate.SteScore(76)graduate.ShowInfo()
}

对上面代码的小结

  1. Pupil 和Graduate 两个结构体的字段和方法几乎,但是我们却写了相同的代码, 代码复用性不强
  2. 出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展
  3. 解决方法-通过继承方式来解决


2.继承基本介绍和示意图

继承可以解决代码复用,让我们的编程更加靠近人类思维。

当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的Student),在该结构体中定义这些相同的属性和方法。

其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个Student 匿名结构体即可。[示意图]

在这里插入图片描述

在Golang 中,如果一个struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。



3.嵌套匿名结构体的基本语法

type Goods struct {Name stringPrice int
}
type Book struct {Goods //这里就是嵌套匿名结构体GoodsWriter string
}


4.快速入门案例

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) SteScore(score int) {stu.Score = score
}///
///type Pupil struct {Student //嵌入Student匿名结构体
}type Graduate struct {Student //嵌入Student匿名结构体
}//保留特有的方法
func (p *Pupil) Tesing() {fmt.Println("小学生正在考试中")
}func (p *Graduate) Tesing() {fmt.Println("大学生正在考试中")
}///func main() {//当我们对结构体嵌入了匿名结构体后使用方法会发生变化//方式1pupil := &Pupil{}pupil.Student.Name = "tom"pupil.Student.Age = 10pupil.Tesing()pupil.Student.SteScore(75)pupil.Student.ShowInfo()graduate := &Graduate{}graduate.Student.Name = "mary"graduate.Student.Age = 20graduate.Tesing()graduate.Student.SteScore(68)graduate.Student.ShowInfo()fmt.Println()//方式2......pupil.Name = "tom~"pupil.Age = 100pupil.Tesing()pupil.SteScore(750)pupil.ShowInfo()graduate.Name = "mary~"graduate.Age = 200graduate.Tesing()graduate.SteScore(680)graduate.ShowInfo()}


5.继承的深入讨论

  1. 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。【举例说明】

  2. 匿名结构体字段访问可以简化

package mainimport "fmt"type A struct { //公开Name string //公开age  int    //私有
}func (a *A) SayOk() {fmt.Println("A SayOk", a.Name)
}func (a *A) hello() {fmt.Println("A hello", a.age)
}type B struct {A
}func main() {var b Bb.A.Name = "tom"b.A.age = 19b.A.SayOk()b.A.hello()//上面写法可以简化b.Name = "smith"b.age = 20b.SayOk()b.hello()
}
b.Name
  1. 结构体匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分【举例说明】
package mainimport "fmt"type A struct { //公开Name string //公开age  int    //私有
}func (a *A) SayOk() {fmt.Println("A SayOk", a.Name)
}func (a *A) hello() {fmt.Println("A hello", a.age)
}///
type B struct {AName string
}func (b *B) SayOk() {fmt.Println("B SayOk", b.Name)
}///func main() {var b Bb.Name = "jack"    //使用就近原则,导入jackb.A.Name = "scott" //使用匿名结构体全路径b.age = 100b.SayOk()   //使用就近原则b.A.SayOk() //使用匿名结构体全路径b.hello()
}
  1. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。【举例说明】
package mainimport "fmt"type A struct {Name stringAge  int
}type B struct {Name  stringScore float64
}//C同时嵌入了2个不同的结构体,此时就必须明确指定匿名结构体名字
type C struct {AB
}func main() {var c C//如果c没有Name字段,而A和B有Name, 这时就必须通过指定置名结构体名字来区分//所以 c.Name 就会包编译错误, 这个规则对方法也是一 样的!c.A.Name = "tom"fmt.Println(c.A.Name)}
package mainimport "fmt"type A struct {Name stringAge  int
}type D struct {a A //有名结构体 组合关系}func main() {如果D中是一个有名结构体,则访问有名结构体的字段时,就必须带上有名结构体的名字var d D//d.Name = "jack" //直接去d里面找Name,发现没有叫做Name的就直接报错,d.a.Name = "jack" //因为A是有名结构体,需要完整的路径}
package mainimport "fmt"type Goods struct {Name  stringPrice float64
}type Brend struct {Name    stringAddrees string
}type Tv1 struct {GoodsBrend
}type Tv2 struct { //使用指针类型*Goods*Brend
}func main() {//嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值tv1 := Tv1{Goods{"电视机001", 5999.09}, Brend{"海尔", "山东"}} //方式1fmt.Println(tv1)tv2 := Tv1{ //方式2Goods{Name:  "电视机002",Price: 5000.09,},Brend{Name:    "海尔",Addrees: "山东",},}fmt.Println(tv2)//使用指针tv3 := Tv2{&Goods{"电视机003", 6999.09}, &Brend{"创维", "河南"}}fmt.Println(*tv3.Goods, *tv3.Brend)tv4 := Tv2{&Goods{Name:  "电视机004",Price: 4000.99,},&Brend{Name:    "创维",Addrees: "河南",},}fmt.Println(*tv4.Goods, *tv4.Brend)
}


6.练习

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

package mainimport "fmt"type Monster struct {Name stringAge  int
}type E struct {Monsterint     //匿名字段时基本数据类型n   int //如果有了1个int,后面int必须有名称
}func main() {var e Ee.Name = "牛魔王"e.Age = 500e.int = 20e.n = 10fmt.Println(e) //{{牛魔王 500} 20 10}
}

说明

  1. 如果一个结构体有 int 类型的匿名字段,就不能第二个。
  2. 如果需要有多个 int 的字段,则必须给 int 字段指定名


7.面向对象编程-多重继承

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

  • 案例

    通过一个案例来说明多重继承使用

在这里插入图片描述

  • 多重继承细节说明

    1. 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。【案例演示】

      在这里插入图片描述

    2. 为了保证代码的简洁性,建议大家尽量不使用多重继承



九、接口(interface)

1.基本介绍

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

在这里插入图片描述



2.接口快速入门

这样的设计需求在Golang 编程中也是会大量存在的,前面说过,一个程序就是一个世界,在现实世界存在的情况,在程序中也会出现。我们用程序来模拟一下前面的应用场景。

package mainimport "fmt"//声明一个接口
type Usb interface {//声明了2个没有实现的方法Start()Stop()
}//让手机Phone实现Usb接口的方法
type Phone struct {
}func (p Phone) Start() {fmt.Println("启动手机...")
}
func (p Phone) Stop() {fmt.Println("关闭手机...")
}//让相机Camera实现Usb接口的方法
type Camera struct {
}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) { //2.接收到phone//通过usb接口变量来调用start和stop方法usb.Start() //3.寻找关于phone的Start方法 输出:启动手机...usb.Stop()  //4.寻找关于phone的Stop方法 输出:关闭手机...// //上面等同于下面这个// phone := Phone{}// phone.Start()
}func main() {//测试,先创建结构体变量computer := Computer{}phone := Phone{}camera := Camera{}computer.Working(phone) //1.传入phone参数fmt.Println()computer.Working(camera)
}


3.基本语法

interface 类型可以定义一组方法,但是这些不需要实现。并且interface 不能包含任何变量。到某个自定义类型(比如结构体Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。

在这里插入图片描述

implement


4.接口使用的应用场景

在这里插入图片描述

在这里插入图片描述



5.注意事项和细节

package mainimport "fmt"type AInterface interface { //定义一个接口Say()
}type Stu struct { //定义一个结构体Name string
}func (stu Stu) Say() { //定义一个方法体fmt.Println("Stu Say()")
}func main() { //调用接口var stu Stu //结构体变量var a AInterface = stustu.Say()a.Say()
}
package mainimport "fmt"type AInterface interface { //定义一个接口Say()
}type BInterface interface { //定义一个接口Hello()
}type Monster struct { //定义struct结构体
}func (m Monster) Hello() { //定义Hello并调用Monster结构体fmt.Println("Monster Hello()")
}func (m Monster) Say() {fmt.Println("Monster Say()")
}func main() { //调用接口//Monster实现 了AInterface 和BInterfacevar monster Monstervar a AInterface = monstervar b BInterface = monstera.Say()b.Hello()
}
package maintype BInterface interface {test01()
}type CInterface interface {test02()
}type AInterface interface { //如果要实现A接口,必须把A接口所有的反法全部实现BInterfaceCInterfacetest03()
}type Stu struct {
}//如果需要实现AInterface,就需要将BInterface CInterface的方法都实现
func (stu Stu) test01() {} //缺一不可func (stu Stu) test02() {} //缺一不可func (stu Stu) test03() {} //缺一不可//如果需要实现AInterface,就需要将BInterface CInterface的方法都实现
func main() {var stu Stuvar a AInterface = stuvar b BInterface = stua.test01()b.test01()
}
package mainimport "fmt"type T interface{} //定义一个空接口type Stu struct { //定义一个结构体
}func main() {var nume float64 = 8.0//方式1var stu Stu    //调用结构体var t1 T = stu //声明位空接口类型并调用结构体t1 = numefmt.Println(t1)//方式2var t2 interface{} = stu //定义一个空接口类型,并传给t2t2 = numefmt.Println(t2)
}


6.接口编程的最佳实践

package mainimport ("fmt""math/rand""sort"
)// 实现对Hero 结构体切片的排序: sort.Sort(data Interface)//1.声明Hero结构体
type Hero struct {Name stringAge  int
}//2.声明一个Hero结构体切片类型
type HeroSlice []Hero //声明Hero切片//3.interface接口
func (hs HeroSlice) Len() int {return len(hs) //遍历数组的数量,并返回
}//Less方法就是决定你使用什么标准进行排序
//3.1.按Hero年龄进行从小到大进行排序
func (hs HeroSlice) Less(i, j int) bool {return hs[i].Age < hs[j].Age //判断大小,返回bool类型//修改对Name的排序//return hs[i].Name < hs[j].Name
}//3.2把值进行交换
func (hs HeroSlice) Swap(i, j int) {hs[i], hs[j] = hs[j], hs[i]
}func main() {//先定义一个数组/切片var intSlice = []int{0, -1, 10, 7, 90}//要求对interface进行排序fmt.Println(intSlice)sort.Ints(intSlice)fmt.Println(intSlice)//========================================//对结构体切片进行排序var heros HeroSlice//生成英雄herofor i := 0; i < 10; i++ {hero := Hero{ //调用Hero结构体,传入英雄和英雄的年龄Name: fmt.Sprintf("英雄%d", rand.Intn(100)), //使用rand.Intn生成0到100的伪随机数Age:  rand.Intn(100),                      //使用rand.Intn生成0到100的伪随机数}heros = append(heros, hero) //使用append对切片进行动态追加,并赋值给heros}//看看排序前的顺序fmt.Println(heros)for _, v := range heros { //使用range对heros进行遍历fmt.Println(v)}fmt.Println("-----排序后-----")//调用sort.Sortsort.Sort(heros)//看看排序后的顺序for _, v := range heros {fmt.Println(v)}
}
//1.声明Student 结构体
type Student struct{Name stringAge intScore float64
}
//将Student 的切片,安Score 从大到小排序!!


7.实现接口vs 继承

package mainimport ("fmt"
)/
//猴子Monkey结构体
type Monkey struct {Name string
}func (ithis *Monkey) Climbing() { //猴子Monkey的方法fmt.Println(ithis.Name, "生来会爬树")
}
//
//小猴子LittleMonkey结构体
type LittleMonkey struct {Monkey //继承猴子Monkey
}
//
//声明鸟的能力BirdAble接口
type BirdAble interface {Flying() //飞翔Flying方法
}//让小猴子LittleMonkey实现鸟的能力BirdAble
func (ithis *LittleMonkey) Flying() {fmt.Println(ithis.Name, "通过学习,会飞翔了")
}
//
//声明鱼的能力FishAble接口
type FishAble interface {Swimming() //游泳Swimming方法
}//让小猴子LittleMonkey实现鱼的能力FishAble
func (ithis *LittleMonkey) Swimming() {fmt.Println(ithis.Name, "学会了游泳")
}
/func main() {//创建一个LittleMonkey实例monkey := LittleMonkey{Monkey{Name: "悟空",},}monkey.Climbing() //调用Monkey的方法monkey.Flying()   //调用鸟的能力BirdAble接口,实现飞翔Flying方法monkey.Swimming() //调用鱼的能力BirdAble接口,实现游泳Swimming方法
}


  • 实现接口可以看作是对继承的一种补充

    在这里插入图片描述



  • 接口和继承解决的解决的问题不同

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

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



PersonStudentBirdAbleLittleMonkey


  • 接口在一定程度上实现代码解耦




十、面向对象编程三大特性-多态

1.基本介绍

变量(实例)具有多种形态。面向对象的第三大特征,在Go 语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。



2.快速入门

在前面的Usb 接口案例,Usb usb ,既可以接收手机变量,又可以接收相机变量,就体现了Usb 接口多态特性。

package mainimport "fmt"//声明一个接口
type Usb interface {//声明了2个没有实现的方法Start()Stop()
}//让手机Phone实现Usb接口的方法
type Phone struct {
}func (p Phone) Start() {fmt.Println("启动手机...")
}
func (p Phone) Stop() {fmt.Println("关闭手机...")
}//让相机Camera实现Usb接口的方法
type Camera struct {
}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) { //2.接收到phone//通过usb接口变量来调用start和stop方法usb.Start() //3.寻找关于phone的Start方法 输出:启动手机...usb.Stop()  //4.寻找关于phone的Stop方法 输出:关闭手机...// //上面等同于下面这个// phone := Phone{}// phone.Start()
}func main() {//测试,先创建结构体变量computer := Computer{}phone := Phone{}camera := Camera{}computer.Working(phone) //1.传入phone参数fmt.Println()computer.Working(camera)
}

在这里插入图片描述



3.接口体现多态的两种形式

package mainimport "fmt"///
//声明一个接口
type Usb interface {//声明了2个没有实现的方法Start()Stop()
}//////
//让手机Phone实现Usb接口的方法
type Phone struct {Name string
}func (p Phone) Start() {fmt.Println("启动手机...")
}
func (p Phone) Stop() {fmt.Println("关闭手机...")
}func (p Phone) Call() {fmt.Println("可以使用电话...")
}//////
//让相机Camera实现Usb接口的方法
type Camera struct {Name string
}func (c Camera) Start() {fmt.Println("启动相机...")
}func (c Camera) Stop() {fmt.Println("关闭相机...")
}///type Computer struct {
}func (computer Computer) Working(usb Usb) {usb.Start()usb.Stop()//如果usb是指向Phone结构体变量,则还需要调用Call方法//类型断言if phone, ok := usb.(Phone); ok { //判断usb的参数是否为phone,phone.Call() //当等于时就执行Call()方法,否则跳过}
}func main() {//定义一个Usb接口数组,可以存放Phone和Camear的结构体变量//这里就体现出多态数组var usbArr [3]Usb   //声明一个数组,里面可以存放3个元素fmt.Println(usbArr) //[<nil> <nil> <nil>]//使用接口实现多个数据类型usbArr[0] = Phone{"vivo"}usbArr[1] = Phone{"小米"}usbArr[2] = Camera{"索尼"}fmt.Println(usbArr) //多态数组[{vivo} {小米} {索尼}]fmt.Println()var computer Computerfor _, v := range usbArr {computer.Working(v)fmt.Println()}
}


4.类型断言

在这里插入图片描述

1.基本介绍

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

在这里插入图片描述

package mainimport "fmt"func main() {//带检测的类型断言var x interface{}var b float32 = 1.1x = by, ok := x.(float64) //返回ok是否为ture或falseif ok {              //等同于ok == turefmt.Println("convert succes")fmt.Printf("y的类型是%T 值是%v", y, y)} else {fmt.Println("convert fail")}fmt.Println("继续执行")
}


2.类型断言的最佳实践

package mainimport "fmt"///
//声明一个接口
type Usb interface {//声明了2个没有实现的方法Start()Stop()
}//////
//让手机Phone实现Usb接口的方法
type Phone struct {Name string
}func (p Phone) Start() {fmt.Println("启动手机...")
}
func (p Phone) Stop() {fmt.Println("关闭手机...")
}func (p Phone) Call() {fmt.Println("可以使用电话...")
}//////
//让相机Camera实现Usb接口的方法
type Camera struct {Name string
}func (c Camera) Start() {fmt.Println("启动相机...")
}func (c Camera) Stop() {fmt.Println("关闭相机...")
}///type Computer struct {
}func (computer Computer) Working(usb Usb) {usb.Start()usb.Stop()//如果usb是指向Phone结构体变量,则还需要调用Call方法//类型断言if phone, ok := usb.(Phone); ok { //判断usb的参数是否为phone,phone.Call() //当等于时就执行phone.Call()方法,否则跳过}
}func main() {//定义一个Usb接口数组,可以存放Phone和Camear的结构体变量//这里就体现出多态数组var usbArr [3]Usb   //声明一个数组,里面可以存放3个元素fmt.Println(usbArr) //[<nil> <nil> <nil>]//使用接口实现多个数据类型usbArr[0] = Phone{"vivo"}usbArr[1] = Phone{"小米"}usbArr[2] = Camera{"索尼"}fmt.Println(usbArr) //多态数组[{vivo} {小米} {索尼}]fmt.Println()var computer Computerfor _, v := range usbArr {computer.Working(v)fmt.Println()}
}
package mainimport "fmt"//编写一个函数,可以判断输入的参数是什么类型
func TyeepJudge(items ...interface{}) { //定义一个...根据大小自动扩容的空接口for index, x := range items { //遍历items ,index为下标,x为值index++switch x.(type) { //固定写法,传入遍历后的值对类型进行判断case bool:fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)case float32:fmt.Printf("第%v个参数是float32类型,值是%v\n", index, x)case float64:fmt.Printf("第%v个参数是float64类型,值是%v\n", index, x)case int, int16, int32, int64:fmt.Printf("第%v个参数是 整数 类型,值是%v\n", index, x)case string:fmt.Printf("第%v个参数是string类型,值是%v\n", index, x)default:fmt.Printf("第%v个参数类型不确定,值是%v\n", index, x)}}
}func main() {var n1 float32 = 1.2var n2 float64 = 1.43var n3 int32 = 43var n4 string = "tom"n5 := "北京"n6 := 123TyeepJudge(n1, n2, n3, n4, n5, n6)
}
package mainimport "fmt"//1.自定义一个类型
type Student struct{}//编写一个函数,可以判断输入的参数是什么类型
func TyeepJudge(items ...interface{}) { //定义一个...根据大小自动扩容的空接口for index, x := range items { //遍历items ,index为下标,x为值index++switch x.(type) { //固定写法,传入遍历后的值对类型进行判断case bool:fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)case float32:fmt.Printf("第%v个参数是float32类型,值是%v\n", index, x)case float64:fmt.Printf("第%v个参数是float64类型,值是%v\n", index, x)case int, int16, int32, int64:fmt.Printf("第%v个参数是 整数 类型,值是%v\n", index, x)case string:fmt.Printf("第%v个参数是string类型,值是%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("第%v个参数类型不确定,值是%v\n", index, x)}}
}func main() {var n1 float32 = 1.2var n2 float64 = 1.43var n3 int32 = 43var n4 string = "tom"n5 := "北京"n6 := 123stu1 := Student{}  //2.自定义类型stu2 := &Student{} //3.指针类型TyeepJudge(n1, n2, n3, n4, n5, n6, stu1, stu2) //4.传入参数
}

章节目录

【Golang第1~3章:基础】如何安装golang、第一个GO程序、golang的基础

【Golang第4章:函数】Golang包的引用,return语句、指针、匿名函数、闭包、go函数参数传递方式,golang获取当前时间

【Golang第5章:数组与切片】golang如何使用数组、数组的遍历和、使用细节和内存中的布局;golang如何使用切片,切片在内存中的布局

【Golang第6章:排序和查找】golang怎么排序,golang的顺序查找和二分查找,go语言中顺序查找二分查找介绍和案例

【Golang第7章:map】go语言中map的基本介绍,golang中map的使用案例,go语言中map的增删改查操作,go语言对map的值进行排序

【Golang第8章:面向对象编程】Go语言的结构体是什么,怎么声明;Golang方法的调用和声明;go语言面向对象实例,go语言工厂模式;golang面向对象的三大特性:继承、封装、多态

【Golang第9章:项目练习】go项目练习家庭收支记账软件项目、go项目练习客户管理系统项目

【Golang第10章:文件操作】GO语言的文件管理,go语言读文件和写文件、GO语言拷贝文件、GO语言判断文件是否存在、GO语言Json文件格式和解析

【Golang第11章:单元测试】GO语言单元测试

【Golang第12章:goroutine协程与channel管道】GO语言goroutine协程和channel管道的基本介绍、goroutine协