Go语言不是一门面向对象的语言,没有对象和继承,也没有面向对象的多态、重写相关特性。数据结构
Go所拥有的是数据结构,它能够关联方法。Go也支持简单但高效的组合(Composition),请搜索面向对象和组合。函数
虽然Go不支持面向对象,但Go经过定义数据结构的方式,也能实现与Class类似的功能。指针
一个简单的例子,定义一个Animal数据结构:code
type Animal struct { name string speak string }
这就像是定义了一个class,有本身的属性。对象
在稍后,将会介绍如何向这个数据结构中添加方法,就像为类定义方法同样。不过如今,先简单介绍下数据结构。blog
除了int、string等内置的数据类型,咱们能够定义structure来自定义数据类型。继承
建立数据结构最简单的方式:内存
bm_horse := Animal{ name:"baima", speak:"neigh", }
注意,上面最后一个逗号","不能省略,Go会报错,这个逗号有助于咱们去扩展这个结构,因此习惯后,这是一个很好的特性。string
上面bm_horse := Animal{}
中,Animal就像是一个类,这个声明和赋值的操做就像建立了一个Animal类的实例,也就是对象,其中对象名为bm_horse
,它是这个实例的惟一标识符。这个对象具备属性name和speak,它们是每一个对象所拥有的key,且它们都有本身的值。从面向对象的角度上考虑,这其实很容易理解。产品
还能够根据Animal数据结构再建立另一个实例:
hm_horse := Animal{ name:"heima", speak:"neigh", }
bm_horse
和hm_horse
都是Animal的实例,根据Animal数据结构建立而来,这两个实例都拥有本身的数据结构。以下图:
从另外一种角度上看,bm_horse
这个名称实际上是这个数据结构的一个引用。再进一步考虑,其实面向对象的类和对象也是一种数据结构,每个对象的名称(即bm_horse
)都是对这种数据结构的引用。关于这一点,在后面介绍指针的时候将很是有助于理解。
如下是两外两种有效的数据结构定义方式:
// 定义空数据结构 bm_horse := Animal{} // 或者,先定义一部分,再赋值 bm_horse := Animal {name:"baima"} bm_horse.speak = "neigh"
此外,还能够省略数据结构中的key部分(也就是属性的名称)直接为数据结构中的属性赋值,只不过这时赋的值必须和key的顺序对应。
bm_horse := Animal{"baima","neigh"}
在数据结构的属性数量较少的时候,这种赋值方式也是不错的,但属性数量多了,不建议如此赋值,由于很容易混乱。
要访问一个数据结构中的属性,以下:
package main import ("fmt") func main(){ type Animal struct { name string speak string } bm_horse := Animal{"baima","neigh"} fmt.Println("name:",bm_horse.name) fmt.Println("speak:",bm_horse.speak) }
前面说过,Animal是一个数据结构的模板(就像类同样),不是实例,bm_horse
才是具体的实例,有本身的数据结构,因此,要访问本身数据结构中的数据,能够经过本身的名称来访问本身的属性:
bm_horse.name bm_horse.speak
bm_horse := Animal{}
表示返回一个数据结构给bm_horse,bm_horse指向这个数据结构,也能够说bm_horse是这个数据结构的引用。
除此,还有另外一种赋值方式,比较下两种赋值方式:
bm_horse := Animal{"baima","neigh"} ref_bm_horse := &Animal{"baima","neigh"}
这两种赋值方式,有何不一样?
:=
操做符都声明左边的变量,并赋值变量。赋值的内容基本神似:
bm_horse
,bm_horse
今后变成Animal的实例;&
在数据结构前面,它表示返回这个数据结构的引用,也就是这个数据结构的地址,因此ref_bm_horse
也指向这个数据结构。那bm_horse
和ref_bm_horse
都指向这个数据结构,有什么区别?
实际上,赋值给bm_horse
的是Animal实例的地址,赋值给ref_bm_horse
是一个中间的指针,这个指针里保存了Animal实例的地址。它们的关系至关于:
bm_horse -> Animal{} ref_bm_horse -> Pointer -> Animal{}
其中Pointer在内存中占用一个长度为一个机器字长的单独数据块,64位机器上一个机器字长是8字节,因此赋值给ref_bm_horse
的这个8字节长度的指针地址,这个指针地址再指向Animal{}
,而bm_horse
则是直接指向Animal{}
。
若是还不明白,我打算用perl语言的语法来解释它们的区别,由于C和Go的指针太过"晦涩"。
在Perl中,一个hash结构使用%
符号来表示,例如:
%Animal = ( name => "baima", speak => "neigh", );
这里的"Animal"表示的是这个hash结构的名称,而后经过%+NAME
的方式来引用这个hash数据结构。其实hash结构的名称"Animal"就是这个hash结构的一个引用,表示指向这个hash结构,只不过这个Animal
是建立hash结构是就指定好的已命名的引用。
perl中还支持显式地建立一个引用。例如:
$ref_myhash = \%Animal;
%Animal
表示的是hash数据结构,加上\
表示这个数据结构的一个引用,这个引用指向这个hash数据结构。perl中的引用是一个变量,因此使用$ref_myhash
表示。
也就是说,hash结构的名称Animal
和$ref_myhash
是彻底等价的,都是hash结构的引用,也就是指向这个数据结构,也就是指针。因此,%Animal
能表示取hash结构的属性,%$ref_myhash
也能表示取hash结构的属性,这种从引用取回hash数据结构的方式称为"解除引用"。
另外,$ref_myhash
是一个变量类型,而%Animal
是一个hash类型。
引用变量能够赋值给另外一个引用变量,这样两个引用都将指向同一个数据结构:
$ref_myhash1 = $ref_myhash;
如今,$ref_myhash
、$ref_myhash1
和Animal
都指向同一个数据结构。
总结下上面perl相关的代码:
%Animal = ( name => "baima", speak => "neigh", ); $ref_myhash = \%Animal; $ref_myhash1 = $ref_myhash;
%Animal
是hash结构,Animal
、$ref_myhash
、$ref_myhash1
都是这个hash结构的引用。
回到Go语言的数据结构:
bm_horse := Animal{} hm_horse := &Animal{}
这里的Animal{}
是一个数据结构,至关于perl中的hash数据结构:
( name => "baima", speak => "neigh", )
bm_horse是数据结构的直接赋值对象,它直接表示数据结构,因此它等价于前面perl中的%Animal
。而hm_horse
是Animal{}
数据结构的引用,它等价于perl中的Animal
、$ref_myhash
、$ref_myhash1
。
之因此Go中的指针很差理解,就是由于数据结构bm_horse和引用hm_horse都没有任何额外的标注,看上去都像是一种变量。但其实它们是两种不一样的数据类型:一种是数据结构,一种是引用。
星号有两种用法:
x *int
表示变量x是一个引用,这个引用指向的目标数据是int类型。更通用的形式是x *TYPE
*x
表示x是一个引用,*x
表示解除这个引用,取回x所指向的数据结构,也就是说这是 一个数据结构,只不过这个数据结构多是内置数据类型,也多是自定义的数据结构x *int
的x是一个指向int类型的引用,而&y
返回的也是一个引用,因此&y
的y若是是int类型的数据,&y
能够赋值给x *int
的x。
注意,x的数据类型是*int
,不是int,虽然x所指向的是数据类型是int。就像前面perl中的引用只是一个变量,而其指向的倒是一个hash数据结构同样。
*x
表明的是数据结构自身,因此若是为其赋值(如*x = 2
),则新赋的值将直接保存到x指向的数据中。
例如:
package main import ("fmt") func main(){ var a *int c := 2 a = &c d := *a fmt.Println(*a) // 输出2 fmt.Println(d) // 输出2 }
var a *int
定义了一个指向int类型的数据结构的引用。a = &c
中,由于&c
返回的是一个引用,指向的是数据结构c,c是int类型的数据结构,将其赋值给a,因此a也指向c这个数据结构,也就是说*a
的值将等于2。因此d := *a
赋值后,d自身是一个int类型的数据结构,其值为2。
package main import "fmt" func main() { var i int = 10 println("i addr: ", &i) // 数据对象10的地址:0xc042064058 var ptr *int = &i fmt.Printf("ptr=%v\n", ptr) // 0xc042064058 fmt.Printf("ptr addr: %v\n", &ptr) // 指针对象ptr的地址:0xc042084018 fmt.Printf("ptr地址: %v\n", *&ptr) // 指针对象ptr的值0xc042064058 fmt.Printf("ptr->value: %v", *ptr) // 10 }
Go函数给参数传递值的时候是以复制的方式进行的。
由于复制传值的方式,若是函数的参数是一个数据结构,将直接复制整个数据结构的副本传递给函数,这有两个问题:
例如,第一个问题:
package main import ("fmt") type Animal struct { name string weight int } func main(){ bm_horse := Animal{ name: "baima", weight: 60, } add(bm_horse) fmt.Println(bm_horse.weight) } func add(a Animal){ a.weight += 10 }
上面的输出结果仍然为60。add函数用于修改Animal的实例数据结构中的weight属性。当执行add(bm_horse)
的时候,bm_horse
传递给add()函数,但并非直接传递给add()函数,而是复制一份bm_horse
的副本赋值给add函数的参数a,因此add()中修改的a.weight
的属性是bm_horse
的副本,而不是直接修改的bm_horse,因此上面的输出结果仍然为60。
为了修改bm_horse所在的数据结构的值,须要使用引用(指针)的方式传值。
只需修改两个地方便可:
package main import ("fmt") type Animal struct { name string weight int } func main(){ bm_horse := &Animal{ name: "baima", weight: 60, } add(bm_horse) fmt.Println(bm_horse.weight) } func add(a *Animal){ a.weight += 10 }
为了修改传递给函数参数的数据结构,这个参数必须是直接指向这个数据结构的。因此使用add(a *Animal)
,既然a是一个Animal数据结构的一个实例的引用,因此调用add()的时候,传递给add()中的参数必须是一个Animal数据结构的引用,因此bm_horse
的定义语句中使用&
符号。
当调用到add(bm_horse)
的时候,由于bm_horse
是一个引用,因此赋值给函数参数a时,复制的是这个数据结构的引用,使得add能直接修改其外部的数据结构属性。
大多数时候,传递给函数的数据结构都是它们的引用,但极少数时候也有需求直接传递数据结构。
能够为数据结构定义属于本身的函数。
package main import ("fmt") type Animal struct { name string weight int } func (a *Animal) add() { a.weight += 10 } func main() { bm_horse := &Animal{"baima",70} bm_horse.add() fmt.Println(bm_horse.weight) // 输出80 }
上面的add()函数定义方式func (a *Animal) add(){}
,它所表示的就是定义于数据结构Animal上的函数,就像类的实例方法同样,只要是属于这个数据结构的实例,都能直接调用这个函数,正如bm_horse.add()
同样。
面向对象中有构造器(也称为构造方法),能够根据类构造出类的实例:对象。
Go虽然不支持面向对象,没有构造器的概念,但也具备构造器的功能,毕竟构造器只是一个方法而已。只要一个函数可以根据数据结构返回这个数据结构的一个实例对象,就能够称之为"构造器"。
例如,如下是Animal数据结构的一个构造函数:
func newAnimal(n string,w int) *Animal { return &Animal{ name: n, weight: w, } }
如下返回的是非引用类型的数据结构:
func newAnimal(n string,w int) Animal { return Animal{ name: n, weigth: w, } }
通常上面的方法类型称为工厂方法,就像工厂同样根据模板不断生成产品。但对于建立数据结构的实例来讲,通常仍是会采用内置的new()方式。
尽管Go没有构造器,但Go还有一个内置的new()函数用于为一个数据结构分配内存。其中new(x)
等价于&x{}
,如下两语句等价:
bm_horse := new(Animal) bm_horse := &Animal{}
使用哪一种方式取决于本身。但若是要进行初始化赋值,通常采用第二种方法,可读性更强:
# 第一种方式 bm_horse := new(Animal) bm_horse.name = "baima" bm_horse.weight = 60 # 第二种方式 bm_horse := &Animal{ name: "baima", weight: 60, }
在前面出现的数据结构中的字段数据类型都是简简单单的内置类型:string、int。但数据结构中的字段能够更复杂,例如能够是map、array等,还能够是自定义的数据类型(数据结构)。
例如,将一个指向同类型数据结构的字段添加到数据结构中:
type Animal struct { name string weight int father *Animal }
其中在此处的*Animal
所表示的数据结构实例极可能是其它的Animal实例对象。
上面定义了father,还能够定义son,sister等等。
例如:
bm_horse := &Animal{ name: "baima", weight: 60, father: &Animal{ name: "hongma", weight: 80, father: nil, }, }
Go语言支持Composition(组合),它表示的是在一个数据结构中嵌套另外一个数据结构的行为。
package main import ( "fmt" ) type Animal struct { name string weight int } type Horse struct { *Animal // 注意此行 speak string } func (a *Animal) hello() { fmt.Println(a.name) fmt.Println(a.weight) //fmt.Println(a.speak) } func main() { bm_horse := &Horse{ Animal: &Animal{ // 注意此行 name: "baima", weight: 60, }, speak: "neigh", } bm_horse.hello() }
上面的Horse数据结构中包含了一行*Animal
,表示Animal的数据结构插入到Horse的结构中,这就像是一种面向对象的类继承。注意,没有给该字段显式命名,但能够隐式地访问Horse组合结构中的字段和函数。
另外,在构建Horse实例的时候,必须显式为其指定字段名(尽管数据结构中并无指定其名称),且字段的名称必须和数据结构的名称彻底相同。
而后调用属于Animal数据结构的hello方法,它只能访问Animal中的属性,因此没法访问speak属性。
不少人认为这种代码共享的方式比面向对象的继承更加健壮。
例如,将上面属于Animal数据结构的hello函数重载为属于Horse数据结构的hello函数:
package main import ( "fmt" ) type Animal struct { name string weight int } type Horse struct { *Animal // 注意此行 speak string } func (h *Horse) hello() { fmt.Println(h.name) fmt.Println(h.weight) fmt.Println(h.speak) } func main() { bm_horse := &Horse{ Animal: &Animal{ // 注意此行 name: "baima", weight: 60, }, speak: "neigh", } bm_horse.hello() }