結構體與指針類型
指針類型字段
具名字段
舉例
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)
}