• Go语言 6 结构体、方法和接口


     文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/

    Go学习群:415660935

    结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合。这些数据称为结构体的成员。Go语言的结构体和其他语言的类有同等的地位,但Go语言放弃了包括继承在内的大量面向对象特性,只保留了组合这个最基础的特性。这也体现了Go语言的简洁性。这章内容主要实现了Go语言的面向对象编程。

    6.1.1 结构体定义

    定义一个结构体相当于定义了一个新的类型。定义结构体的一般形式:

    type 类型名称 struct {

       成员列表

    }

       例如,定义一个类型Person

    type Person struct {

      name string

      age  int

    }

    另外需要注意的是,结构体成员的输入顺序也有重要的意义。如果成员的顺序不同,意味着定义了不同的结构体类型。一个命令为S的结构体类型将不能再包含S类型的成员,因为一个聚合的值不能包含它自身(该限制同样适应于数组)但是S类型的结构体可以包含*S指针类型的成员,这可以创建递归的数据结构,例如链表和树结构。如果结构体没有任何成员的话,就是空结构体,写作struct{}.它的大小为0,也不包含任何信息。

    6.1.2结构体初始化

    结构体变量的的初始化可以按顺序直接初始化,也可以按照字段:值的方式初始化,这种方式的初始化对于字段的初始化的顺序可以是任意的。另外,也可以先定义结构体变量,随后赋值。

    p1 := Person{"张三", 20} // 1 按顺序初始化

    fmt.Println(p1)

    p2 := Person{age: 18, name: "李四"} // 2采用字段:值的方式初始化,可以任意顺序

    fmt.Println(p2)

    p3 := Person{}// 3 未显示初始化,其成员默认被初始化为零值

    fmt.Println(p3)

    p3.name = "王五" //给每个成员赋值

    p3.age = 19

    fmt.Println(p3)

    6.1.3 匿名组合实现继承

    1 匿名组合的实现

    只声明一个成员对应的数据类型而不指明成员的名字,这类成员就叫匿名成员。通过匿名成员实现的匿名组合,可以完成继承的功能。下面定义一个Student类,继承Person类的功能。匿名组合类型相当于以其类型名称(去掉包名部分)作为成员变量的名字。 

    type Person struct {

        name string

        age  int

    }

    type Student struct {

        Person

        major string

    }

     

    func main() {

      s1 := Student{Person{"张三", 18}, "计算机应用"}

      fmt.Println(s1)

      fmt.Println(s1.Person)

      fmt.Println("姓名:", s1.Person.name, "年龄:", s1.Person.age, 

    "专业:", s1.major)

      fmt.Println("姓名:", s1.name, "年龄:", s1.age, "专业:", s1.major)

    }

    //打印结果:

    {{张三 18} 计算机应用}

    {张三 18}

    姓名: 张三 年龄: 18 专业: 计算机应用

    姓名: 张三 年龄: 18 专业: 计算机应用

    2匿名组合的重名问题 

    以下的示例中,Base1、Base2和Child名称相同。另外,匿名组合中Base2使用了指针类型。

    type Base1 struct {

        name string

    }

    type Base2 struct {

        name string

    }

    type Child struct {

        Base1

        *Base2

        name string

    }

     

    func main() {

        c := Child{Base1{"base1"}, &Base2{"base2"}, "child"}

        fmt.Println(c)

        fmt.Println(c.name)

        fmt.Println(c.Base1.name)

        fmt.Println(c.Base2.name)

    }

     

    //打印结果:

    {{base1} 0xc0420082c0 child}

    child

    base1

    base2

    6.1.4 匿名结构

    func main() {

      a := &struct {

          Name string

          Age  int

      }{

          Name: "zhangsan",

          Age:  18,

      }

      fmt.Println(a)

      b := struct{}{}

      fmt.Println(b)

    }

    //打印结果:

    &{zhangsan 18}

    {}

    6.1.5 结构体比较

    如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的。可比较的结构体类型和其它可比较的类型一样,可以用于mapkey类型。

    6.2 方法

    方法就是与某个类型(也可以是内置类型)关联的函数。理所当然我们可以给一个具体的结构体类型添加方法,使得它更像面向对象中的类。

    6.2.1 方法声明

    在函数声明时,在其名字之前放上一个变量,即是一个方法。相当于为这种类型定义了一个独占的方法。方法可以被声明到任意类型,只要这个类型本身不是一个指针或接口。只有类型和指向它们的指针才可以是接收器。如果一个类型名本身是一个指针的话,是不允许出现在接收器中的。

    方法声明的语法格式如下:

    func  (变量名 类型)方法名称( [形参列表] ) [返回值列表]{

       方法体

    }

    func  (变量名 *类型)方法名称( [形参列表] ) [返回值列表]{

       方法体

    }

    接下来定义了一个新类型Integer,它和int没有本质不同,只是增加了个新方法Less()

    type Integer int

     

    func (a Integer) Less(b Integer) bool {

      return a < b

    }

     

    func main() {

      var i Integer = 2

      fmt.Println(i.Less(3)) //调用对象i的Less方法

    }

    在上面的例子中我们把Integer看着面向对象编程中的类,iInteger类型的对象,Less()是对象的方法。如果需要修改对象i的值,这就需要使用指针作为接收器。

    type Integer int

     

    func (a *Integer) Add(b Integer) {

      *a += b

    }

    func main() {

      var i Integer = 2

      i.Add(3)

      fmt.Println(i) // 5

    }

    不管你的方法的接收者是指针类型还是非指针类型,都可以通过指针/非指针类型进行调用,编译器会自动做类型转换。在实际的项目开发中,一般会约定如果某个类型有一个指针作为接收器的方法,那么所有该类型的所有方法都必须有一个指针接收器。

    在声明一个方法的接收者该是指针还是非指针类型时,你需要考虑两方面:

    1>对象本身是否特别大,如果声明为非指针变量时,调用会产生一次拷贝;

    2>如果使用指针类型,需要注意这种指针类型指向的始终是一块内存地址,就算你对其进行了拷贝。

    6.2.2 方法值和方法表达式

    某个对象的方法称为方法值,某个类型的方法称为方法表达式。

    type Integer int

     

    func (a Integer) Add(b Integer) Integer {

      return a + b

    }

    func main() {

      var i Integer = 2

      add := i.Add         //方法值:将对象i的方法作为值赋值给add变量

      fmt.Println(add(3))

      add2 := Integer.Add //方法表达式:将类型Integer的方法赋值给add2变量,add2的第一个参数作为接收器

      fmt.Println(add2(i, 3))

    }

       

    方法表达式可以根据变量来决定调用同一类型的不同方法。

    type Integer int

     

    func (a Integer) Add(b Integer) Integer {

        return a + b

    }

    func (a Integer) Sub(b Integer) Integer {

        return a - b

    }

    func Operator(i, j Integer, sel bool) Integer {

        var op func(a, b Integer) Integer

        if sel {

            op = Integer.Add

        } else {

            op = Integer.Sub

        }

        return op(i, j)

    }

     

    func main() {

        fmt.Println(Operator(10, 5, true))   //15

        fmt.Println(Operator(10, 5, false))  //5

    }

    6.2.3 可见性

    Go语言只有一种控制可见性的手段:首字母大写可见,小写字母则不可见。Go语言中符号的可访问性是包一级的而不是类型一级的。

    package main

     

    import (

        "fmt"

    )

     

    type Person struct {

        Name string

        age  int

    }

     

    func (p Person) Show() {

        fmt.Println("Name:", p.Name, "age:", p.age)

    }

    func (p *Person) setAge(age int) {

        p.age = age

    }

    func main() {

        p := Person{"张三", 18}

        p.Show()     //包外也可见

        p.age = 19   //包内可见,包外不可见

        p.setAge(20) //包内可见,包外不可见

        p.Show()

    }

    6.3 接口

    6.3.1接口类型

    接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起。很多面向对象的语言都有类似的接口概念,但是Go语言中接口类型的独特之处在于它是满足隐式实现的。另外,一个接口也可以嵌入其它接口,即接口组合。

    6.3.2 实现接口的条件

    一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。
     

    // 1 定义Phone接口

    type Phone interface {

        Call()

    }

    //  定义DumbPhone类型,并实现了Phone接口

    type DumbPhone struct { //非智能手机

        Name string

    }

    func (dp DumbPhone) Call() {

        fmt.Println("使用" + dp.Name + "非智能机打电话!")

    }

     

    // 2 定义Camera接口

    type Camera interface {

        TakePicture()

    }

    // 定义DigitalCamera类型,并Camera接口

    type DigitalCamera struct { //数码相机

        Name string

    }

    func (d DigitalCamera) TakePicture() {

        fmt.Println("使用" + d.Name + "数码相机照相!")

    }

     

    // 3 定义PhoneCamera接口,嵌入了Phone和Camera接口

    type PhoneCamera interface {

        Phone

        Camera

    }

    type CameraPhone interface {//与PhoneCamera接口等价,虽然顺序不同

        TakePicture()

        Call()

    }

    // 3 定义SmartPhone类型,并实现了PhoneCamera接口

    type SmartPhone struct { // 智能手机

        Name string

    }

     

    func (sp SmartPhone) Call() {

        fmt.Println("使用" + sp.Name + "智能机手机打电话!")

    }

    func (sp SmartPhone) TakePicture() {

        fmt.Println("使用" + sp.Name + "智能手机照相!")

    }

    6.3.3 接口赋值

    1 将一个对象赋值给一个接口

    func main() {

        var p Phone

        p = &DumbPhone{Name: "诺基亚"}

        p.Call()

        p = &SmartPhone{Name: "华为"}

        p.Call()

     

        var c Camera

        c = &DigitalCamera{Name: "佳能"}

        c.TakePicture()

        c = &SmartPhone{Name: "华为"}

        c.TakePicture()

     

        var pc PhoneCamera

        pc = &SmartPhone{Name: "华为"}

        pc.Call()

        pc.TakePicture()

     

    }

    2 将一个接口赋值给另一个接口

    Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么这两个接口是等价的,可以相互赋值。 接口PhoneCamera与接口CameraPhone等价。

    var pc PhoneCamera

        pc = &SmartPhone{Name: "华为"}

        pc.Call()

        pc.TakePicture()

        var cp CameraPhone

        cp = pc          //将一个接口赋值给另一个等价接口

        cp.Call()

        cp.TakePicture()

    接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A。 接口Phone是接口PhoneCamera的子集,接口Camera也是接口PhoneCamera的子集。所以可以有如下的赋值:

    var pc PhoneCamera

        pc = &SmartPhone{Name: "华为"}

        var p Phone

        var c Camera

        p = pc                //父集赋值给子集

        p.Call()

        c = pc                //父集赋值给子集  

        c.TakePicture()

    6.3.4 接口查询

    接口查询语法类似x.(T)x表示一个接口,T表示另一个接口类型。用来判断接口x操作的对象的是否实现了另一个接口类型。

    var p Phone

        p = &SmartPhone{Name: "华为"}

        p.Call()

        if pc, ok := p.(PhoneCamera); ok {

            pc.TakePicture()

        }

    6.3.5 空接口

    由于Go语言中任何对象实例都满足空接口interface{},所以interface{}看起来像是可
    以指向任何对象的类型。一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。

    6.3.6 类型查询

    Go语言中,还可以更加直截了当地询问接口指向的对象实例的类型。

    //伪代码如下

    var v1 interface{} = ...

    switch v := v1.(type) {

    case int: // 现在v的类型是int

    case string: // 现在v的类型是string

    ...

        }

  • 相关阅读:
    【转载】MDX 去年当月值、差值、同比
    【原创】Analyzer安全性异常(应用程序视图执行安装策略不允许的操作)
    Analyzer普通用户登录不了[从网络访问此计算机]
    【转载】51CTO如何防止SQL注入的解决方法
    【转载】51CTOAndroidManifest.xml文件详解
    Eclipse插件安装方式
    Typemock揭示 安装其它三方软件可能引起冲突,那试试不安装直接引用它的DLL
    项目从VS2010 升 VS2012 遇到的代表性问题及解决
    c# comboBox模糊匹配
    sqlJDBC安装使用
  • 原文地址:https://www.cnblogs.com/mazg/p/8275190.html
Copyright © 2020-2023  润新知