本文将介绍go语言中结构体的定义与使用,以及介绍方法的含义。通过结构体和方法相结合,能够完成许多面向对象编程语言中对象能完成的事情。
一、结构体与方法的定义1.结构体与工厂函数
- go语言通过type与struct关键字定义结构体,每个结构体可以拥有若干个字段,这些字段可以是具名的,也可以是匿名的,结构体的实例可以通过"."符号来访问到对应的字段。
- 工厂函数,工厂函数是一种约定俗成的函数,通常用于实例化一个结构体,通常以newName方式命名。
- 结构体与工厂函数的示例
type person struct{
name string // 结构体中每个字段名必须唯一
age int // 结构体字段名大写字母开头,可以导出给外部变量使用
int // 匿名字段:结构体中每种数据类型的匿名字段有且仅有一个
}
// newPerson 工厂函数,类似于面向对面编程语言中的构造函数
func newPerson(name string,age int)*person{
return &person{
name: name,
age: age,
int:32,
}
}
2.方法与接收者
func (p *person) setAge(age int){
p.age = age // 改变接收者的值,得用指针接收者
}
func (p person) getName()string{
return p.name
}
二、结构体内嵌
1.继承
在面向对象编程的语言中,子类可以通过继承父类,从而获取与父类相同的字段属性与方法。在Go语言中虽然没有类的概念,但可以通过内嵌结构体实现类似的效果。
// 继承
type man struct{
person // 通过匿名字段嵌入person结构体,man可以访问person的所有字段,也能调用其方法
sex string
}
func newMan(name string,age int,sex string)*man{
return &man{
person:person{
name: name,
age: age,
int:32,
},
sex: sex,
}
}
func main(){
m := newMan("lhq",22,"male")
fmt.Println(m.name,m.age,m.sex) // 输出: lhq 22 male,man可以访问其内嵌结构体的字段
m.setAge(19)
fmt.Println(m.age) // 输出: 19 ,通过内嵌结构体进行继承,man可以调用person的方法
}
上述代码定义了一个新的man结构体,其中匿名内嵌了一个person结构体,再定义一个属于自己的字段sex,这样,man结构体的实例既可以访问person结构体的字段,又能访问自己的sex字段,同时还能调用person结构体的方法。这就近似实现了继承。
2.字段冲突
结构体内嵌使得结构体更加丰富,但同时也会带来相应的冲突。考虑如下代码:
type women struct{
age int // 外部有一个age字段
sex string
person // person内部也有一个age字段
}
func newWomen(name string,age int,sex string)*women{
return &women{
age: age,
sex: sex,
person:person{
name: name,
age: age,
int:32,
},
}
}
我们定义一个women,里面内嵌了person结构体,其本身又定义了一个age字段。那么当我们使用women.age来访问或者修改age时,到底是访问的哪一个age呢?
w := newWomen("xyt",18,"female")
fmt.Println(w.name,w.age,w.person.age,w.sex) //输出:xyt 18 18 female
w.setAge(21)
fmt.Println(w.age,w.person.age) // 输出:18 21,可以看到,通过w.age访问age字段,得到的是外部字段的值,想要获取内部字段,需要指定内部结构体对象
我们可以看到,实际访问的是外部的age字段,go语言中规定,当结构体中不同层次存在相同名字的字段时,默认外部字段会覆盖内部字段。(注意:这里的覆盖并不是指内部字段的值被外部字段的值覆盖,实际上内部字段的值依然存在,只是我们需要使用"."符号进入到内部结构体再去访问内部字段)
3.方法冲突
与字段冲突一样,如果结构体中不同层次声明了同一个方法,那么通过外部使用该方法,默认使用的是外部结构体声明的方法。
func (p person) getName()string{
return p.name
}
func (w *women) getName()string{
return "i don't tell you"
}
func main(){
fmt.Println(w.getName()) // 同理,通过w.getName()调用方法,调用的是外部的方法,输出:I don't tell you
}
需要注意的是:当结构体同一层次中出现字段或方法冲突时,go语言将会报错,因为产生了模糊的字段域,我们要避免同一层次中出现字段或方法冲突。
三、结构体字段的导出与标签// 标签的使用
type user struct{
name string
age int
}
func newUser(name string,age int)*user{
return &user{
name: name,
age: age,
}
}
func main(){
// 使用json包序列化与反序列化数据
u1 := newUser("u1",18)
msg,err := json.Marshal(u1) // 该函数可以将一个结构体序列化成一个json格式的[]byte切片
if err != nil{
fmt.Printf("Marshal failed,err:%v\n",err)
return
}
fmt.Println(string(msg)) // 输出{}
}
// 标签的使用
type user struct{
Name string
Age int
}
func newUser(name string,age int)*user{
return &user{
Name: name,
Age: age,
}
}
func main(){
// 使用json包序列化与反序列化数据
u1 := newUser("u1",18)
msg,err := json.Marshal(u1) // 该函数可以将一个结构体序列化成一个json格式的[]byte切片
if err != nil{
fmt.Printf("Marshal failed,err:%v\n",err)
return
}
fmt.Println(string(msg)) // 输出{"Name":"u1","Age":18}
}
// 标签的使用
type user struct{
Name string `json:"name"`
Age int `json:"age"`
}
func newUser(name string,age int)*user{
return &user{
Name: name,
Age: age,
}
}
func main(){
// 使用json包序列化与反序列化数据
u1 := newUser("u1",18)
msg,err := json.Marshal(u1) // 该函数可以将一个结构体序列化成一个json格式的[]byte切片
if err != nil{
fmt.Printf("Marshal failed,err:%v\n",err)
return
}
fmt.Println(string(msg)) // 输出{"name":"u1","age":18}
}
总结
结构体可以定义具名字段与匿名字段,同时可以进行结构体内嵌,但要注意字段冲突与方法冲突。当外部包需要访问结构体的字段或方法时,首字母必须大写让字段或方法导出。方法是指定接收者调用的函数,接收者分为值接收者与指针接收者,尽可能使用指针接收者。