Go语言入门[六] 结构体和接口| 青训营

630 阅读5分钟

golang.jpeg

type

  • Go语言提供了type关键字用于一些特殊情况的自定义。它发挥的作用和C++typedef的作用十分相似,或者说,你甚至可以把它看成Go语言中的typedef

  • 对于一些复杂的数据类型,我们可以使用type使其拥有别名

package main

import (
    "fmt"
    "reflect"
)

type stringList []string

func main() {
    strList := stringList{"test1", "test2"}
    fmt.Println("strList的数据类型:", reflect.TypeOf(strList))
    fmt.Println("strList的值:", reflect.ValueOf(strList))
}
  • 对于一些枚举类型,我们也可以使用别名使得其表达的意思更为清晰
package main

import (
    "fmt"
)

type Grade int

const (
    FirstGrade Grade = iota
    SecondGrade
    ThirdGrade
    FourthGrade
    FifthGrade
    SixthGrade
)

func main() {
    fmt.Println("FirstGrade :", FirstGrade)
    fmt.Println("SecondGrade:", SecondGrade)
    fmt.Println("ThirdGrade :", ThirdGrade)
    fmt.Println("FourthGrade:", FourthGrade)
    fmt.Println("FifthGrade :", FifthGrade)
    fmt.Println("SixthGrade :", SixthGrade)
}
  • 后文中讲到的自定义结构体类型其实也会用到type,如此你可以这么看:我们使用type Name struct{ }的形式声明了一个结构体,并把这个结构体别名为Name,是不是一下就理解为何要声明的时候使用到type关键字了呢?

结构体

结构体类型

  • 结构体类型的变量的定义大致形式如下所示
var struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}
  • 这是一个变量,这意味着我们可以在函数内定义并使用它
package main

import (
    "fmt"
    "reflect"
)

func main() {
    var student1 struct {
        Name string
        Age  int
    }
    student1.Name = "Codey"
    student1.Age = 18

    fmt.Println("student1数据类型:", reflect.TypeOf(student1))
    fmt.Println("student1的值:", reflect.ValueOf(student1))
}
  • 如果要使用变量的简易定义形式:=,需要注意对应值的映射后一定要加,
package main

import (
    "fmt"
    "reflect"
)

func main() {
    student2 := struct {
        Name string
        Age  int
    }{
        Name: "Codey",
        Age:  18, //这个逗号千万不能忘记,若是和大括号同行,这个逗号才可以省略
    }

    fmt.Println("student2数据类型:", reflect.TypeOf(student2))
    fmt.Println("student2的值:", reflect.ValueOf(student2))
}

自定义结构体类型

  • 自定义结构体的方式和其它语言差不多,只是需要type关键字需要额外记忆
package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Name string
    Age  int
}

func main() {
    var student1 Student
    student1.Name = "Codey"
    student1.Age = 18

    fmt.Println("student1数据类型:", reflect.TypeOf(student1))
    fmt.Println("student1的值:", reflect.ValueOf(student1))
}
  • 事实上,Go语言既不是面向对象的,也不是面向过程的,身为一个极其灵活的语言,虽然它不提供面向对象类似语言的特质(拥有类,可以在类内写构造函数等),但是如果我们想要实现类似的对象.函数式的调用方法,该怎么实现?
package main
import (
    "fmt"
)

type Student struct {
    Name string
    Age  int
}
func (s Student) PrintAge() {
    fmt.Println(s.Age)
}
func main() {
    var student Student
    student.Name = "Tom"
    student.Age = 18
    fmt.Println("student年龄:")
    student.PrintAge()
}
  • 如果无法理解,可以换另一种方式来看:PrintAge()函数前接受一个s类型的对象,定义一个函数使得传入的对象可以调用这个函数,从而实现模拟面向对象编程的效果

结构体内嵌类型

  • 结构体在定义属性字段的时候可以只写类型,不写属性名,由编译器自主推导为相应字段赋值。但一个类型的字段只能写一个,因为编译器无法自然顺延相应类型的推导值。像这样只定义类型,不写属性名的字段被称为匿名字段
package main

import "fmt"

type Student struct {
    Name string
    Age  int
    int
}

func newStudent(name string, age int) Student {
    return Student{Name: name, Age: age, int: 10}
}
func main() {
    stu := newStudent("Codey", 18)
    fmt.Println("匿名字段的值:", stu.int)
}
  • 在引用相应的值时,只需要变量.数据类型即可

结构体内嵌结构体

  • Go语言中没有继承这个概念,结构体的内嵌一般用于处理拥有相同属性的类型,但是Youtube有一个很有趣的Clean Code Struct相关的视频有说过,如果不是必要,最好减少继承。对于一般的情况,比如我们不需要关心代码执行的实行过程如何,只需要调用相同的方法时,我们大可以采用接口实现,用组合的形式,让接口成为规范程序运行的协议,因为接口没有规定过多的程序行为,所以组合会比继承更加轻量Go的内嵌更类似于组合而不是继承
package main

import "fmt"

type Callable struct{}

func (c Callable) Call() {
    fmt.Println("can call")
}

type Photographic struct{}

func (p Photographic) Photograph() {
    fmt.Println("can take photos")
}

type Moblie struct {
    Callable
    Photographic
}

type Pad struct {
    Photographic
}

func main() {
    var m Moblie
    fmt.Println("Moblie Function:")
    m.Call()
    m.Photograph()

    var p Pad
    fmt.Println("Pad Function:")
    p.Photograph()
}
  • 这个例子生动形象地展示了组合的实际状态,我们可以按需组合或内嵌,使得新生成的结构体符合我们的需求

接口

  • Go语言的接口和其它语言相同,轻量且可以使得程序符合多态的设计。接口的一般定义如下所示
var interface_name interface { //接口类型的变量
    Print()// 接口需要实现的函数
}
type interface_name interface { // 自定义接口类型
    Print()// 接口需要实现的函数
}
  • Go语言的接口可以被称之为鸭子类型,即“走起来像鸭子,叫起来像鸭子,那么它就是一只鸭子”。换句话说,如果这个类型模拟了接口中需要实现的所有方法,那么我们就说这个类型实现了这个接口。Go语言中的接口有以下两种特性

    • 只要有类实现(模拟)了接口中包含的所有方法,我们就称这个类型实现了这个接口

    • 只要是实现了这个接口的类型,用这个类型定义的变量就可以给这个接口声明的接口赋值

  • 也就是说,接口类型(interface{})类型的变量,可以接收任何变量的赋值

package main

import (
    "fmt"
)

var stuInterface interface {
    PrintAge()
}

type Student struct {
    Name string
    Age  int
}

func (s Student) PrintAge() {
    fmt.Println(s.Age)
}

func main() {
    stuInterface = Student{
        Name: "Codey",
        Age:  18,
    }
    stuInterface.PrintAge()
}
  • 这里的示例我们定义了一个接口变量,然后让Student这个结构体模拟了接口的实现,在其后我们就可以直接把Student类型的变量赋值给stuInterface,调用接口中已经定义的函数。运行无任何异常。