值和引用
• 值语义和引用语义
值语义和引用语义的差别在于赋值:
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) 函数名(参数列表) (返回值列表) { // 函数体 }
▪ 示例如下
有疑问加站长微信联系(非本文作者)