介绍
使用关键字定义类型有两种操作:
- 不带有
=
的是新类型定义 - 带有
=
的是别名定义
自定义类型
通过 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)
,以下两种情况可以类型之间可以相互转换
- 都拥有同样的底层类型的话
- 都是指向同一种类型的指针类型
需要注意的是:数字类型之间、字符串和一些 slice 类型也是允许转换的,例如:
- 浮点数转整数,小数部分丢失
- 字符串转为
[]byte
切片,会分配一份字符串数据副本
命令类型的底层类型,决定了它支持什么操作,例如底层如果是数值类型的话,那么就支持算数运算。
option 模式
这个模式在 go 语言里到处都是,很常见。主要有两种方式:
- 函数模式
- 接口模式
方式一:将 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)