go语言类型别名

本文探讨了Go语言1.9版本引入的类型别名特性,它解决了类型兼容性问题。类型别名与类型定义的主要区别在于,别名类型与原类型相同,而类型定义则创建新的类型。在非本地类型上不能定义方法,且在结构体嵌入时,别名不会增加额外成员。文章通过示例代码展示了类型别名的使用和限制,并指出在调用方法时可能产生的歧义。

关键字版本浅谈

​ 类型别名是 Go 1.9 版本添加的新功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。在 C/C++ 语言中,代码重构升级可以使用宏快速定义一段新的代码,Go语言中没有选择加入宏,而是解决了重构中最麻烦的类型名变更问题

在 Go 1.9 版本之前定义内建类型的代码是这样写的:

type byte uint8
type rune int32

而在 Go 1.9 版本之后变为:

type byte = uint8
type rune = int32

这个修改就是配合类型别名而进行的修改

区分类型别名与类型定义

定义类型别名的写法:

type TypeAlias = Type

类型别名规定:TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型,就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人

类型别名与类型定义表面上看只有一个等号的差异,那么它们之间实际的区别有哪些呢?下面通过一段代码来理解

package main
import (
    "fmt"
)
// 将NewInt定义为int类型
type NewInt int
// 将int取一个别名叫IntAlias
type IntAlias = int
func main() {
    // 将a声明为NewInt类型
    var a NewInt
    // 查看a的类型名
    fmt.Printf("a type: %T\n", a)
    // 将a2声明为IntAlias类型
    var a2 IntAlias
    // 查看a2的类型名
    fmt.Printf("a2 type: %T\n", a2)
}

代码运行结果:

a type: main.NewInt
a2 type: int

代码说明:

  • 第6行,将 NewInt 定义为 int 类型,这是常见的定义类型的方法,通过 type 关键字的定义,NewInt 会形成一种新的类型,NewInt 本身依然具备 int 类型的特性
  • 第8行,将 IntAlias 设置为 int 的一个别名,使用 IntAlias 与 int 等效
  • 第13行,使用%T格式化参数,打印变量 a 本身的类型
  • 第17行,打印 a2 变量的类型

非本地类型不能定义方法

能够随意地为各种类型起名字,是否意味着可以在自己包里为这些类型任意添加方法呢?参见下面的代码演示:

package main
import (
    "time"
)
// 定义time.Duration的别名为MyDuration
type MyDuration = time.Duration
// 为MyDuration添加一个函数
func (m MyDuration) EasySet(a string) {
}
func main() {
}

代码说明:

  • 第6行,为 time.Duration 设定一个类型别名叫 MyDuration
  • 第8行,为这个别名添加一个方法

编译上面的代码报错,信息如下

cannot define new methods on non-local type time.Duration

编译器提示:不能在一个非本地的类型 time.Duration 上定义新方法,非本地类型指的就是 time.Duration 不是在 main 包中定义的,而是在 time 包中定义的,与 main 包不在同一个包中,因此不能为不在一个包中的类型定义方法

解决这个问题有下面两种方法:

  • 第6行,修改 type MyDuration time.Duration,也就是将 MyDuration 从别名改为类型
  • 将 MyDuration 的别名定义放在 time 包中

在结构体成员嵌入时使用别名

当类型别名作为结构体嵌入的成员时会发生什么情况呢?请参考下面的代码

package main
import (
    "fmt"
    "reflect"
)
// 定义商标结构
type Brand struct {
}
// 为商标结构添加Show()方法
func (t Brand) Show() {
}
// 为Brand定义一个别名FakeBrand
type FakeBrand = Brand
// 定义车辆结构
type Vehicle struct {
    // 嵌入两个结构
    FakeBrand
    Brand
}
func main() {
    // 声明变量a为车辆类型
    var a Vehicle
   
    // 指定调用FakeBrand的Show
    a.FakeBrand.Show()
    // 取a的类型反射对象
    ta := reflect.TypeOf(a)
    // 遍历a的所有成员
    for i := 0; i < ta.NumField(); i++ {
        // a的成员信息
        f := ta.Field(i)
        // 打印成员的字段名和类型
        fmt.Printf("FieldName: %v, FieldType: %v\n", f.Name, f.Type.
            Name())
    }
}

代码输出如下:

FieldName: FakeBrand, FieldType: Brand
FieldName: Brand, FieldType: Brand

代码说明如下:

  • 第7行,定义商标结构
  • 第10行,为商标结构添加 Show() 方法
  • 第13行,为 Brand 定义一个别名 FakeBrand
  • 第14-19行,定义车辆结构 Vehicle,嵌入 FakeBrand 和 Brand 结构
  • 第22行,将 Vechicle 实例化为 a
  • 第25行,显式调用 Vehicle 中 FakeBrand 的 Show() 方法
  • 第27行,使用反射取变量 a 的反射类型对象,以查看其成员类型
  • 29-31行,遍历 a 的结构体成员
  • 第33行,打印 Vehicle 类型所有成员的信息

这个例子中,FakeBrand 是 Brand 的一个别名,在 Vehicle 中嵌入 FakeBrand 和 Brand 并不意味着嵌入两个 Brand,FakeBrand 的类型会以名字的方式保留在 Vehicle 的成员中

如果25行尝试修改为

a.Show()

编译器报错

ambiguous selector a.Show

在调用 Show() 方法时,因为两个类型都有 Show() 方法,会发生歧义,证明 FakeBrand 的本质确实是 Brand 类型

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr_Csyn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值