Go类型


[toc]

Go类型

用户自定义类型

声明一个类型的时,这个声明就给编译器提供一个框架,告知必要的内存大小和表示信息。声明后与内置类型的运作方式类型。

需要注意得是,不同类型即使相互兼容,但是也不能相互赋值

// 定义一个新的类型
type user struct {
    name       string
    email      string
    ext        int
    privileged bool
}

// 类型可以嵌套
type admin struct {
    persion user
    level   string
}

初始化与赋值

// 默认值为新类型里属性对应的默认值
var bill user
fmt.Println(bill)

//类型创建并初始化
newUser := user{
	name:       "smith",
	email:      "smith@example.com",
	ext:        0,
	privileged: true,
}
fmt.Println(newUser)

//类型创建并初始化的另外一种方式,需要注意值得顺序
newUser2 := user{"smith", "smith@example.com", 0, false}
fmt.Println(newUser2)

方法

方法能给用户定义得类型添加新的行为。

在定义方法的时候,关键字func和函数名之间的参数被称作为接受者,将函数与接受者的类型绑定在一起。如果一个函数有接受者,那么这个函数就被称作为方法。

Go中有两种接受者: 值接受者和指针接收者。

如果使用值接收者,那么调用时会使用这个值的一个副本来执行

type user struct {
	name       string
	email      string
	ext        int
	privileged bool
}
// 为user添加修改名字的方法,值接受者
func (u user) changeName(name string) {
	u.name = name
}

// 指针接受者
func (u *user) changeEmail(email string) {
	u.email = email
}

值得注意的是,值接受者使用值得副本来调用方法,而指针接受者使用实际的值调用方法,也可以使用一个值来调用使用指针接受者声明的方法。

newUser.changeName("smith1")
fmt.Println(newUser)

    //(&newUser).changeEmail("smith1@example.com"),两者是等价的
newUser.changeEmail("smith1@example.com")
fmt.Println(newUser)
(&newUser).changeEmail("smith2@example.com")
fmt.Println(newUser)

newUser4 := &user{"smith", "smith@example.com", 0, false}
newUser4.changeName("123")
fmt.Println(newUser4)

什么时候使用指针接受者,什么时候使用值接受者

首先需要明白如果给某个类型增加或者删除某个值,是要修改当前值,还是更新当前值?如果是要创建一个新值,该类型的方法就使用值接受者,如果要修改当前值,就使用指针接受者。

原则是不要关注某个方法如何处理值,而是关注这个值的本质是什么

如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。

使用指针作为方法的接收者的理由:

  • 方法能够修改接收者指向的值。
  • 避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。

何时使用值类型

1.如果接受者是一个 map,func 或者 chan,使用值类型(因为它们本身就是引用类型)。
2.如果接受者是一个 slice,并且方法不执行 reslice 操作,也不重新分配内存给 slice,使用值类型。
3.如果接受者是一个小的数组或者原生的值类型结构体类型(比如 time.Time 类型),而且没有可修改的字段和指针,又或者接受者是一个简单地基本类型像是 int 和 string,使用值类型就好了。

一个值类型的接受者可以减少一定数量的垃圾生成,如果一个值被传入一个值类型接受者的方法,一个栈上的拷贝会替代在堆上分配内存(但不是保证一定成功),所以在没搞明白代码想干什么之前,别因为这个原因而选择值类型接受者。

何时使用指针类型

1.如果方法需要修改接受者,接受者必须是指针类型。
2.如果接受者是一个包含了 sync.Mutex 或者类似同步字段的结构体,接受者必须是指针,这样可以避免拷贝。
3.如果接受者是一个大的结构体或者数组,那么指针类型接受者更有效率。(多大算大呢?假设把接受者的所有元素作为参数传给方法,如果你觉得参数有点多,那么它就是大)。
4.从此方法中并发的调用函数和方法时,接受者可以被修改吗?一个值类型的接受者当方法调用时会创建一份拷贝,所以外部的修改不能作用到这个接受者上。如果修改必须被原始的接受者可见,那么接受者必须是指针类型。
5.如果接受者是一个结构体,数组或者 slice,它们中任意一个元素是指针类型而且可能被修改,建议使用指针类型接受者,这样会增加程序的可读性

当你看完这个还是有疑虑,还是不知道该使用哪种接受者,那么记住使用指针接受者

是使用值接受者还是指针接受者,不应该由该方法是否修改了接收到的值来决定。这个决策应该基于该类型的本质。
只有一个例外,需要让类型值复合某个接口的时候,即便类型的本质是非原始的,也可以选择使用值接受者声明方法。这样做完全复合调用方法的机制。
ref:https://www.136.la/tech/show-933792.html

类型的本质

内置类型

  • 数值类型
  • 字符串类型
  • 布尔类型

内置类型的本质是原始类型。
对于内置类型,对这些值进行增加或者删除的时候,会创建新值,把这些类型的值传递给方法或者函数的时候,应该传递一个对应值的副本。

引用类型

  • 切片
  • 映射
  • 通道
  • 接口
  • 函数类型

引用类型创建的变量成为标头值。每个标头值包含一个指向底层数据结构的指针。因此通过复制一个引用类型的值的副本,本质上就是在共享底层数据结构。
每个引用类型还包含足以独特的字段,用于管理底层数据结构。标头值是为了复制而设计的,所以永远不要共享一个引用类型的值

结构类型

结构类型用来描述一组数据值,这组值得本质可以是原始的,也可以是非原始的。

嵌入类型

嵌入类型是将已有的类型直接声明在新的结构类型里。被嵌入的类型成为新的外部类型的内部类型。

type user struct {
	name  string
	email string
}

type admin struct {
	user // 内嵌
	level string
}

user1 := user{"smith", "smith@email.com"}
ad := admin{
	user:  user1,
	level: "super",
}
fmt.Println(ad)

内部类型的提升:对于外部类型来说,内部类型总是存在的。这就意味着,虽然没有指定内部类型对应的字段名,还是可以使用内部类型的类型名来范文内部类型的值。
即下面两者是等价的。

fmt.Println(ad.name) //smith
fmt.Println(ad.user.name) //smith

同样的,对于接口的值而言,因为内部类型的提升,内部类型实现的接口会自动地提升到外部类型,因此外部类型也同样实现了这个接口

// 定义接口
type notifier interface {
	notify()
}

// 内部类型实现接口
func (u *user) notify() {
	fmt.Printf("user interface name:%s email:%s\n", u.name, u.email)
}

// 外部类型使用内部类型地接口
ad.notify()     //user interface name:smith email:smith@email.com

但是如果外部类型同样实现同样的接口或者使用同样的变量,内部类型就不会得到提升。

//重新定义admin
type admin struct {
	user
	email string // 与user有同样的字段
}

// admin实现接口
func (u *admin) notify() {
	fmt.Printf("Admin interface name:%s email:%s\n", u.name, u.email)
}

user1 := user{"smith", "smith@email.com"}
ad := admin{
	user:  user1,
	email: "super@test.com",
}

// 同样的字段或者变量,内部类型不会得到提升
fmt.Println(ad.email)      //super@test.com
fmt.Println(ad.user.email) //smith@email.com
ad.notify() // Admin interface name:smith email:super@test.com

需要注意一点是,即便内部类型是未公开的,内部类型声明的字段是公开的。那么这些公共字段也可以通过外部类型去访问


文章作者: 彭峰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 彭峰 !
  目录