类型(一)
go 语言是一种静态语言,编译器需要在编译阶段就知道值的类型以便于分配内存等.
类型的声明
- 结构类型的声明
// 声明一个结构类型
type user struct {
name string
age uint
}
// 使用结构类型声明一个变量(一般情况下初始化一个零值用var关键字,非零值用字面量结构和短变量的声明创建)
// group声明后user里的name和age非别初始化为空字符串和0
var chujiu user
// 字面量+短变量声明初始化
chujiu := user{
name:"chujiu",
age:21,
}
// 不使用字段声明初始化,这种user里的值必须和use结构里的字段顺序一致.
chujiu := user{"chujiu",21}
// 内嵌结构,即使用其他结构类型声明字段
type admin struct {
person user
level string
}
// 字面量结构+短变量操作符初始化admin
admin := admin{
person: user{
name:"chujiu",
age:21,
},
level:"青铜",
}
- 基于一个类型的声明
type userName string // string是userName的基础类型.
// 基于userName声明其他类型,虽然string是userName的基础类型,但userName和string并不是同一个类型.在go里面一切皆类型.
type person userName
// 这样转换类型编译会出错:Cannot use 'string("初九")' (type string) as type userName in assignment,string和userName是两种类型,两者之间不存在隐式转换。
var user userName
user = string("初九")
方法
方法就是给定义的类型添加一组新的行为,也就是函数,声明事用关键字func。
type user struct {
name string
age uint
}
func (u user) changeNmae(name string) {
u.name = name
}
func (u *user) changeAge(age int) {
u.age = age
}
func main() {
u := &user{"初九", 23}
// 指向user类型值的指针,用来调用使用指针接受者声明的方法。
u.changeAge(30)
// 指向user类型值的指针,用来调用使用值接受者声明的方法。
u.changeNmae("chujiu")
fmt.Printf("name=%s,age=%d\n", u.name, u.age) // name=初九,age=30
u1 := user{"初九", 23}
// 指向user类型的值,用来调用使用指针接受者声明的方法。
u1.changeAge(31)
// 指向user类型的值,用来调用使用值接受者声明的方法。
u1.changeNmae("chujiu1")
fmt.Printf("name=%s,age=%d\n", u1.name, u1.age) // name=初九,age=31
}
go语言中,不管是使用类型的值来调用类型的方法,还是通过指向类型的值的指针来调用类型的方法都是可以的。
如果u的类型是T的话,&u肯定是一个指针类型T,对于go struct 的指针类型,(*T).field等同于T.field。*
从上面的例子当中不管是通过值还是指针调用都可以更改age的值,但name的值确没有修改,那么这是为什么呢?
在go语言当中,把类型的方法func后的括号里的叫做接收者(receiver),receiver是在函数声明的时候就已经确定了的,有2种接受的方法,分别是值接收者和指针接收者。不管调用者是值还是指针,只要是类型的方法的receiver是值类型,都无法改变调用者的内部状态,只有receiver是指针类型,才可以改变调用者的状态。
如果receiver是一个值,那么在调用方法时会创建一个副本。相反如果是指针则在调用时是一个指针。都是拷贝一个副本到方法体当中,只不过receiver是指针时,拷贝的指针和原来的指针指向同一个值,所以如果一个指针指向的值发生了变化,指向这个值的指针的值也会发生变化。
在上面的例子当中,初始化user类型给了一个变量u,通过u去调用changeAge的时候,方法里会接收到一个u的指针,而调用changeNmae的时候,方法里接收到的是一个u的拷贝的副本。
类型的本质
在决定receiver是值还是指针时不应该只看方法是做什么的,更多的应该是关注这个类型的本质是什么,如果给这个类型增加或者删除某个值,是要创建一个新值那该类型的方法就应该使用值接收,如果是修改当前值就应该用指针接收者。
go中的内置类型就是语言提供的数值类型,字符串类型,布尔类型。还有几种引用类型,切片,映射,通道,接口和函数类型,其次就是结构类型了。
- 内置类型
对于内置类型,这些是语言的原始类型,当对这些类型进行增删改的时候都会创建一个新的值。所以当把这些类型的值传递给方法或者函数时应该传递一个值的副本,避免对函数外的变量的污染,如果需要修改应该返回一个新的值,由调用者决定是否覆盖原来的值。
- 引用类型
对于引用类型来说,是一个特殊的数据结构,会有一个指向底层数据结构的指针。本质就是底层共享一个底层数据结构。所以通过值来接受本质上就是在共享底层的数据结构。
- 结构类型
结构类型包含了一组值的集合,里面的值可以是基础的原始数据类型,也可以是自定义的非原始类型。如果一个结构类型的本质是原始的那么就应该用值来接收,但是大多数结构类型的本质并不是原始的,而是非原始的,这种情况下对这个类型的值做增加或者删除的操作更应该操作值本身。而使用值接收需要考虑复制的安全性。
是使用值接收还是指针接收者,不应该由该方法是否修改了接收到的值来决定,这个决策应该属于类型的本质。但还有一个例外就是,当类型中的值符合某个接口是,即使类型的本质是非原始的,也可以选择使用值接收者的声明方法。
在声明一个method的receiver该是指针还是非指针类型时,还需要考虑这个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷贝;另一方面是如果你用指针类型作为receiver,那么需要注意,这种指针类型指向的始终是一块内存地址,即使你对其进行了拷贝,如果对拷贝的指针值修改了,那么原指针指向的值也会发生变化,所以需要考虑边界值。
网友评论