go 类型系统

值语义和引用语义
值语义和引用语义的差别在于赋值:



b = a
b.Modify()
如果b的修改不会影响a的值,那么属于值类型,否则属于引用类型。



• 值类型和引用类型
√ 引用类型一个特点:引用不绑定特定对象(c++中引用是要绑定特定对象),例如有两个同类型引用a和b,它们可以引用各自的对象A和B;但如果a和b的引用都指向A,那么通过b修改对象内容可以反应到a引用之中。



   √ golang从本质上说,一切皆是值类型,并没有内建一个类似java或c#等语言中的reference类型。 

√ golang可以使用指针,可以在行为上达到类似java或c#等语言中reference的效果。

√ golang中从行为表现上看,数组属于值类型,数组切片、字典、通道和接口属于引用类型。


▶ 引用类型实现
√ golang中通过指针能实现引用类型效果。



   √ golang中通过指针或对象访问成员都是使用点操作符,因此指针看起来和对象一样,即类似引用。


package main



import (
“fmt”
)



type Rect struct {
width, height float64
}



func main() {
var a Rect = Rect{100, 200}
var b *Rect = &a



b.width = 300
b.height = 400

fmt.Println(a.width, a.height) // 300 400 } ▶ 数组
√ golang中从行为表现上看,数组属于值类型。


package main



import (
“fmt”
)



func main() {
var a [3]int = [3]int{1, 2, 3}
var b [3]int = a



b[1]++

fmt.Println(a) // [1 2 3]
fmt.Println(b) // [1 3 3] } ▶ 数组切片
√ 我们大致可以将数组切片[]T抽象表示为:


type slice struct {
first *T
len int
cap int
}
√ golang数组切片本质上是一个含有存储指针的结构体,因此本质上说它是值类型,但从表现行为上看是一个引用类型。



package main



import (
“fmt”
)



func main() {
var a []int = []int{1, 2, 3}
var b []int = a



b[1]++

fmt.Println(a) // [1 3 3]
fmt.Println(b) // [1 3 3] } ▶ 字典类型
√ 我们大致可以将字典map[K]V抽象为:


type Map_K_V struct {
// …
}



type map[K]V struct {
impl *Map_K_V
}
√ golang字典本质上是一个含有字典指针的结构体,因此本质上说它是值类型,但从表现行为上看是一个引用类型。



package main



import (
“fmt”
)



func main() {
var a, b map[string]string
a = make(map[string]string)
a[“1”] = “haha”
a[“2”] = “hehe”



b = a
b["2"] = "shit"

fmt.Println(a) // map[1:haha 2:shit]
fmt.Println(b) // map[1:haha 2:shit] } ▶ 通道和接口
√ 与数组切片和字典类似,通道和接口本质上说是值类型,但从行为表现上看属于引用类型。


结构体(struct)
• 结构体定义
golang中的结构体(struct)和其他语言中的类(class)有同等地位。不同的是,golang放弃了包括继承在内的大量面向对象特性,只保留了组合这个最基础特性。



▶ 语法如下
type 结构体名 struct {
// 成员定义
}
• 构造和初始化
▶ new方式
▪ new函数原型
http://godoc.golangtc.com/pkg/builtin/#new



func new(Type) *Type {

}
▪ 语法如下
// 创建对象实例
p := new(Type)



// 初始化代码
p.xxxx = xxxx
p.xxxx = xxxx

▪ 示例如下
package main



import (
“fmt”
)



type Rect struct {
width, height float64
}



func main() {
rect := new(Rect)



rect.width = 100
rect.height = 200

fmt.Println(rect.width, rect.height) } ▶ { }方式 ▪ 语法如下 // 对象类型 obj0 := Type{} obj1 := Type{v1, v2, ..., vn} obj2 := Type{k1: v1, k2: v2, ..., kn: vn}


// 指针类型
ptr0 := &Type{}
ptr1 := &Type{v1, v2, …, vn}
ptr2 := &Type{k1: v1, k2: v2, …, kn: vn}
√ 在golang中,未进行显式初始化的变量将会被初始化为该类型的“零”值(bool:false, int:0, string:”“)。



   √ 在golang中,没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成。


▪ 示例如下
package main



import (
“fmt”
)



type Rect struct {
width, height float64
}



func main() {
// 对象
rect0 := Rect{}
fmt.Println(rect0.width, rect0.height) // 0, 0



rect1 := Rect{100, 200}
fmt.Println(rect1.width, rect1.height) // 100, 200

//rect2 := Rect{100} // error : too few values in struct initializer

rect3 := Rect{width: 100, height: 200}
fmt.Println(rect3.width, rect3.height) // 100, 200

rect4 := Rect{height: 200}
fmt.Println(rect4.width, rect4.height) // 0, 200

// 指针
rect5 := &Rect{}
fmt.Println(rect5.width, rect5.height) // 0, 0

rect6 := &Rect{100, 200}
fmt.Println(rect6.width, rect6.height) // 100, 200

//rect7 := &Rect{100} // error : too few values in struct initializer

rect8 := &Rect{width: 100, height: 200}
fmt.Println(rect8.width, rect8.height) // 100, 200

rect9 := &Rect{height: 200}
fmt.Println(rect9.width, rect9.height) // 0, 200 } • 指针的作用
√ 在golang中,结构体属于值类型,这就意味着跨函数传递某个对象将只能得到其副本,倘若要在另一个函数中修改对象的内容,那么结果只是修改了副本内容,原对象的内容将没有改变。


package main



import (
“fmt”
)



type Rect struct {
width, height float64
}



func ModifyRect(r Rect) {
r.width = 1000
r.height = 1000
return
}



func main() {
rect := Rect{100, 200}
ModifyRect(rect)
fmt.Println(rect.width, rect.height) // 100, 200
}
√ 只有通过传递指针,才能跨函数修改对象内容。而由于访问对象成员无论是实例还是指针,使用的都是点操作符,这就带来了类型的隐蔽性,因此在创建对象和传递实例时,尽量使用对象指针。



package main



import (
“fmt”
)



type Rect struct {
width, height float64
}



func ModifyRect(r *Rect) {
r.width = 1000
r.height = 1000
return
}



func main() {
rect := &Rect{100, 200}
ModifyRect(rect)
fmt.Println(rect.width, rect.height) // 1000, 1000
}
• 添加成员方法
√ 在golang中,可以给任意类型(包括内置类型,但不包括指针类型)添加相应的成员方法。



   √ 在golang中,没有隐藏的this或self指针,即方法施加的目标对象将被显式传递,没有被隐藏起来。

√ 在golang中,成员对象定义时候指定作用的目标对象是对象实例还是对象指针。

√ 在golang中,无论是对象实例还是对象指针都可以调用成员函数,不管成员函数作用对象类型,但是不影响最后的结果,即结果只由作用的目标类型决定(是对象实例还是对象指针)。


▶ 对象实例传递
√ 对象实例传递,即非指针,按值传递。



   √ 这种方式将无法在方法中修改对象实例的内容,因为按值传递得到是对象副本,这与函数参数性质一样。


▪ 语法如下
func (obj Type) 函数名 (参数列表) (返回值列表) {
// 函数体
}
▪ 示例如下
package main



import (
“fmt”
)



type Rect struct {
width, height float64
}



func (r Rect) ModifyRect() {
r.width = 1000
r.height = 1000
fmt.Println(r.width, r.height) // 1000, 1000
}



func main() {
rect1 := Rect{100, 200}
rect1.ModifyRect()
fmt.Println(rect1.width, rect1.height) // 100, 200



rect2 := &Rect{100, 200}
rect2.ModifyRect() //指针依然可以调用,但不改变结果
fmt.Println(rect2.width, rect2.height) // 100, 200 } ▶ 对象指针传递 ▪ 语法如下
√ 对象指针传递,可以修改作用对象的内容。


func (ptr *Type) 函数名 (参数列表) (返回值列表) {
// 函数体
}
▪ 示例如下
package main



import (
“fmt”
“reflect”
)



type Rect struct {
width, height float64
}



func (r *Rect) ModifyRect() {
r.width = 1000
r.height = 1000
fmt.Println(r.width, r.height) // 1000, 1000
}



func main() {
rect1 := Rect{100, 200}
fmt.Println(reflect.ValueOf(rect1).Type()) // main.Rect
rect1.ModifyRect() // 使用对象实例调用依然有效
fmt.Println(rect1.width, rect1.height) // 1000, 1000



rect2 := &Rect{100, 200}
fmt.Println(reflect.ValueOf(rect2).Type()) // *main.Rect
rect2.ModifyRect() // 使用对象指针调用依然有效
fmt.Println(rect2.width, rect2.height) // 1000, 1000 } ▶ 为已有类型添加成员方法
√ golang有个很酷的特性:可以通过给已有类型Type起一个别名Alias,然后为Alias增加一些新的方法使其成为一个新的类型,同时Alias将完全拥有Type的所有方法。


▪ 语法如下
type Alias Type



func (a Alias) 函数名(参数列表) (返回值列表) {
// 函数体
}



func (a *Alias) 函数名(参数列表) (返回值列表) {
// 函数体
}

pre-declared type (预声明类型)



golang 默认的有几个预声明类型:boole,num,string type. 这些预声明类型被用来构造其他的类型。



type literal(字面量类型)



由其他预声明类型组合而成(没有使用type 关键字定义),array,struct,pointer,slice,map,channel,function,interface
[2]int ,[ ] int , chan int , map[string] string ,* int
注意:type literal 中的struct,interface 特指匿名的类型,没有使用type包装,



命名类型和非命名类型
named type : 一个类型通过一个标识符标示就是命类型名字(type name)



unamed type: 一个类型是由先前已声明类型组合构成被称为类型字面量(type literal)



二者区别:
named type 可以有自己的methods, mehtods 是一种特殊形式的函数,该函数的第一个参数是该type的变量或指向该类型变量的指针(receiver).



unamed type:不能定义自己的method



type



包括



特点



named type



pre-declared



type declared new type



可以定义自己的method



unamed type



type literal



不可以定义自己的method



一个特别重要的事情需要记住:pre-declared types 也是命名类型。所以:int 是named type , 但是 *int ,[]int 不是。



特别注意:我们不能直接为int 定义method



func (a int) methodname() {



}



不是因为int 是unnamed type ,而是为一个type定义方法必须在该类型的所在的package , int 的scope (作用域是)universe (全局的),int 是语言层面预声明的,其属于任何package,也就没有办法为其增加method.



package main



import (
“fmt”
)



func (a int) Print() {
fmt.Println(a)
}



func main() {
m := 12
a.Print()
}
[root@tata.domain /project/go/src/test]#go build name.go


command-line-arguments


./name.go:7: cannot define new methods on non-local type int
./name.go:13: undefined: a



Underlying Type 底层类型
所有type(类型)都有个underlying type(底层类型)。Pre-declared types 和type literals 的底层类型是它们自身;通过type newtype oldtype 的底层类型,是逐层递归向下查找的,直到查到的oldtype 是Pre-declared types 和type literals。



type



underlying type



示例



pre-declared type



itself



string 的underlying type 还是string



type literal



itself



map[string]string的underlying type 还是map[string]string



通过type定义的新类型:



type newtype oldtype



逐层向下递归查找oldtype,直到oldtype是pre-declared type 或type literal;找到的pre-declared type 或type literal就是underlying type



type Map map[string]string



type SpecialMap Map



则:Map 和 SpecialMap 的underlying type



都是map[string]string



另一条重要规则是:



type newtype oldtype: newtype不会从oldtype或其underlying type继承任何methods .



有两个特例就是:接口和组合类型(composite type)



new type



Methods SET



type interface2 interface1



interface2 会继承interface1的methods set



type NewType struct {



InnerType



}



NewType 会继承InnerType的methods set



其他type newtype oldtype



newtype 不会从oldtype 或其underlying type中继承任何方法



这里表达的意思就是:一旦你定义一个新类型,你往往是想为其定义一个新的methods set



static type 静态类型和dynamic type 动态类型
static type就是变量定义声明是的类型,



只有interface 有动态类型
接口类型变量有dynamic type,接口的动态类型是其在运行时绑定值的类型
接口的动态类型可以在运行时发生变化,但是动态类型其必须满足可以赋值给接口类型的条件,否则会发生panic;



非interface 的类型的dynamic type 就是其static type



static type就是变量定义声明是的类型,接口类型变量还有个dynamic type,接口的动态类ixngshi在运行时其存储的值的类型,接口的动态类型可以变化,但是其必须满足可以赋值给接口类型的规则;对于非接口类型,动态类型就是静态类型。


Category golang