Struct
Go不支持面向对象,但Go通过定义数据结构的方式,也能实现与类Class的功能
type Mankind struct {
height int
hair string
}
定义了人的身高、头发属性,但是还没有人的行为方法
man := Mankind{
height: 175,
hair: "short",
}
hair 后的逗号","不能省略,Go会报错,也有利于扩展这个数据结构
在 man := Mankind{ } 中可以把 Mankind 是一个类,man 是 它的一个实例对象,拥有 height 和 hair 属性
生成一个woman实例
woman := Mankind{
height: 165,
hair: "long",
}
// 定义空数据结构
man := Mankind{ }
// 或者,先定义一部分,再赋值
man := Mankind {height: 175}
man.hair = "short"
也可以按位置传参
man := Mankind{175,"short"}
指针
man := Mankind{175,"short"} 表示返回一个数据结构给 man ,可以说 man 是这个数据结构的引用
看一下以下两种赋值方式
man := Mankind{175,"short"}
woman := &Mankind{165,"long"}
区别:赋值给 man 的是 Mankind { } 的实例地址,赋值给 woman 的是一个中间指针,指针保存了指向 Mankind { } 的实例地址
man —> Mankind { }
woman —> Pointer —> Mankind { }
Pointer在内存中占用一个长度为一个机器字长的单独数据块,64位机器上一个机器字长是8字节
赋值给woman的这个8字节长度的指针地址,这个指针地址再指向 Mankind { },而man则是直接指向Mankind { }
var p *int // p 是一个指针
i := 24 // 定义 i
p = &i // & 使 p 取到 i 的值,即24
* 操作符表示指针指向的底层值
*p 可以读取到 24
*p = 25 //通过指针 p 设置 i 为25
总结: p 是指针,*p 是值
Go函数参数传值
Go函数给参数传递值的时候是以复制的方式进行的
复制传值的方式,如果函数的参数是一个数据结构,将直接复制整个数据结构的副本传递给函数
函数内部无法修改传递给函数的原始数据结构,它修改的只是原始数据结构拷贝后的副本
package main
import ("fmt")
type Mankind struct {
height int
hair string
}
func main(){
man := Mankind{
height: 175,
hair: "short",
}
add(man)
fmt.Println(man.height)
}
func add(a Mankind){
a.height += 10
}
上面的输出仍为175
为了修改man所在的数据结构的值,需要使用引用(指针)的方式传值
package main
import ("fmt")
type Mankind struct {
height int
hair string
}
func main(){
man := &Mankind{
height: 175,
hair: "short",
}
add(man)
fmt.Println(man.height)
}
func add(a *Mankind){
a.height += 10
}
修改传递给函数参数的数据结构,这个参数必须是直接指向这个数据结构
a是一个Animal数据结构的一个实例的引用,所以调用add()的时候,传递给add()中的参数必须是一个Animal数据结构的引用
方法:属于数据结构的函数
可以为数据结构定义属于自己的函数
package main
import ("fmt")
type Mankind struct {
height int
hair string
}
func (a *Mankind) add( ){
a.height += 10
}
func main(){
man := &Mankind{
height: 175,
hair: "short",
}
man.add( )
fmt.Println(man.height) // 185
}
上面的add()函数定义方式func (a *Mankind) add(){},它所表示的就是定义于数据结构Animal上的函数,就像类的实例方法一样,只要是属于这个数据结构的实例,都能直接调用这个函数,正如man.add()一样
构造器
只要一个函数能够根据数据结构返回这个数据结构的一个实例对象,就可以称之为"构造器"
以下是Animal数据结构的一个构造函数
func newMankind(n string,w int) *Mankind {
return &Mankind{
name: n,
weight: w,
}
}
以下返回的是非引用类型的数据结构
func newMankind(n string,w int) Mankind {
return Mankind{
name: n,
weight: w,
}
}
一般上面的方法类型称为工厂方法,就像工厂一样根据模板不断生成产品。但对于创建数据结构的实例来说,一般还是会采用内置的new()方式
new函数
Go没有构造器,但Go还有一个内置的new()函数用于为一个数据结构分配内存。其中new(x)等价于&x{},以下两语句等价
man := new(Mankind)
man := &Mankind{}
赋值方式
# 第一种方式
man := new(Mankind)
man.height = 175
man.weight = 65
# 第二种方式
woman := &Mankind{
height: 165,
weight: 45,
}
扩展数据结构字段
前面出现的数据类型比较简单string int等,我们可以构造更复杂的,例如可以是map、array等,还可以是自定义的数据类型(数据结构)
type Mankind struct {
name string
weight int
father *Mankind
}
其中在此处的*Mankind所表示的数据结构实例很可能是其它的Mankind实例对象
man := &Mankind{
height: 175,
weight: 65,
father: &Mankind{
height: 176,
weight: 70,
father: nil,
},
}
Composition(组合)
在一个数据结构中嵌套另一个数据结构的行为
package main
import (
"fmt"
)
type Mankind struct {
height int
weight int
}
type Monkey struct {
*Mankind // 注意此行
speak string
}
func (a *Mankind) hello() {
fmt.Println(a.height)
fmt.Println(a.weight)
//fmt.Println(a.speak)
}
func main() {
monkey := &Monkey{
Mankind: &Mankind{ // 注意此行
height: 175,
weight: 65,
},
speak: "how are you doing today ?",
}
monkey.hello()
}
上面的monkey数据结构中包含了一行*Mankind,表示Mankind的数据结构插入到Monkey的结构中,这就像是一种面向对象的类继承。注意,没有给该字段显式命名,但可以隐式地访问Monkey组合结构中的字段和函数
另外,在构建Monkey实例的时候,必须显式为其指定字段名(尽管数据结构中并没有指定其名称),且字段的名称必须和数据结构的名称完全相同
然后调用属于Mankind数据结构的hello方法,它只能访问Mankind中的属性,所以无法访问speak属性
这种代码共享的方式比面向对象的继承更加健壮
Go中的重载overload
将上面属于Mankind数据结构的hello函数重载为属于Monkey数据结构的hello函数
package main
import (
"fmt"
)
type Mankind struct {
height int
weight int
}
type Monkey struct {
*Mankind // 注意此行
speak string
}
func (m *Monkey) hello() {
fmt.Println(m.height)
fmt.Println(m.weight)
fmt.Println(m.speak)
}
func main() {
monkey := &Monkey{
Mankind: &Mankind{ // 注意此行
height: 175,
weight: 65,
},
speak: "how are you doing today ?",
}
monkey.hello()
}