zoukankan      html  css  js  c++  java
  • go语言系列-面向对象编程

    面向对象编程

    结构体

    一个程序就是一个世界,有很多对象(变量)

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

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

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

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

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

    结构体与结构体变量(实例/对象)的关系示意图

    *注意:从猫结构体到变量,就是创建一个Cat结构体变量,也可以说是定义Cat结构体变量*

    对上图的说明

    ​ 将一类事物的特性提取出来(比如猫类),形成一个新的数据类型,就是一个结构体

    ​ 通过这个结构体,我们可以创建多个变量(实例/对象)

    ​ 事物可以猫类,也可以是Person,Fish 或是某个工具类

    基本语法
    type 结构体名称 struct {
    	field1 type
    	field2 type
    }
    
    
    type Cat struct {
    	Name string
    	Age int
    	Color string
    	Hobby string
    }
    
    func main()  {
    	//创建一个Cat的变量
    	var cat1 Cat
    	cat1.Name = "小白"
    	cat1.Age = 3
    	cat1.Color = "白色"
    	cat1.Hobby = "吃 <·)))><<"
    	fmt.Println("cat1 = ", cat1)
    	fmt.Println("猫猫的信息如下:")
    	fmt.Println("name = ", cat1.Name)
    	fmt.Println("age = ", cat1.Age)
    	fmt.Println("color = ", cat1.Color)
    	fmt.Println("hobby = ", cat1.Hobby)
    }
    //cat1 =  {小白 3 白色 吃 <·)))><<}
    //猫猫的信息如下:
    //name =  小白
    //age =  3
    //color =  白色
    //hobby =  吃 <·)))><<
    

    结构体和结构体变量(实例)的区别和联系

    结构体是自定义的数据类型,代表一类事物

    结构体变量(实例)是具体的,实际的,代表一个具体变量

    结构体变量(实例)在内存的布局【重要】

    字段/属性

    基本介绍

    ​ 1) 从概念或叫法上看:结构体字段 = 属性 = field

    ​ 2) 字段是结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型。比如我们前面定义猫结构体的Name string 就是属性

    注意事项和细节说明

    1. 字段声明语法同变量,示例:字段名 字段类型

    2. 字段的类型可以为:基本类型、数组或引用类型

    3. 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则如下:

    布尔类型是false,数值是0,字符串是””

    数组类型的默认值和它的元素类型相关,比如score[3]int 则为[0,0,0]

    指针,slice和map的零值都是nil,即还没有分配空间

    //如果结构体的字段类型是:指针,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)
    
       if p1.ptr == nil {
          fmt.Println("ok1")
       }
    
       if p1.slice == nil {
          fmt.Println("ok2")
       }
    
       if p1.map1 == nil {
          fmt.Println("ok3")
       }
    
       //使用slice,再次说明,一定要make
       p1.slice = make([]int, 10)
       p1.slice[0] = 100
       //使用map,一定要先make
       p1.map1 = make(map[string]string)
       p1.map1["key1"] = "tom"
       fmt.Println(p1)
    }
    //输出:{ 0 [0 0 0 0 0] <nil> [] map[]}
    //ok1
    //ok2
    //ok3
    //{ 0 [0 0 0 0 0] <nil> [100 0 0 0 0 0 0 0 0 0] map[key1:tom]}
    
    1. 不同结构体变量的字段是独立的,互不影响,一个结构体变量字段的更改,不影响另外一个,结构体是值类型
    type Monster struct {
       Name string
       Age int
    }
    func main(){
       var monster1 Monster
       monster1.Name = "牛魔王"
       monster1.Age = 500
    
       monster2 := monster1 //结构体是值类型,默认为值拷贝
       monster2.Name = "青牛精"
       fmt.Println("monster1 = ",monster1)
       fmt.Println("monster2 = ",monster2)
    }
    //输出:monster1 =  {牛魔王 500}
    //monster2 =  {青牛精 500}
    

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

    方式1:直接声明   var person Person
    type Cat struct {
    	Name string
    	Age int
    	Color string
    	Hobby string
    }
    
    func main()  {
    	//创建一个Cat的变量
    	var cat1 Cat
    	cat1.Name = "小白"
    	cat1.Age = 3
    	cat1.Color = "白色"
    	cat1.Hobby = "吃 <·)))><<"
      
    方式2:{}         var person Person = Person{}  
    type Monster struct {
       Name string
       Age int
    }
    func main(){
      //{}
       p := Monster{"zisefeizhu",21}
       fmt.Println(p)
       //输出:{zisefeizhu 21}
    }  
      
    方式3: &        var person *Person = new (Person)
    type Monster struct {
       Name string
       Age int
    }
    func main(){
       //方式3
       //案例:var person *Person = new (Person)
       var p3 *Monster = new(Monster)
       //因为p3是一个指针,因此标准的给字段赋值方式
       //(*p3).Name = "smith" 也可以这样写 p3.Name = "smith"
    
       //原因:Go的设计者为了程序员使用方便,底层会对p3.Name = "smith"进行处理
       //会给p3加上取值运算(*p3).Name = "smith"
       (*p3).Name = "smith"
       p3.Name = "john"
    
       (*p3).Age = 30
       p3.Age = 100
       fmt.Println(*p3)  //{john 100}
    }  
    
    方式4: {}           var person *Person = &Person{}
    type Monster struct {
       Name string
       Age int
    }
    func main(){
       //方式4 - {}
       //var person *Person = &Person{}
       //下面的语句,也可以直接给字符赋值
       //var person *Person = &Person{"mary",60}
       var person *Monster = &Monster{}
       //因为person是一个指针,因此标准的访问字段的方法
       //(*person).Name = "scott"
       //go的设计者为了程序使用方便,也可以person.Name = "scott"
       //原因和上面一样,底层会对person.Name = "scott" 进行处理,会加上(*person)
       (*person).Name = "scott"
       person.Name = "scott ~"
    
       (*person).Age = 88
       person.Age = 10
       fmt.Println(*person)  //{scott ~ 10}
    }  
    
    说明:
    1)第3种和第4种方式返回的是结构体指针
    2)结构体指针访问字段的标准方式应该是:(*结构体指针)字段名,比如(*person).Name = “tom”
    3)但Go做了一个简化,也支持 结构体指针.字段名,比如person.Name = “tom”。更加符合程序员使用的习惯,Go编译器底层对person.Name做了转化(*person).Name  
    

    struct类型的内存分配机制

    定义一个Person结构体(包括 名字,年龄)

    type Person struct {
       Name string
       Age int
    }
    
    func main()  {
       var p1 Person
       p1.Age = 10
       p1.Name = "小明"
       var p2 Person = p1
    
       fmt.Println(p2.Age)
       p2.Name = "tom"
       fmt.Printf("p2.Name = %v p1.Name = %v",p2.Name,p1.Name)
    }
    //输出:10
    //p2.Name = tom p1.Name = 小明
    
    
    

    变量总是存在内存中的,那么结构体变量在内存中究竟是怎样存在的?

    画一个图说明:结构体变量在内存中如何存在

    看下面代码,分析原因

    type Person struct {
       Name string
       Age int
    }
    
    func main()  {
       var p1 Person
       p1.Age = 10
       p1.Name = "小明"
       var p2 *Person = &p1  //这里是关键 --> 画图示意图
    
       fmt.Println((*p2).Age)  //10
       fmt.Println(p2.Age)  //10
       p2.Name = "tom ~"
       fmt.Printf("p2.Name = %v p1.Name = %v 
    ",p2.Name,p1.Name)  //p2.Name = tom ~ p1.Name = tom ~
       fmt.Printf("p2.Name = %v p1.Name = %v 
    ",(*p2).Name,p1.Name)  //p2.Name = tom ~ p1.Name = tom ~
    
       fmt.Printf("p1的地址%p
    ",&p1)  //p1的地址0xc00004a420
       fmt.Printf("p2的地址%p p2的值%p
    ",&p2, p2)  //p2的地址0xc000080018 p2的值0xc00004a420
       fmt.Println(p2.Age)  //10
       p2.Name = "tom"
       fmt.Printf("p2.Name = %v p1.Name = %v",p2.Name,p1.Name) //p2.Name = tom p1.Name = tom
    }
    


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

    1)结构体的所有字段在内存中连续的

    //结构体
    type Point struct {
       x int
       y int
    }
    //结构体
    type Rect struct {
       leftUp, rightDown Point
    }
    //结构体
    type Rect2 struct {
       leftUp, rightDown *Point
    }
    
    func main()  {
       r1 := Rect{Point{1,2},Point{3,4}}
       //r1有四个int,在内存中是连续分布
       //打印地址
       fmt.Printf("r1.leftUp.x 地址=%p r1.leftUp.y 地址=%p r1.rightDown.x 地址=%p r1.rightDown.y 地址=%p
    " ,
          &r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y) 
       //r.leftUp.x 地址=0xc000052140 r1.leftUp.y 地址=0xc000052148 r1.rightDown.x 地址=0xc000052150 r1.rightDown.y 地址=0xc000052158
       
       //r2有两个*Point类型,这两个*Point类型的本身地址也是连续的
       //但是它们指向的地址不一定是连续
       r2 := Rect2{&Point{10,20},&Point{30,40}}
       //打印地址
       //打印地址
       fmt.Printf("r2.leftUp 本身地址 = %p r2.rightDown 本身地址 = %p 
    ",
          &r2.leftUp, &r2.rightDown)
       //r2.leftUp 本身地址 = 0xc0000401c0 r2.rightDown 本身地址 = 0xc0000401c8 
       
       //它们指向的地址不一定是连续...  这个要看系统在运行时是如何分配
       fmt.Printf("r2.leftUp 指向地址 = %p r2.rightDown 指向地址 = %p 
    ",
          r2.leftUp, r2.rightDown)
       //r2.leftUp 指向地址 = 0xc000054090 r2.rightDown 指向地址 = 0xc0000540a0 
    }
    

    对应的分析图

    2)结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)

    type A struct {
       Num int
    }
    type B struct {
       Num int
    }
    func main()  {
       var a A
       var b B
       a = A(b) //? 可以转换,但是有要求,就是结构体的字段要完全一样(包括:名字、个数和类型)
       fmt.Println(a, b)  //{0} {0}
    }
    
    1. 结构体进行type重新定义(相当于取别名),Go认为是新的数据类型,但是相互间可以强转
    2. struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用就是序列化和反序列化

    序列化的常见使用

    import (
    	"encoding/json"
    	"fmt"
    )
    
    type Monster struct {
    	Name string `json:"name"`  // `json:"name"` 就是struct tag
    	Age int `json:"age"`
    	Skill string `json:"skill"`
    }
    
    func main()  {
    	//1.创建一个Monster变量
    	Monster := Monster{"牛魔王",500,"芭蕉扇"}
    	//2.将monster变量序列化为json格式字串
    	// json.Marshal 函数中使用反射,这里只是用一下反射,在后面会详细介绍
    	jsonStr, err := json.Marshal(Monster)
    	if err != nil {
    		fmt.Println("json 处理错误",err)
    	}
    	fmt.Println("jsonStr",string(jsonStr))  //jsonStr {"name":"牛魔王","age":500,"skill":"芭蕉扇"}
    }
    

    方法

    在某些情况下,需要声明(定义)方法。比如Person结构体:除了有一些字段外(年龄,姓名...),Person结构体还有一些行为比如:可以说话、跑步...,通过学习,还可以做算术题。这时就要用方法才能完成

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

    方法的声明和调用

    type A struct {
    	Num int
    }
    func (a A)test(){
    	fmt.Println(a.Num)
    }
    
    对上面的语法的说明
    func (a A)test() {} 表示A结构体有 - 方法,方法名为test
    (a A)体现test方法是和A类型绑定的
    
    type Person struct {
       Name string
    }
    //给Person类型绑定 -- 方法
    func (p Person) test() {
       fmt.Println("test() name = ", p.Name)  //test() name =  tom
    }
    func main()  {
       var p Person
       p.Name = "tom"
       p.test() // 调用方法
    }
    

    对上面的总结

    1. test方法和Person类型绑定

    2)test方法只能通过Person类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调用

    1. func (p Person)test(){}... p表示哪个Person变量调用,这个p就是它的副本,这点和函数传参非常相似

    2. p这个名字,由程序员指定,不是固定,比如修改成person也是可以

    type Person struct {
       Name string
    }
    //给Person类型绑定 -- 方法
    func (person Person) test() {
       fmt.Println("test() name = ", person.Name)  //test() name =  tom
    }
    func main()  {
       var p Person
       p.Name = "tom"
       p.test() // 调用方法
    }
    

    快速入门

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

    方法的调用和传参机制原理[重要]

    方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当作实参也传递给方法。

    案例1

    画出前面getSum方法的执行过程+说明

    1. 在通过一个变量去调用方法时,其调用机制和函数一样

    2. 不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)

    案例2

    请编写一个程序,要求如下:

    1. 声明一个结构体Circle,字段为radius

    2. 声明一个方法area和Circle绑定,可以返回面积

    3. 提示:画出area执行过程+说明

    方法的声明(定义)

    func (recevier type) methodName (参数列表) (返回值列表) {
    	方法体
    	return 返回值
    }
    
    1)参数列表:表示方法输入
    2)recevier type:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
    3)recevier type:type可以是结构体,也可以其它的自定义类型
    4)recevier:就是type类型的一个变量(实例),比如:Person结构体的一个变量(实例)
    5)返回值列表:表示返回的值,可以多个
    6)方法主体:表示为了实现某一功能代码块
    7)return语句不是必须的
    

    方法的注意事项和细节

    1. 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式

    2. 如果程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理

    3. Go中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct,比如int,float32等都可以有方法

    type integer int
    
    func (i integer) print()  {
       fmt.Println("i = ",i)
    }
    //编写一个方法,可以改变i的值
    func (i *integer) change() {
       *i = *i + 1
    }
    func main()  {
       var i integer = 10
       i.print()
       i.change()
       fmt.Println("i = ", i)
    }
    
    //输出:i =  10
    //i =  11
    
    1. 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问

    2. 如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出

    type Student struct {
       Name string
       Age int
    }
    //给*Student实现方法String()
    func (stu *Student) String() string {
       str := fmt.Sprintf("Name = [%v] Age = [%v]",stu.Name, stu.Age)
       return str
    }
    
    func main() {
       //定义一个Student变量
       stu := Student{
          Name: "tom",
          Age:  20,
       }
       //如果实现了*Student 类型的string方法,就会自动调用
       fmt.Println(&stu)
    }
    //输出:Name = [tom] Age = [20]
    

    方法和函数的区别

    1. 调用方式不一样

    ​ 函数的调用方式:函数名(实参列表)

    ​ 方法的调用方式:变量.方法名(实参列表)

    1. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
    type Person struct {
       Name string
    }
    
    //函数
    //对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
    func test01(p Person)  {
       fmt.Println(p.Name)  //tom
    }
    func test02(p *Person)  {
       fmt.Println(p.Name)  //tom
    }
    func main()  {
       p := Person{"tom"}
       test01(p)
       test02(&p)
    }
    
    1. 对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
    type Person struct {
       Name string
    }
    
    //3)对于方法(如struct的方法),
    // 接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
    func (p Person) test03()  {
       p.Name = "jack"
       fmt.Println("test03() = ",p.Name)
    }
    
    func (p *Person) test04()  {
       p.Name = "mary"
    -   fmt.Println("test04() = ",p.Name)
    }
    
    func main()  {
       p := Person{"tom"}
       p.test03()
       fmt.Println("main() p.name = ",p.Name)
    
       (&p).test03()  //从形式上传入地址,但是本质仍然是值拷贝
       fmt.Println("main() p.name = ",p.Name)
    
       (&p).test04()
       fmt.Println("main() p.name = ",p.Name)
       p.test04()
    }
    //test03() =  jack
    //main() p.name =  tom
    //test03() =  jack
    //main() p.name =  tom
    //test04() =  mary
    //main() p.name =  mary
    //test04() =  mary
    

    总结

    1. 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定

    2. 如果是和值类型,比如(p Person),则是值拷贝,如果和指针类型,比如是(p Person)则是地址拷贝

    方法练习题

    编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个 10*8的矩形,在main方法中调用该方法

    type MethodUtils struct {
       //字段...
    }
    //给MethodUtils编写方法
    func (mu MethodUtils) print()  {
       for i := 1; i <= 10; i++ {
          for j := 1; j <= 8; j++ {
             fmt.Print("*")
          }
          fmt.Println()
       }
    }
    func main()  {
       var mu MethodUtils
       mu.print()
    }
    //输出:
    //********
    //********
    //********
    //********
    //********
    //********
    //********
    //********
    //********
    //********
    

    编写一个方法,提供m 和 n 两个参数,方法中打印一个 m * n 的矩形

    type MethodUtils struct {
       //字段...
    }
    func (mu MethodUtils) print2(m int, n int)  {
       for i := 1; i <= 10; i++ {
          for j := 1; j <= 8; j++ {
             fmt.Print("*")
          }
          fmt.Println()
       }
    }
    
    func main()  {
       var mu MethodUtils
       mu.print2(10,8)
    }
    //输出:
    //********
    //********
    //********
    //********
    //********
    //********
    //********
    //********
    //********
    //********
    

    编写一个方法算该矩形的面积(可以接收长len,和宽width),将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印

    type MethodUtils struct {
       //字段...
    }
    func (mu MethodUtils) area(len float64, width float64) (float64)  {
       return  len * width
    }
    
    func main()  {
       var mu MethodUtils
       fmt.Println("面积 =",mu.area(10,20))  //面积 = 200
    }
    

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

    type MethodUtils struct {
       //字段...
    }
    
    func (mu *MethodUtils) JudgeNum(num int)  {
       if num % 2 == 0 {
          fmt.Println(num,"是偶数..")  //   10 是偶数..
    
       } else {
          fmt.Println(num,"是奇数..")
       }
    }
    
    func main()  {
       var mu MethodUtils
       mu.JudgeNum(10)
    }
    

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

    type MethodUtils struct {
       //字段...
    }
    
    func (mu *MethodUtils) print(n int, m int, key string)  {
       for i := 1; i <= n; i++ {
          for j := 1; j <= m; j++ {
             fmt.Print(key)
          }
          fmt.Println()
       }
    }
    
    func main()  {
       var mu MethodUtils
       mu.print(5,5,"+")
    }
    

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

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

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

    //实现形式1
    type Calcuator struct {
       Num1 float64
       Num2 float64
    }
    
    func (calcuator *Calcuator) getSum()  float64 {
       return calcuator.Num1 + calcuator.Num2
    }
    
    func (calcuator *Calcuator) getSub() float64 {
       return calcuator.Num1 - calcuator.Num2
    }
    
    package main
    
    import "fmt"
    //实现形式2
    type Calcuator struct {
       Num1 float64
       Num2 float64
    }
    
    func (calcuator *Calcuator) getRes(operator byte) float64  {
       res := 0.0
       switch operator {
       case '+':
          res = calcuator.Num1 + calcuator.Num2
       case '-':
          res = calcuator.Num1 - calcuator.Num2
       case '*':
          res = calcuator.Num1 * calcuator.Num2
       case '/':
          res = calcuator.Num1 / calcuator.Num2
       default:
          fmt.Println("运算符输入有误...")
       }
       return res
    }
    

    在MerhodUtils结构体编个方法,从键盘接收整数(1-9),打印对应乘法表

    type MerhodUtils struct {
    	//字段
    }
    
    func (m MerhodUtils) jiu(n int)  {
    	for i := 1; i <= n; i++ {
    		for j := 1; j <= i; j++ {
    			fmt.Printf("%v * %v = %v	",i,j,i*j)
    		}
    		fmt.Println()
    	}
    }
    
    func main()  {
    	var mu MerhodUtils
    	var num int
    	fmt.Println("请键入要输入的大于等于1小于等于9的自然数: ")
    	fmt.Scanln(&num)
    	mu.jiu(num)
    }
    //请键入要输入的大于等于1小于等于9的自然数: 
    //2
    //1 * 1 = 1	
    //2 * 1 = 2	2 * 2 = 4
    

    编写方法,使给定的一个二维数组(3 × 3)转置

    面向对象编程应用实例

    步骤

    1. 声明(定义)结构体,确定结构体名

    2. 编写结构体的字段

    3. 编写结构体的方法

    学生案例

    1. 编写一个Student结构体,包含name、gender、age、id、score字段,分别为string、string、int、int、float64类型

    2. 结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值

    3. 在main方法中,创建Student结构体实例(变量),并访问say方法,并将调用结果打印输出

    type Student struct {
       name string
       gender string
       age int
       id int
       score 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: "zisefeizhu",
          gender:"male",
          age: 18,
          id: 1000,
          score: 99.98,
       }
       fmt.Println(stu.say())
    }
    //输出:student 的信息 name = [zisefeizhu] gender = [male] age = [18] id = [1000] score = [99.98]
    

    小狗案例

    1. 编写一个Dog结构体,包含name、age、weight字段

    2. 结构体中声明一个say方法,返回string类型,方法返回信息中包含所有字段值

    3. 在main方法中,创建Dog结构体实例(变量),并返回say方法,将调用结果打印输出

    type Dog struct {
       name string
       age int
       wgight float64
    }
    
    func (dog *Dog) say() string  {
       infoStr := fmt.Sprintf("dog 的信息 name = [%v] age = [%v] weight = [%v]",
          dog.name, dog.age, dog.wgight)
       return infoStr
    }
    func main()  {
       //创建一个Student实例变量
       var stu = Dog{
          name: "xiaohua",
          age: 18,
          wgight: 23,
       }
       fmt.Println(stu.say())
    }
    //输出:dog 的信息 name = [xiaohua] age = [18] weight = [23]
    

    盒子案例

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

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

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

    type Box struct {
       len float64
       width float64
       height float64
    }
    //声明一个方法获取立方体的体积
    func (box *Box) getVolum() float64 {
       return  box.len * box.width * box.height
    }
    func main()  {
       var box Box
       box.len = 1.1
       box.width = 2.0
       box.height = 3.0
       volumn := box.getVolum()
       fmt.Printf("体积为=%.2f",volumn)
    }
    //输出:体积为=6.60
    

    景区门票案例

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

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

    type Visitor struct {
       Name string
       Age int
    }
    //声明一个方法获取立方体的体积
    func (visitor *Visitor) showPrice() {
       if visitor.Age >= 90 || visitor.Age <= 8 {
          fmt.Println("考虑到安全,就不要玩了")
          return
       }
       if visitor.Age > 18 {
          fmt.Printf("游客的名字为 %v 年龄为 %v 收费20元
    ", visitor.Name, visitor.Age)
       } else {
          fmt.Printf("游客的名字为 %v 年龄为%v 免费 
    ", visitor.Name, visitor.Age)
       }
    }
    
    func main()  {
       var v  Visitor
       for {
          fmt.Println("请输入你的名字")
          fmt.Scanln(&v.Name)
          if v.Name == "n" {
             fmt.Println("退出程序...")
             break
          }
          fmt.Println("请输入你的年龄")
          fmt.Scanln(&v.Age)
          v.showPrice()
       }
    }
    //输出:请输入你的名字
    //zisefeizhu
    //请输入你的年龄
    //20
    //游客的名字为 zisefeizhu 年龄为 20 收费20元
    //请输入你的名字
    //n
    //退出程序...
    

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

    Go在创建结构体实例(变量)时,可以直接指定字段的值

    方式1:
    type Stu struct {
       Name string
       Age int
    }
    
    func main()  {
       //方式1
       //在创建结构体变量时,就直接指定字段的值
       var  stu1 = Stu{"zisefeizhu",20}  // stu1 --> 结构体数据空间
       stu2 := Stu{"jingxing",20}
       //在创建结构体变量时,把字段名和字段值写在一起,这种写法,就不依赖字段的定义顺序
       var stu3 = Stu{
          Name: "yike",
          Age: 20,
       }
       var stu4 = Stu{
          Name: "gengpan",
          Age: 20,
       }
       fmt.Println(stu1, stu2, stu3, stu4)
    }
    //输出:{zisefeizhu 20} {jingxing 20} {yike 20} {gengpan 20}
    
    方式2:
    type Stu struct {
       Name string
       Age int
    }
    
    func main()  {
       //方式2
       var stu5 *Stu = &Stu{"小王", 20} //stu5 --> 地址 --> 结构体数据[xxxx,xxxx]
       stu6 := &Stu{"小紫", 20}
       //在创建结构体指针变量时。把字段名和字段值写在一起,这种写法,就不依赖字段的定义顺序
       var stu7 = &Stu{
          Name: "小林",
          Age: 20,
       }
       var stu8 = &Stu{
          Age: 20,
          Name: "小耿",
       }
       fmt.Println(*stu5, *stu6, *stu7, *stu8)
    }
    //输出:{小王 20} {小紫 20} {小林 20} {小耿 20}
    

    工厂模式

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

    看一个需求

    一个结构体的声明是这样的:

    pachage model
    type Student struct {
    Name string ...
    }
    

    因为这里的Student的首字母S是大写的,如果我们想在其它包创建Student的实例(比如main包),引入model包后,就可以直接创建Student结构体的变量(实例)。但是问题来了,如果首字母是小写的,比如是type student struct {...}就不行了,怎么办-->工厂模式来解决>

    工厂模式来解决问题

    使用工厂模式实现跨包创建结构体实例(变量)的案例:

    如果model包的*结构体变**量**首字母大写,引入后,直接使用*,没有问题

    ![img](file:///C:UserslinkunAppDataLocalTempksohtml1372wps1.jpg)

    如果model包的结构体变量首字母小写,引入后,不能直接使用,可以工厂模式解决

    student.go

    package model
    
    //定义一个结构体
    type student struct {
       Name string
       Score float64
    }
    
    //因为student结构体首字母是小写,因此只能在model使用
    //通过工厂模式来解决
    func NewStudent(n string, s float64) *student  {
       return &student{
          Name:  n,
          Score: s,
       }
    }
    

    main.go

    package main
    
    import (
       "2020-04-04/model"
       "fmt"
    )
    
    func main() {
       var stu = model.NewStudent("tom", 21)
       fmt.Println(*stu) //&{...}
       fmt.Println("name = ", stu.Name, "score = ", stu.Score)
    }
    //name =  tom score =  21
    

    思考题

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

    解决方法如下:

    ​ student.go

    //定义一个结构体
    type student struct {
       Name string
       score float64
    }
    
    //因为student结构体首字母是小写,因此只能在model使用
    //通过工厂模式来解决
    func NewStudent(n string, s float64) *student  {
       return &student{
          Name:  n,
          score: s,
       }
    }
    
    //如果score字段首字母小写,则,在其它包不可以直接访问,可以提供一个方法
    func (s *student) GetScore() float64  {
       return s.score
    }
    

    main.go

    import (
       "2020-04-04/model"
       "fmt"
    )
    
    func main() {
       var stu = model.NewStudent("tom", 22)
       fmt.Println(*stu) //&{...}
       fmt.Println("name = ", stu.Name, "score = ", stu.GetScore())
    }
    //{tom 22}
    //name =  tom score =  22
    

    面向对象编程思想-抽象

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

    快速入门案例

    //定义一个结构体Account
    type Account struct {
    	AccountNo string
    	Pwd string
    	Balance float64
    }
    //方法
    //1.存款
    func (account *Account) Deposite(money float64,pwd string){
    	//看下输入的密码是否正确
    	if pwd != account.Pwd {
    		fmt.Println("你输入的密码不正确")
    		return
    	}
    	//看看存款是否正确
    	if money <= 0 {
    		fmt.Println("你输入的金额不正确")
    		return
    	}
    	account.Balance += money
    	fmt.Println("存款成功!")
    }
    //取款
    func (account *Account) WithDraw(money float64, pwd string)  {
    	//看一下输入的密码是否正确
    	if pwd != account.Pwd {
    		fmt.Println("你输入的密码不正确")
    		return
    	}
    	//看看存款金额是否正确
    	if money <= 0 || money > account.Balance{
    		fmt.Println("你输入的金额不正确")
    		return
    	}
    	account.Balance -= money
    	fmt.Println("取款成功~")
    }
    //查询余额
    func (account *Account) Query(pwd string)  {
    	//看一下输入的密码是否正确
    	if pwd != account.Pwd {
    		fmt.Println("你输入的密码不正确")
    		return
    	}
    	fmt.Printf("你的账号为=%v 余额=%v 
    ",account.AccountNo,account.Balance)
    }
    func main()  {
    	var pwd string
    	var balance float64
    	account := Account{
    		AccountNo: "1111111",
    		Pwd: "666666",
    		Balance: 0.0,
    	}
    	fmt.Println("请输入密码")
    	fmt.Scanln(&pwd)
    	if pwd == account.Pwd {
    		fmt.Println("请输入金额")
    		fmt.Scanln(&balance)
    		account.Query(pwd)
    		account.Deposite(balance,pwd)
    		account.Query(pwd)
    	}
    }
    //请输入密码
    //666666
    //请输入金额
    //2000
    //你的账号为=1111111 余额=0 
    //存款成功!
    //你的账号为=1111111 余额=2000 
    

    对上面代码进行修饰:增加一个控制台的菜单,可以让用户动态的输入选项

    package main
    
    import "fmt"
    //定义一个结构体Account
    type Account struct {
    	AccountNo string
    	Pwd string
    	Balance float64
    }
    //方法
    //1.存款
    func (account *Account) Deposite(money float64,pwd string){
    	//看下输入的密码是否正确
    	if pwd != account.Pwd {
    		fmt.Println("你输入的密码不正确")
    		return
    	}
    	//看看存款是否正确
    	if money <= 0 {
    		fmt.Println("你输入的金额不正确")
    		return
    	}
    	account.Balance += money
    	fmt.Println("存款成功!")
    }
    //取款
    func (account *Account) WithDraw(money float64, pwd string)  {
    	//看一下输入的密码是否正确
    	if pwd != account.Pwd {
    		fmt.Println("你输入的密码不正确")
    		return
    	}
    	//看看存款金额是否正确
    	if money <= 0 || money > account.Balance{
    		fmt.Println("你输入的金额不正确")
    		return
    	}
    	account.Balance -= money
    	fmt.Println("取款成功~")
    }
    //查询余额
    func (account *Account) Query(pwd string)  {
    	//看一下输入的密码是否正确
    	if pwd != account.Pwd {
    		fmt.Println("你输入的密码不正确")
    		return
    	}
    	fmt.Printf("你的账号为=%v 余额=%v 
    ",account.AccountNo,account.Balance)
    }
    func main()  {
    	var xuanxiang byte
    	var pwd string
    	var balance float64
    	account := Account{
    		AccountNo: "1111111",
    		Pwd: "666666",
    		Balance: 0.0,
    	}
    	fmt.Println("请输入密码")
    	fmt.Scanln(&pwd)
    	if pwd == account.Pwd {
    	for  {
    		fmt.Println("请输入菜单选项:")
    		fmt.Println("1. 存款")
    		fmt.Println("2. 取款")
    		fmt.Println("3. 余额")
    		fmt.Println("4. 退出")
    		fmt.Println("请输入菜单选项:")
    		fmt.Scanln(&xuanxiang)
    		switch xuanxiang {
    		case 1:
    			fmt.Println("请输入金额")
    			fmt.Scanln(&balance)
    			account.Query(pwd)
    			account.Deposite(balance,pwd)
    			account.Query(pwd)
    			//fmt.Println("请输入金额")
    			//fmt.Scanln(&balance)
    			//account.Deposite(balance,pwd)
    		case 2:
    			fmt.Println("请输入金额")
    			fmt.Scanln(&balance)
    			account.Query(pwd)
    			account.WithDraw(balance,pwd)
    			account.Query(pwd)
    		case 3:
    			//
    			account.Query(pwd)
    		default:
    			return
    		}
    	}
    
    		//fmt.Println("请输入金额")
    		//fmt.Scanln(&balance)
    		//account.Query(pwd)
    		//account.Deposite(balance,pwd)
    		//account.Query(pwd)
    	}
    }
    //请输入密码
    //666666
    //请输入金额
    //2000
    //你的账号为=1111111 余额=0
    //存款成功!
    //你的账号为=1111111 余额=2000
    

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

    Go仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样

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

    封装的好处

    1. 隐藏实现细节

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

    如何体现封装

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

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

    封装的实现步骤

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

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

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

    func(var 结构体类型名) SetXxx(参数列表) (返回值列表) {
    	//加入数据验证的业务逻辑
    	var.字段 = 参数
    }
    
    1. 提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值
    func (var 结构体类型名) GetXxx() {
    	return var.age
    }
    

    *特别说明*:在Go开发中并没有特别强调封装,这点并不像Java,所以不用总是用Java的语法特性来看待Go,Go本身对面向对象的特性做了简化的

    快速入门案例

    编写一个程序(person.go),不能随便查看人的年龄、工资等隐私,并对输入的年龄进行合理的验证。

    设计:model包(person.go),main包(main.go调用Person结构体)

    person.go

    type person struct {
       Name string
       age int  //其它包不能直接访问
       sal float64
    }
    //写一个工厂模式的函数,相当于构造函数
    func NewPerson(name string) *person {
       return &person{
          Name: name,
       }
    }
    //为了访问age和sal 编写一对SetXxx的方法和GetXxx的方法
    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.go

    func main()  {
       p := model.NewPerson("smith")
       p.SetAge(18)
       p.SetSal(5000)
       fmt.Println(p)
       fmt.Println(p.Name, "age =", p.GetAge(), "sal =", p.GetSal())
    }
    //输出:&{smith 18 5000}
    //smith age = 18 sal = 5000
    

    type person struct {
       name string
       age int  //其它包不能直接访问
       sal float64
    }
    //写一个工厂模式的函数,相当于构造函数
    func NewPerson() *person {
       return &person{
          name: "",
          age:  0,
          sal:  0,
       }
    }
    //为了访问age和sal 编写一对SetXxx的方法和GetXxx的方法
    func (p *person) SetAge(name string ,age int, sal float64)  {
       if name != "" {
          p.name = name
       }
       if age > 0 && age < 150 {
          p.age = age
       } else {
          fmt.Println("年龄范围不正确..")
          //给程序员一个默认值
       }
    
       if sal >= 3000 && sal <= 30000 {
          p.sal = sal
       } else {
          fmt.Println("薪水范围不正确...")
       }
    }
    
    func (p *person) GetAge() (string,int, float64) {
       return p.name ,p.age, p.sal
    }
    
    
    func main()  {
       //p := model.NewPerson("smith")
       p := model.NewPerson()
       p.SetAge("smith",18, 5000)
       fmt.Println(p)
       fmt.Println(p.GetAge())
    }
    

    再改

    //写一个工厂模式的函数,相当于构造函数
    func NewPerson(name string, age int, sal float64) *person {
       if name == "" {
          fmt.Println("输入名字错误")
          return nil
       }
       if age < 0 && age > 150 {
          fmt.Println("年龄范围不对...")
          return nil
       }
       if sal < 3000 && sal > 30000 {
          fmt.Println("薪水范围不正确...")
          return nil
       }
       return &person{
          name: name,
          age:  age,
          sal:  sal,
       }
    }
    
    func (p *person) GetAge() (string,int, float64) {
       return p.name ,p.age, p.sal
    }
    
    func main()  {
       p := model.NewPerson("smith",18,50000)
       fmt.Println(p)
       fmt.Println(p.GetAge())
    }
    

    要求

    1. 创建程序,在model包中定义Account结构体:在main函数中体现Go的封装性

    2. Account结构体要求具有字段:账号(长度在6-10之间)、余额(必须>20)、密码(必须是6位数)

    3. 通过SetXxx的方法给Account的字段赋值

    4. 在main函数中测试

    account.go

    //定义一个结构体account
    type account struct {
       accountNo string
       pwd string
       balance float64
    }
    //工厂模式的函数-构造函数
    func NewAccount(accountNo string, pwd string, balance float64) *account  {
       if len(accountNo) < 6 || len(accountNo) > 10 {
          fmt.Println("账号的长度不对")
          return nil
       }
       if len(pwd) != 6 {
          fmt.Println("密码的长度不对...")
          return nil
       }
       if balance < 20 {
          fmt.Println("余额数目不对...")
          return nil
       }
       return &account{
          accountNo: accountNo,
          pwd: pwd,
          balance: balance,
       }
    }
    
    //方法
    //存款
    func (account *account) Deposite(money float64, pwd string)  {
       //看下输入的密码是否正确
       if pwd != account.pwd {
          fmt.Println("你输入的密码不正确")
          return
       }
       //看看存款金额是否正确
       if money <= 0 {
          fmt.Println("你输入的金额不正确")
          return
       }
       account.balance += money
       fmt.Println("存款成功~")
    }
    //取款
    func (account *account) WithDraw(money float64, pwd string)  {
       //看下输入的密码是否正确
       if pwd != account.pwd {
          fmt.Println("你输入的密码不正确")
          return
       }
       //看看存款金额是否正确
       if money <= 0 || money > account.balance {
          fmt.Println("你输入的金额不正确")
          return
       }
       account.balance -= money
       fmt.Println("取款成功~")
    }
    //查询余额
    func (account *account) Query(pwd string)  {
       //看下输入的密码是否正确
       if pwd != account.pwd {
          fmt.Println("你输入的密码不正确")
          return
       }
    
       fmt.Printf("你的账号为 = %v 余额 = %v 
    ", account.accountNo, account.balance)
    }
    

    main.go

    func main()  {
       //创建一个account变量
       account := model.NewAccount("zisefeizhu","000",40)
       if account != nil {
          fmt.Println("创建成功 = ", account)
       } else {
          fmt.Println("创建失败")
       }
    }
    //输出:密码的长度不对...
    //创建失败
    

    增加如下功能:通过SetXxx的方法给Account的字段赋值通过GetXxx方法获取字段的值

    account.go

    package model
    
    import "fmt"
    //定义一个结构体account
    type account struct {
    	accountNo string
    	pwd string
    	balance float64
    }
    //工厂模式的函数-构造函数
    func NewAccount(accountNo string, pwd string, balance float64) *account  {
    	return &account{
    		accountNo: accountNo,
    		pwd: pwd,
    		balance: balance,
    	}
    }
    
    func (accounter *account) SetAccountNo(accountNo string) {
    	if len(accountNo) < 6 || len(accountNo) > 10 {
    		fmt.Println("账号的长度不对")
    	}
    }
    
    func (accounter *account) GetAccountNo() string {
    	return accounter.accountNo
    }
    
    //方法
    //存款
    func (account *account) Deposite(money float64, pwd string)  {
    	//看下输入的密码是否正确
    	if pwd != account.pwd {
    		fmt.Println("你输入的密码不正确")
    		return
    	}
    	//看看存款金额是否正确
    	if money <= 0 {
    		fmt.Println("你输入的金额不正确")
    		return
    	}
    	account.balance += money
    	fmt.Println("存款成功~")
    }
    //取款
    func (account *account) WithDraw(money float64, pwd string)  {
    	//看下输入的密码是否正确
    	if pwd != account.pwd {
    		fmt.Println("你输入的密码不正确")
    		return
    	}
    	//看看存款金额是否正确
    	if money <= 0 || money > account.balance {
    		fmt.Println("你输入的金额不正确")
    		return
    	}
    	account.balance -= money
    	fmt.Println("取款成功~")
    }
    //查询余额
    func (account *account) Query(pwd string)  {
    	//看下输入的密码是否正确
    	if pwd != account.pwd {
    		fmt.Println("你输入的密码不正确")
    		return
    	}
    
    	fmt.Printf("你的账号为 = %v 余额 = %v 
    ", account.accountNo, account.balance)
    }
    

    main.go

    package main
    
    import (
    	"2020-04-04/model"
    	"fmt"
    )
    
    func main()  {
    	//创建一个account变量
    	account := model.NewAccount("zisefeizhu","000",40)
    	fmt.Println(account)
    	fmt.Println(account.GetAccountNo())
    }
    //&{zisefeizhu 000 40}
    //zisefeizhu
    

    类似改法

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

    看一个问题,引出继承的必要性

    看一个学生考试系统的程序extend01.go,提出代码复用的问题

    //编写一个学生考试系统
    //小学生
    type Pupil struct {
       Name string
       Age int
       Score int
    }
    //显示他的成绩
    func (p *Pupil) ShowInfo() {
       fmt.Printf("学生名 = %v 年龄 = %v 成绩 = %v
    ", 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 string
       Age int
       Score int
    }
    //显示他的成绩
    func (p *Graduate) ShowInfo() {
       fmt.Printf("学生名 = %v 年龄 = %v 成绩 = %v
    ", 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: "tom",
          Age: 20,
       }
       graduate.testing()
       graduate.SetScore(90)
       graduate.ShowInfo()
    }
    //输出:小学生正在考试中...
    //学生名 = tom 年龄 = 10 成绩 = 90
    //大学生正在考试中...
    //学生名 = tom 年龄 = 20 成绩 = 90
    
    对上面代码的小结
    1)Pupil和Graduate两个结构体的字段和方法几乎一样,但是我们却写了相同的代码,代码复用性不强
    2)出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展
    3)解决方法 - 通过继承方式来解决
    

    继承基本介绍和示意图

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

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

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

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

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

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

    快速入门案例

    对extends01.go改进,使用嵌套匿名结构体的方式来实现继承特性,体会继承的好处

    //编写一个学生考试系统
    //小学生
    type Student struct {
       Name string
       Age int
       Score int
    }
    //将Pupil 和 Graduate 共有的方法也绑定到 *Student
    func (stu *Student) ShowInfo() {
       fmt.Printf("学生名 = %v 年龄 = %v 成绩 = %v
    ", 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 = 8
       pupil.testing()
       pupil.SetScore(70)
       pupil.ShowInfo()
    
       //测试
       graduate := &Graduate{}
       graduate.Student.Name = "marry"
       graduate.Student.Age = 28
       graduate.testing()
       graduate.SetScore(90)
       graduate.ShowInfo()
    }
    //输出:小学生正在考试中...
    //学生名 = tom 年龄 = 8 成绩 = 70
    //大学生正在考试中...
    //学生名 = marry 年龄 = 28 成绩 = 90
    

    继承给编程带来的便利

    1. 代码的复用性提高了

    2. 代码的扩展性和维护性提高了

    继承的深入讨论

    1)结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用

    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.Name)
    }
    type B struct {
       A
    }
    
    func main()  {
       var b B
       b.A.Name = "zisefeizhu"
       b.A.age = 19
       b.A.SayOk()
       b.A.Hello()
    }
    //输出:A SayOk zisefeizhu
    //A Hello zisefeizhu
    
    1. 匿名结构体字段访问可以简化

      对上面的代码小结
      (1)当我们直接通过b访问字段或方法时,其执行流程如下比如b.Name
      (2)编译器会先看b对应的类型有没有Name,如果有,则直接调用B类型的Name字段
      (3)如果没有就去看B中嵌入的匿名结构体A有没有声明Name字段,如果有就调用,如果没有继续查找...如果都找不到就报错
      3) 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分

    2. 结构体嵌入两个(或多个)匿名结构体,如果两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明指定匿名结构体名字,否则编译报错

    3. 如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字

      6 ) 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

    type Goods struct {
       Name string
       Price float64
    }
    type Brand struct {
       Name string
       Address string
    }
    type TV struct {
       Goods
       Brand
    }
    type TV2 struct {
       *Goods
       *Brand
    }
    
    func main()  {
       tv := TV{ Goods{"电视机001", 5000.99},Brand{"海尔","山东"},}
       tv2 := TV{
          Goods{
             Price: 5000.99,
             Name: "电视机002",
          },
          Brand{
             Name: "夏普",
             Address: "北京",
          },
       }
       fmt.Println("tv", tv)
       fmt.Println("tv2", tv2)
       tv3 := TV2{ &Goods{"电视机003", 7000.99},&Brand{"创维","河南"},}
       tv4 := TV2{
          &Goods{
             Name: "电视机004",
             Price: 9000.99,
          },
          &Brand{
             Name: "长虹",
             Address: "四川",
          },
       }
       fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
       fmt.Println("tv4", *tv4.Goods, *tv4.Brand)
    }
    //tv {{电视机001 5000.99} {海尔 山东}}
    //tv2 {{电视机002 5000.99} {夏普 北京}}
    //tv3 {电视机003 7000.99} {创维 河南}
    //tv4 {电视机004 9000.99} {长虹 四川}
    

    课堂练习

    结构体的匿名字段是基本数据类型,如何访问?
    type Monster struct {
       Name string
       Age int
    }
    type E struct {
       Monster
       int
       n int
    }
    
    func main()  {
       //演示一下匿名字段时基本数据类型的使用
       var e E
       e.Name = "狐狸精"
       e.Age = 300
       e.int = 20
       e.n = 40
       fmt.Println("e = ", e)
    }
    //输出:e =  {{狐狸精 300} 20 40}
    
    说明
    1)如果一个结构体有int类型的匿名字段,就不能有第二个
    2)如果需要有多个int的字段,则必须给int字段指定名字
    

    面向对象编程-多重继承

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

    多重继承细节

    1. 若嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分

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

    接口

    Go中多态特性主要是通过接口来体现的

    usb插槽就是现实中的接口你可以把手机,相机,u盘都插在usb插槽上,而不用担心那个插槽是专门插哪个的,原因是做usb插槽的厂家和做各种设备的厂家都遵守了统一的规定包括尺寸,排线等等。

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

    //声明/定义一个接口
    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)要使用的时候,在根据具体情况把这些方法写出来(实现)

    基本语法


    小结说明:

    1. 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想

    2. Go中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么变量就实现了这个接口,因此,Go中没有implement这样的关键字

    接口使用的应用场景

    1. 中国要制造的轰炸机,专家只需要把飞机需要的功能/规格定下来即可,然后让别的人具体实现即可

    2. 现在有一个项目经理,管理三个程序员,开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现

    ......

    注意事项和细节

    接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)

    type AInterface interface {
       say()
    }
    type stu struct {
       Name string
    }
    
    func (stu stu) say() {
       fmt.Println("stu say()")
    }
    func main()  {
       var stu stu //结构体变量,实现了say() 实现了AInterface
       var a AInterface = stu
       a.say()
    }
    

    接口中所有的方法都没有方法体,即都是没有实现的方法

    在Go中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口

    一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型

    只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型

    type AInterface interface {
       say()
    }
    type stu struct {
       Name string
    }
    type integer int
    
    func (i integer) say()  {
       fmt.Println("integer say i =", i)
    }
    
    func (stu stu) say() {
       fmt.Println("stu say()")
    }
    func main()  {
       var i integer = 10
       var b AInterface = i
       b.say()
       var stu stu //结构体变量,实现了say() 实现了AInterface
       var a AInterface = stu
       a.say()
    }
    //输出:integer say i = 10
    //stu say()
    

    一个自定义类型可以实现多个接口

    type AInterface interface {
       say()
    }
    type BInterface interface {
       Hello()
    }
    type Monster struct {
    
    }
    
    func (m Monster) Hello() {
       fmt.Println("Monster Hello()")
    }
    func (m Monster) say()  {
       fmt.Println("Monster say ")
    }
    func main()  {
       //Monster实现了AInterface 和BInterface
       var monster Monster
       var a2 AInterface = monster
       var b2 BInterface = monster
       a2.say()
       b2.Hello()
    }
    //输出:Monster say 
    //Monster Hello()
    

    Go接口中不能有任何变量

    一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现

    package main
    
    type BInterface interface {
       test01()
    }
    type CInterface interface {
       test02()
    }
    type AInterface interface {
       BInterface
       CInterface
       test03()
    }
    //如果需要实现AInterface,就需要将BInterface CInterface的方法都实现
    type Stu struct {
    
    }
    
    func (stu Stu) test01() {
    
    }
    func (stu Stu) test02() {
    
    }
    func (stu Stu) test03() {
    
    }
    
    func main()  {
       var stu Stu
       var a AInterface = stu
       a.test01()
    }
    

    interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil

    空接口interface{}没有任何方法,所以所有类型都实现了空接口,即可以把任何一个变量赋给空接口

    type BInterface interface {
       test01()
    }
    type CInterface interface {
       test02()
    }
    type AInterface interface {
       BInterface
       CInterface
       test03()
    }
    //如果需要实现AInterface,就需要将BInterface CInterface的方法都实现
    type Stu struct {
    
    }
    
    func (stu Stu) test01() {
    
    }
    func (stu Stu) test02() {
    
    }
    func (stu Stu) test03() {
    
    }
    
    type  T interface {
       //空接口
    }
    func main()  {
       var stu Stu
       var t T = stu //ok
       fmt.Println(t)
       var t2 interface{} = stu
       var num1 float64 = 8.8
       t2 = num1
       t = num1
       fmt.Println(t2, t)
       var a AInterface = stu
       a.test01()
    }
    //输出:{}
    //8.8 8.8
    

    接口编程最佳实践

    import (
       "fmt"
       "math/rand"
       "sort"
    )
    //1.声明Hero结构体
    type Hero struct {
       Name string
       Age 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) {
       //交换
       hs[i], hs[j] = hs[j], hs[i]
    }
    //1.声明Student结构体
    type Student struct {
       Name string
       Age int
       Score float64
    }
    
    //将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 HeroSlice
       for 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.Sort
       sort.Sort(heroes)
       fmt.Println("______________排序后______________")
       //看看排序后的顺序
       for _,v := range heroes {
          fmt.Println(v)
       }
       i := 10
       j := 20
       i,j = j,i
       fmt.Println("i = ", i, "j = ", j)
    }
    //[-1 0 7 10 90]
    //{英雄 81 87}
    //{英雄 47 59}
    //{英雄 81 18}
    //{英雄 25 40}
    //{英雄 56 0}
    //{英雄 94 11}
    //{英雄 62 89}
    //{英雄 28 74}
    //{英雄 11 45}
    //{英雄 37 6}
    //______________排序后______________
    //{英雄 56 0}
    //{英雄 37 6}
    //{英雄 94 11}
    //{英雄 81 18}
    //{英雄 25 40}
    //{英雄 11 45}
    //{英雄 47 59}
    //{英雄 28 74}
    //{英雄 81 87}
    //{英雄 62 89}
    //i =  20 j =  10
    

    接口练习题

    实现接口 vs 继承

    package main
    
    import "fmt"
    //Monkey结构体
    type Monkey struct {
       Name string
    }
    //声明接口
    type BirdAble interface {
       Flying()
    }
    type FishAble interface {
       Swimming()
    }
    
    func (this *Monkey) climbing()  {
       fmt.Println(this.Name,"生来会爬树..")
    }
    //LittleMonkey 结构体
    type LittleMonkey struct {
       Monkey //继承
    }
    //让LittleMonkey 实现BirdAble
    func (this *LittleMonkey) Flying() {
       fmt.Println(this.Name,"通过学习,会飞翔...")
    }
    
    //让LittleMonkey 实现FishAble
    func (this *LittleMonkey) Swimming() {
       fmt.Println(this.Name,"通过学习,会游泳...")
    }
    func main()  {
       //创建一个LittleMonkey实例
       monkey := LittleMonkey{
          Monkey{
             Name: "悟空",
          },
       }
       monkey.climbing()
       monkey.Flying()
       monkey.Swimming()
    }
    //输出:悟空 生来会爬树..
    //悟空 通过学习,会飞翔...
    //悟空 通过学习,会游泳...
    
    对上面代码的小结
    	当A结构体继承了B结构体,那么A结构就自动的继承了B结构体的字段和方法,并且可以直接使用
    	当A结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此可以认为:实现接口是对继承机制的补充
    


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

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

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

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

    接口比继承更加灵活 Person Student BirdAble LittleMonkey

    ​ 接口比继承更加灵活,继承是满足is - a的关系,而接口只需满足like - a的关系

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

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

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

    快速入门案例

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

    //声明/定义一个接口
    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接口变量就体现出多态的特点
       //通过usb接口变量来调用Start和Stop方法
       usb.Start()
       usb.Stop()
    }
    func main()  {
       //测试
       //先创建结构体变量
       computer := Computer{}
       phone := Phone{}
       camera := Camera{}
       //关键点
       computer.Working(phone)
       computer.Working(camera)
    }
    

    接口体现多态的两种形式

    多态参数

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

    多态数组

    ​ 演示一个案例:在Usb数组中,存放Phone结构体和Camera结构体变量

    //声明/定义一个接口
    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("相机停止工作...")
    }
    //计算机
    type Computer struct {
    
    }
    func main()  {
       //定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
       //这里体现出多态数组
       var usbArr [3]Usb
       usbArr[0] = Phone{"vivo"}
       usbArr[1] = Phone{"华为"}
       usbArr[2] = Phone{"小米"}
       fmt.Println(usbArr)
    }
    

    类型断言

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

    案例演示

    func main()  {
       //类型断言的其它案例
       var x interface{}
       var b2 float32 = 2.2
       x = b2 //空接口,可以接收任意类型
       //x => float32 [使用类型那个断言]
       y := x.(float32)    //转成具体类型
       fmt.Printf("y 的类型是 %T 值是 = %v", y, y)
    }
    //输出:y 的类型是 float32 值是 = 2.2
    
    对上面代码的说明:
    	在进行类型断言时,如果类型不匹配,就会报panic,因此进行类型断言时,	要确保原来的空接口指向的就是断言的类型
    如何在进行断言时,带上检测机制,如果成功就OK,否则也不要报panic
    func main()  {
       //类型断言的其它案例
       var x interface{}
       var b2 float32 = 3.3
       x = b2 //空接口,可以接收任意类型
       //x => float32 [使用类型那个断言]
       if y, ok := x.(float32); ok {
          fmt.Println("convert success")
          fmt.Printf("y 的类型是%T 值是%v", y, y)
       } else {
          fmt.Println("convert fail")
       }
       fmt.Println("继续执行...")
       //y := x.(float32)    //转成具体类型
       //fmt.Printf("y 的类型是 %T 值是 = %v", y, y)
    }
    //输出:y 的类型是float32 值是3.3继续执行...
    

    类型断言的最佳实践

    在前面的Usb接口案例做改进:

    给Phone结构体增加一个特有的方法call(),当Usb接口接收的是Phone变量时,还需要调用call方法

    package main
    
    import "fmt"
    //声明/定义一个接口
    type Usb interface {
       //声明了两个没有实现的方法
       Start()
       Stop()
    }
    type Phone struct {
       name string
    }
    //让Phone实现Usb接口的方法
    func (p Phone) Start() {
       fmt.Println(p.name, "手机开始工作...")
    }
    func (p Phone) Stop() {
       fmt.Println(p.name, "手机停止工作...")
    }
    
    type Camera struct {
       name string
    }
    //让Camera实现Usb接口的方法
    func (c Camera) Start() {
       fmt.Println(c.name, "相机开始工作...")
    }
    func (c Camera) Stop() {
       fmt.Println(c.name, "相机停止工作...")
    }
    //计算机
    type Computer struct {
    }
    //编写一个方法Working方法,接收一个Usb接口类型变量
    //只要是实现了Usb接口(所谓实现Usb接口,就是指实现了Usb接口声明所有方法)
    func (c Computer) Working(usb Usb) { //usb变量会根据传入的实参,来判断到底是Phone,还是Camera  //usb接口变量就体现出多态的特点
       //通过usb接口变量来调用Start和Stop方法
       usb.Start()
       usb.Stop()
    }
    func main()  {
       //测试
       computer := Computer{}
       var usbArr [3]Usb
       usbArr[0] = Phone{"华为"}
       usbArr[1] = Phone{"vivo"}
       usbArr[2] = Camera{"华为"}
       fmt.Println(usbArr)
       for i := 0; i < len(usbArr); i++ {
          computer.Working(usbArr[i])
       }
    }
    //[{华为} {vivo} {华为}]
    //华为 手机开始工作...
    //华为 手机停止工作...
    //vivo 手机开始工作...
    //vivo 手机停止工作...
    //华为 相机开始工作...
    //华为 相机停止工作...
    

    写一函数,循环判断传入参数的类型:

    /编写一个函数,可以判断输入的参数是什么类型
    func TypeJudge(items... interface{}) {
       for index, x := range items {
          switch x.(type) {
          case bool:
             fmt.Printf("第%v个参数是 bool 类型,值是%v
    ", index, x)
          case float32:
             fmt.Printf("第%v个参数是 float32 类型,值是%v
    ", index, x)
          case float64:
             fmt.Printf("第%v个参数是 float64 类型,值是%v
    ", index, x)
          case int, int32, int64:
             fmt.Printf("第%v个参数是 整数 类型,值是%v
    ", index, x)
          case string:
             fmt.Printf("第%v个参数是 string 类型,值是%v
    ", index, x)
          default:
             fmt.Printf("第%v个参数是   类型,值是%v
    ", index, x)
          }
       }
    }
    func main()  {
       var n1 float32 = 1.1
       var n2 float64 = 2.3
       var n3 int32 = 30
       var name string = "tom"
       address := "北京"
       n4 := 300
       TypeJudge(n1, n2, n3, name, address, n4)
    }
    //第0个参数是 float32 类型,值是1.1
    //第1个参数是 float64 类型,值是2.3
    //第2个参数是 整数 类型,值是30
    //第3个参数是 string 类型,值是tom
    //第4个参数是 string 类型,值是北京
    //第5个参数是 整数 类型,值是300
    

    在前面代码的基础上,增加判断Student类型和*Student类型

    package main
    
    import (
    	"fmt"
    )
    
    type Student struct {
    	name string
    }
    
    //编写一个函数,可以判断输入的参数是什么类型
    func TypeJudge(items... interface{}) {
    	for index, x := range items {
    		switch x.(type) {
    		case Student:
    			fmt.Printf("第%v个参数是 Student 类型,值是%v
    ", index, x)
    		case *Student:
    			fmt.Printf("第%v个参数是 *Student 类型,值是%v
    ", index, x)
    		case bool:
    			fmt.Printf("第%v个参数是 bool 类型,值是%v
    ", index, x)
    		case float32:
    			fmt.Printf("第%v个参数是 float32 类型,值是%v
    ", index, x)
    		case float64:
    			fmt.Printf("第%v个参数是 float64 类型,值是%v
    ", index, x)
    		case int, int32, int64:
    			fmt.Printf("第%v个参数是 整数 类型,值是%v
    ", index, x)
    		case string:
    			fmt.Printf("第%v个参数是 string 类型,值是%v
    ", index, x)
    		default:
    			fmt.Printf("第%v个参数是   类型,值是%v
    ", index, x)
    		}
    	}
    }
    func main()  {
    	var n1 float32 = 1.1
    	var n2 float64 = 2.3
    	var n3 int32 = 30
    	var name string = "tom"
    	address := "北京"
    	n4 := 300
    	var b bool = false
    	stu1 := Student{"zise"}
    	stu2 := &Student{"feizhzu"}
    	TypeJudge(n1, n2, n3, name, address, n4, b, stu1, stu2)
    }
    //第0个参数是 float32 类型,值是1.1
    //第1个参数是 float64 类型,值是2.3
    //第2个参数是 整数 类型,值是30
    //第3个参数是 string 类型,值是tom
    //第4个参数是 string 类型,值是北京
    //第5个参数是 整数 类型,值是300
    //第6个参数是 bool 类型,值是false
    //第7个参数是 Student 类型,值是{zise}
    //第8个参数是 *Student 类型,值是&{feizhzu}
    
  • 相关阅读:
    英语语法总结---二、英语中的从句是怎么回事
    【Cocos得知】技术要点通常的积累
    政府采购清单应包括“问题” 积
    Ubuntu通过使用PyCharm 进行调试 Odoo 8.0 可能出现的问题
    Android自己定义组件系列【8】——面膜文字动画
    手机新闻网站,手持移动新闻,手机报client,jQuery Mobile手机新闻网站,手机新闻网站demo,新闻阅读器开发
    OS和android游戏纹理优化和内存优化(cocos2d-x)
    删除重复数据
    MyEclipse2014 设备 checkstyle、PMD、findbugs 最简单的方法 详细说明
    hdu5044 Tree 树链拆分,点细分,刚,非递归版本
  • 原文地址:https://www.cnblogs.com/zisefeizhu/p/12634276.html
Copyright © 2011-2022 走看看