Go语言类型系统详解

138 阅读5分钟

用户自定义类型

结构体类型

结构体类型通过组合一系列固定且唯一的字段来声明,每个字段类型既可以是内置类型,也可以是其他自定义的类型。

type User struct {
    id int
    name string
    email string
}
//声明结构体类型变量并初始化零值(结构体中的每个字段都会用零值初始化)
var u1 User
//赋值
u1.id = 1
u1.name = "zhangsan1"
u1.email = "zhangsan1@163.com"

还可以用如下方式在变量声明时进行初始化赋值:

//这种形式对声明顺序没有要求
u2 := User{
    id:2,
    name:"zhangsan2",
    email:"zhangsan2@163.com",
}
//这种形式要和结构体声明中的字段顺序一致
u3 := User{id:3, name:"zhangsan3", email:"zhangsan3@163.com"}

基于已有类型

另一种声明用户自定义类型的方法是基于一个已有的类型,使用这种声明类型的方法,从内置类型中创建出更多类型,赋予更高级的功能。

type Interger int

Go认为这两种类型是不同的类型,不同类型不能相互赋值,也不会对不同类型的值做隐式转换。

方法和接口

方法

Go语言中可以通过struct或者基于内置类型进行类型自定义,这些自定义的类型可以添加方法(有点类似于class的概念)。方法实际上也是函数,只是在声明时,在关键字func和方法名之间增加一个参数,这个参数被称为接收者,将函数与接收者的类型绑定在一起。

func (接收者) 函数名(函数参数) 返回类型 {
    函数体
}

Go语言里有两种类型接收者:

  • 值接收者:如果使用值接受者声明方法,调用时会使用这个值的一个副本来执行。
  • 指针接收者:如果使用指针接收者声明方法,使用时会共享调用方法时接收者所指向的值。

Go语言里可以给任何类型添加相应方法(包括基于内置类型定义的类型,但不包括指针类型)。

package main

import (
    "fmt"
)

type User struct {
    id int
    name string
    email string
}

//值接收者
func (u User) changeQEmail() {
    u.email = "zhangsan@qq.com"
}

//指针接收者
func (u *User) changeGEmail() {
    u.email = "zhangsan@gmail.com"
}

func main() {
    u := User{
        id:1,
        name:"zhangsan",
        email:"zhangsan@163.com",
    }

    u.changeQEmail()
    fmt.Println(u.email)
    //输出结果:zhangsan@163.com

    u.changeGEmail()
    fmt.Println(u.email)
    //输出结果:zhangsan@gmail.com
}

除了可以给结构体类型添加方法外还可以给内置类型添加方法,如下所示:

package main

import (
    "fmt"
    "math"
)

type MyFloat float64

func (f MyFloat) abs() float64 {
    return math.Abs(float64(f))
}

func main() {
    //var v MyFloat = -2.5
    v := MyFloat(-2.5)
    fmt.Println(v.abs())
}

在声明一个新类型后,声明一个该类型的方法之前,需要确定使用值接收者还是指针接收者?如果要创建一个新值,该类型的方法就使用值接收者,如果是要修改当前值,就使用指针接收者。

接口

接口类型是由一组方法定义的集合,接口中定义的方法只有函数签名,没有具体实现。如果某个数据类型实现了接口中定义的方法,则称这个数据类型实现了这个接口。

package main

import (
    "fmt"
)

//定义接口
type Mobile interface {
    call() string
}

type IPhone struct {
}

type Android struct {
}

//实现接口中的方法
func (m IPhone) call() string {
    return "iphone"
}

//实现接口中的方法
func (m Android) call() string {
    return "android"
}

func main() {
    //定义一个接口类型的值
    var m Mobile
    //存放IPhone类型的值
    m = new(IPhone)
    fmt.Println(m.call())
    //存放Android类型的值
    m = new(Android)
    fmt.Println(m.call())
}

接口类型的值可以存放实现这些方法的任何类型的值。

面向对象编程

Go语言在包级别进行访问控制,以小写字母开头的名称只在该程序包中可见(相当于private),以大写字母开头的名称在包外也可访问(相当于public)。

Go语言没有类的概念,但它支持数据类型可以定义对应的方法。所谓的方法其实就是函数,只不过与普通函数相比,这类函数是作用在某个数据类型上,所以在函数签名中会有receiver(接收者)来表明当前定义的函数会作用在哪个类型上。Go支持在struct类型或基于内置类型定义的新类型上都可以定义方法,只不过在实际项目中,方法多定义在struct类型上,从这点看我们可以将Go中的struct看作是轻量级的“类”,然后通过struct组合的方式实现在传统面向对象语言中继承的概念。

package main

import (
    "fmt"
)

type Student struct {
    name string
    age int
    gender string
}

type CollegeStudent struct {
    //通过组合的方式实现继承
    Student
    university string
    major string
}

//定义Student类型的方法
func (s Student) getName() string {
    return s.name
}

//定义CollegeStudent类型的方法
func (s CollegeStudent) getMajor() string {
    return s.major
}

//定义CollegeStudent类型的方法
func (s CollegeStudent) getAge() int {
    return s.age
}

func main() {
    s := CollegeStudent{Student{"张三", 21, "男"}, "天津大学", "计算机科学与技术"}
    fmt.Println(s.getName())
    fmt.Println(s.getMajor())
    fmt.Println(s.getAge())
}

通过struct类型和组合的方式我们可以在Go中实现封装和继承,多态也是面向对象很重要的特性之一,Go通过接口实现了这个特性。

package main

import (
    "fmt"
    "math"
)

//定义geometry接口
type geometry interface {
    area() float64
    perimeter() float64
}

//定义rect类型
type rect struct {
    width, height float64
}

//定义circle类型
type circle struct {
    radius float64
}

//rect类型实现geometry接口
func (r rect) area() float64 {
    return r.width * r.height
}

func (r rect) perimeter() float64  {
    return r.width*2 + r.height*2
}

//circle类型实现geometry接口
func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}

func (c circle) perimeter() float64  {
    return math.Pi * c.radius * 2
}

//定义的接口可以作为一种数据类型,当一个变量的类型为一个接口类型时,这个变量可以调用接口中定义的方法
//这里定义一个函数,接收一个类型为geometry的参数变量
func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perimeter())
}

func main() {
    r := rect{width:3, height:5}
    c := circle{radius:5}
    //rect和circle结构类型都实现了geometry接口,所以我们能够把它们的对象作为参数传给接口类型的变量
    measure(r)
    measure(c)
}

接口就是一组方法声明的集合,不具体实现方法。任何类型实现了在接口中声明的全部方法,则表明该类型实现了对应的接口。接口作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值。