值语义和引用语义
值语义和引用语义的差别在于赋值:
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
./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在运行时其存储的值的类型,接口的动态类型可以变化,但是其必须满足可以赋值给接口类型的规则;对于非接口类型,动态类型就是静态类型。