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