结构体与指针类型
指针类型字段
具名字段
举例
go
package struct_knowledge
import "fmt"
//结构体字段为指针类型
func StructWithPoint(){
type Student struct{
name *string
}
var lisa Student
fmt.Printf("赋值前,Student的实例的值%#v\n",lisa)
//错误的赋值方法
//报错:panic: runtime error: invalid memory address or nil pointer dereference
// *lisa.name = "hello"
//正确的赋值方法1
name := "lisa"
lisa.name = &name
fmt.Printf("赋值后,Student的实例的值%#v\n",lisa)
//正确赋值方法2
//先分配内存再赋值
lisa.name = new(string)
*lisa.name = "hello"
fmt.Printf("赋值后,Student的实例的值%#v\n",lisa)
}
结果
sh
赋值前,Student的实例的值struct_knowledge.Student{name:(*string)(nil)}
赋值后,Student的实例的值struct_knowledge.Student{name:(*string)(0xc000186050)}
赋值后,Student的实例的值struct_knowledge.Student{name:(*string)(0xc000186060)}
注意事项
一定要注意指针的内存分配,没有分配内存的指针不能赋值。
go
//方法1:这种是在栈上分配内存
name := "lisa"
lisa.name = &name
fmt.Printf("赋值后,Student的实例的值%#v\n",lisa)
//方法2:在堆上赋值
//先分配内存再赋值
lisa.name = new(string)
*lisa.name = "hello"
fmt.Printf("赋值后,Student的实例的值%#v\n",lisa)
匿名字段
匿名指针类型字段,实际上字段名就是去除*号
的类型名,举例
go
//报错:string redeclared
type Student struct{
*string
string
}
//实际上等价于
type Student struct{
//出现了同名字段所以报错
string *string
string string
}
举例
go
package struct_knowledge
import "fmt"
//结构体字段为指针类型
func StructWithPoint(){
type Student struct{
*string
}
var lisa Student
fmt.Printf("赋值前,Student的实例的值%#v\n",lisa)
//错误的赋值方法
//报错:panic: runtime error: invalid memory address or nil pointer dereference
// *lisa.string = "hello"
//正确的赋值方法1
name := "lisa"
lisa.string = &name
fmt.Printf("赋值后,Student的实例的值%#v\n",lisa)
//正确赋值方法2
//先分配内存再赋值
lisa.string = new(string)
*lisa.string = "hello"
fmt.Printf("赋值后,Student的实例的值%#v\n",lisa)
}
结果
sh
赋值前,Student的实例的值struct_knowledge.Student{name:(*string)(nil)}
赋值后,Student的实例的值struct_knowledge.Student{name:(*string)(0xc0000140a0)}
赋值后,Student的实例的值struct_knowledge.Student{name:(*string)(0xc0000140b0)}
指针类型嵌套结构体
具名结构体
和普通字段的处理情况一样,举例
举例
go
package struct_knowledge
import "fmt"
//结构体指针
func StructWithPoint1(){
type Animal struct{
name string
}
type Dog struct{
Anl *Animal
}
var dog Dog
/*
报错:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x490830]
*/
// dog.Anl.name = "duby"
// fmt.Printf("赋值后,dog的实例的值%#v\n",dog)
//方法1:先分配内存
dog.Anl = &Animal{}
dog.Anl.name = "duby"
fmt.Printf("赋值后,dog的实例的值%#v\n",dog)
//方法2:使用new方法
dog.Anl = new(Animal)
dog.Anl.name = "duby"
fmt.Printf("赋值后,dog的实例的值%#v\n",dog)
}
结果
sh
赋值后,dog的实例的值struct_knowledge.Dog{Anl:(*struct_knowledge.Animal)(0xc0000140a0)}
赋值后,dog的实例的值struct_knowledge.Dog{Anl:(*struct_knowledge.Animal)(0xc0000140b0)}
匿名结构体
和匿名字段一样
go
type Animal struct{
name string
}
type Dog struct{
*Animal
}
//等价于
type Dog struct{
Animal *Animal
}
注意
当使用指针类型的匿名结构体后,普通匿名结构体的特性就失去了,
举例
go
//匿名结构体
func StructWithPoint2(){
type Animal struct{
name string
}
type Dog struct{
*Animal
}
var dog Dog
/*
报错:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x490830]
*/
// dog.name = "Mao"
// fmt.Printf("赋值后,dog的实例的值%#v\n",dog)
//指针都需要先分配
nick := &Animal{name:"Mao"}
dog = Dog{
nick,
}
fmt.Printf("赋值后,dog的实例的值%#v\n",dog)
//也可以采用具名结构体一样的处理方式,例如
dog.Animal = new(Animal)
dog.Animal.name = "duby"
fmt.Printf("赋值后,dog的实例的值%#v\n",dog)
}
对于普通匿名结构体,我们可以用顶层结构体名.字段名
来访问嵌套结构体。
但是指针类型的嵌套结构体会报指针未分配内存的问题,所以我们我们必须给指针类型的结构体分配内存。
结果
sh
赋值后,dog的实例的值struct_knowledge.Dog{Animal:(*struct_knowledge.Animal)(0xc0000140a0)}
赋值后,dog的实例的值struct_knowledge.Dog{Animal:(*struct_knowledge.Animal)(0xc0000140b0)}
接收者类型
结构体的方法的接收者可以为指针也可以为值。
由于结构体是值类型的,所以当方法接收者使用的是值时,方法内的操作与外部无关;
当方法接收者是指针时,方法内的操作会影响到外部的原始变量。
举例
go
package struct_knowledge
import "fmt"
type Day struct{
Name string
Order int
}
func (d Day) ChangeVal(){
if d.Order==1 {
d.Name = "星期一"
}else{
d.Name = "未知"
}
fmt.Printf("接收者为值类型时,方法内的实例值为%#v\n",d)
}
func (d *Day) ChangeValByPorint(){
if d.Order==1 {
d.Name = "星期一"
}else{
d.Name = "未知"
}
fmt.Printf("接收者为值类型时,方法内的实例值为%#v\n",d)
}
调用
go
package main
import (
"fmt"
"go_learn/struct_knowledge"
)
func main(){
var day struct_knowledge.Day
day.Order = 1
day.ChangeVal()
fmt.Printf("接收者为值类型时,方法外的实例值为%#v\n",day)
day.ChangeValByPorint()
fmt.Printf("接收者为指针类型时,方法外的实例值为%#v\n",day)
}
结果
sh
接收者为值类型时,方法内的实例值为struct_knowledge.Day{Name:"星期一", Order:1}
接收者为值类型时,方法外的实例值为struct_knowledge.Day{Name:"", Order:1}
接收者为值类型时,方法内的实例值为&struct_knowledge.Day{Name:"星期一", Order:1}
接收者为指针类型时,方法外的实例值为struct_knowledge.Day{Name:"星期一", Order:1}
理解
1.方法的接收者是指针还是值不是由其调用者决定的,而是由方法本身决定的,如果方法的接收者为指针,方法就会自动取调用者的指针。
go
var day struct_knowledge.Day
day.Order = 1
//调用者都是 Day实例
// changeVal这个方法使用的是实例的值
day.ChangeVal()
//ChangeValByPorint这个方法使用的是实例的指针
day.ChangeValByPorint()
2.结构体指针问题:结构体实例指针的使用形式和其值的使用形式是一样的,所以一定要明确使用的是值还是指针。
golang为了美观,将数组和结构体实例指针前的*去除了。
go
func (d Day) ChangeVal(){
if d.Order==1 {
d.Name = "星期一"
}else{
d.Name = "未知"
}
fmt.Printf("接收者为值类型时,方法内的实例值为%#v\n",d)
}
func (d *Day) ChangeValByPorint(){
if d.Order==1 {
d.Name = "星期一"
}else{
d.Name = "未知"
}
fmt.Printf("接收者为值类型时,方法内的实例值为%#v\n",d)
}