Go语言中的自定义类型与别名详解

silverwq
2022-07-12 / 0 评论 / 221 阅读 / 正在检测是否收录...

介绍

使用关键字定义类型有两种操作:

  1. 不带有 = 的是新类型定义
  2. 带有 = 的是别名定义

自定义类型

通过 type 关键字自定义类型, 是一个全新的类型,底层数据结构一模一样。主要用途是进行概念上进行区分于另外一个类型,是得业务更加清晰明了。

虽然说,它们的底层内存数据结构一样,但是它们概念上是不同。

package main

// Duration 定义一个时长类型
type Duration int64

const (
    Nanosecond  Duration = 1
    Microsecond          = 1000 * Nanosecond
    Millisecond          = 1000 * Microsecond
    Second               = 1000 * Millisecond
    Minute               = 60 * Second
    Hour                 = 60 * Minute
)

func main() {
    var a int64
    const b int64 = 5
    var c Duration
    a = int64(c)
    c = Duration(a) * Hour // 不同类型之间,需要转换,小括号类型转换
    c = 5 * Hour           // 字面量会自动执行类型转
}

Duration 就是一种新的类型,它底层和 int64 一样,但是代表的意思不一样。不同类型之间,需要转换,小括号类型转换,需要注意的是,字面量会自动执行类型转换。

再举个例子,下例中,Plane 类型和 Car 类型的数据结构一样,但是代表的东西不一样,拥有的方法也不一样。

package main

type Car struct {
    Name string
}

func (c Car) Drive() {

}

// Plane 类型和Car类型的数据结构一样,成员变量一样
// 但是方法不一样,概念不一样
type Plane Car

func (p Plane) Fly() {

}

func main() {
    car := Car{
        Name: "车子",
    }
    car.Drive()

    plane := Plane{
        Name: "飞机",
    }
    plane.Fly()
}

类型转换

自定义的类型,是个全新的类型,因为他们底层都一样,可以进行类型转换。对于每个类型,都有一个类型转换操作 T(x),以下两种情况可以类型之间可以相互转换

  1. 都拥有同样的底层类型的话
  2. 都是指向同一种类型的指针类型

需要注意的是:数字类型之间、字符串和一些 slice 类型也是允许转换的,例如:

  1. 浮点数转整数,小数部分丢失
  2. 字符串转为 []byte 切片,会分配一份字符串数据副本

命令类型的底层类型,决定了它支持什么操作,例如底层如果是数值类型的话,那么就支持算数运算。

option 模式

这个模式在 go 语言里到处都是,很常见。主要有两种方式:

  1. 函数模式
  2. 接口模式

方式一:将 option 定义为匿名函数模式

这个模式比较简单,主要是在传入回调函数,然后在构造函数里调用回调函数,修改默认的值。

package main

import "fmt"

type User struct {
    Name string
    Age  int
}

// UserOption 函数的参数一样要是指针类型,因为要修改User结构体
// 用于修改user结构体实例
type UserOption func(*User)

// WithName 用于生产UserOption
func WithName(name string) UserOption {
    return func(u *User) {
        // 调用的时候,这个name会被实际值替换掉,这样的话可以灵活的赋值,例如WithName("张三")
        // 此时相当于u.Name="张三"
        u.Name = name 
    }
}

// WithAge 用于生产UserOption
func WithAge(age int) UserOption {
    return func(u *User) {
        // 调用的时候,这个age会被实际值替换掉,例如WithName(18)
        // 此时相当于u.Age=18
        u.Age = age
    }
}

// opts 定义成不定长的参数
func NewUser(opts ...UserOption) *User {
    // 默认值
    user := &User{
        Age:  30,
        Name: "小明",
    }
    // 修改默认值
    for _, opt := range opts {
        opt(user)
    }
    return user
}

func main() {
    // 传人参数,修改默认值
    user := NewUser(
        WithName("张三"),
        WithAge(18),
    )
    fmt.Println(user)
}

方式二:将 options 定义为接口模式

比第一种写法复杂了很多,优势是:接口里可以包含多个函数,后续如果要新增方法的话,调整起来比较方便。

package main

import "fmt"

type User struct {
    Name string
    Age  int
}

// UserOption 函数的参数一样要是指针类型,因为要修改User结构体
// 用于修改user结构体实例
type UserOption interface {
    // Apply 因为也是要改user实例的内容,所以也要指针类型
    Apply(*User)
}

type UserName struct {
    Name string
}

// Apply 实现UserOption接口
func (u *UserName) Apply(user *User) {
    user.Name = u.Name
}

func NewUserName(name string) *UserName {
    return &UserName{
        Name: name,
    }
}

type UserAge struct {
    Age int
}

// Apply 实现UserOption接口
func (u *UserAge) Apply(user *User) {
    user.Age = u.Age
}

func NewUserAge(age int) *UserAge {
    return &UserAge{
        Age: age,
    }
}

func NewUser(opts ...UserOption) *User {
    // 默认值
    user := &User{
        Age:  30,
        Name: "小明",
    }
    // 修改默认值
    for _, opt := range opts {
        opt.Apply(user)
    }
    return user
}

func main() {
    // 传人参数,修改默认值
    user := NewUser(
        NewUserName("张三"),
        NewUserAge(18),
    )
    fmt.Println(user)
}

别名

别名类型是带有等号的,主要用途是:为了使用方便,减少代码量,例如 interface{} 用别名 Any 来替换,用起来方便。

别名类型只有在代码中存在,编译完成不会有别名类型,所以可以理解为,别名类型和原来的类型一模一样,可以互相比较赋值。

package main

// go 源码里定义的别名
type byte = uint8
type rune = int32
type any = interface{}

func useByteFunc(a byte) {

}

func main() {
    var a uint8
    // 因为byte是uint8的别名,所以可以直接复制
    var b byte = 100
    a = b
    // unit8类型的变量也可以直接赋值给byte类型
    useByteFunc(a)
}

当然,别名类型的源类型,可以是自定义类型。

// Car 自定义类型
type Car struct {
}

type BlackCar = Car

函数类型

函数类型和结构体类型的初始化方式不一样。函数类型可以有自己成员方法,但是没有成员变量。实例化的话,需要传递一个同个类型的函数进来。

package main

// FV 是一个函数类型的变量
var fv = func(arg int) {}

// FT 是一样类型,没有成员变量
type FT func(arg int)

// Hello 类型可以有自己的成员方法
func (ft FT) Hello(arg int) {
    ft(arg)
}

// IFC FT类型可以实现接口
type IFC interface {
    Hello(arg int)
}

func main() {
    // 函数后面加括号,表示调用
    fv(3)
    // 由于FT没有成员变量,不是struct,创建实力不能用{}
    // 需要用()传递一个类型匹配的函数进来,FT的实力可以当做函数使用
    FT(fv)(3)
    // 函数实例可以调用成员方法
    FT(fv).Hello(3)
}
0

评论 (0)

取消