結(jié)構(gòu)體
- 結(jié)構(gòu)體也是一種類型,它可以存儲(chǔ)不同的數(shù)據(jù)類型涵紊,定義在函數(shù)外部:
type 結(jié)構(gòu)體名 struct { }
type Student struct { id int name string sex byte }
- 結(jié)構(gòu)體是一種數(shù)據(jù)存儲(chǔ)格式傍妒,不能在聲明結(jié)構(gòu)體時(shí)初始化成員,每個(gè)成員的值就是所屬類型的默認(rèn)值摸柄;
var stu Student fmt.Println(stu) // {0 "" 0}
- 結(jié)構(gòu)體的賦值
- 通過(guò)結(jié)構(gòu)體變量為其成員賦值颤练;
var stu Student stu.id = 10020 stu.name = "Mack" stu.sex = 1 fmt.Println(stu) // {10020 Mack 1}
- 結(jié)構(gòu)體的成員順序是不變的,匿名初始化時(shí)必須初始化結(jié)構(gòu)體成員的所有成員驱负,而指定名稱的初始化則不需要嗦玖;
var stu Student = Student{10020, "Mack", 1} fmt.Println(stu) // {10020 Mack 1} var stu Student = Student{name: "Mack"}
- 結(jié)構(gòu)體的傳遞是值傳遞,如果結(jié)構(gòu)體的成員都支持
==跃脊、!=
宇挫,那么該結(jié)構(gòu)體也支持==、!=
stu := Student{10020, "Mack", 1} m := stu m.id = 2 fmt.Println(stu) // {10020 Mack 1} fmt.Println(m) // {2 Mack 1} fmt.Println(m==stu) // false
- 結(jié)構(gòu)體與數(shù)組酪术、切片器瘪、字典
//結(jié)構(gòu)體數(shù)組 var arr [5]Student arr3 := [2]Student{{100, "Mack", 1}, {200, "Rose", 0}} //結(jié)構(gòu)體切片 s := []Student{{100, "Mack", 1}, {200, "Rose", 0}} //結(jié)構(gòu)體Map m := make(map[string]Student) //key為string型,value為結(jié)構(gòu)體Student m["10010"] = {100, "Mack", 1} //結(jié)構(gòu)體切片Map ms := make(map[string][]Student) //key為string型,value為切片娱局,且切片元素類型為結(jié)構(gòu)體Student ms["10012"] = append(ms["10012"], Student{1, "Mack", 1}) fmt.Println(ms) //map[10012:[{1 Mack 1}]]
- 結(jié)構(gòu)體本身在定義時(shí)存儲(chǔ)在數(shù)據(jù)區(qū)彰亥,其中的成員則根據(jù)數(shù)據(jù)類型存儲(chǔ)在不同區(qū)域,如數(shù)組棧區(qū)衰齐,切片在堆區(qū)任斋。
指針
指針用于存儲(chǔ)變量的內(nèi)存地址,指針的類型與變量的類型保持一致耻涛;
- 定義:
var p *類型
废酷,一級(jí)指針,指針的默認(rèn)值為nil(空指針)
var p *int fmt.Println(p) // <nil> var num int = 1 p = &num fmt.Println(p) // 0xc00000a0c0 fmt.Printf("%p", &num) // 0xc00000a0c0
-
*
取值運(yùn)算符抹缕,用于從內(nèi)存地址中存儲(chǔ)的值澈蟆,通過(guò)指針還可以修改內(nèi)存中的值;num := 1 p := &num fmt.Println(*p) // 1 *p = 22 fmt.Println(num) // 22
- 空指針指向內(nèi)存編號(hào)為
0
的空間卓研,但是內(nèi)存編號(hào)0-255
被系統(tǒng)占用趴俘,不允許用戶進(jìn)行讀寫操作;var p *int fmt.Println(*p) // runtime error *p = 1 //runtime error
- 野指針:指向一塊未知的空間奏赘,與空指針類似寥闪,都不允許進(jìn)行讀寫操作;
var p *int p = 0x04803c60 *p = 1 //runtime error
- 數(shù)組指針
- 數(shù)組存儲(chǔ)在一塊連續(xù)的內(nèi)存空間磨淌,數(shù)組指針指向數(shù)組的首地址疲憋;
var arr [5]int = [5]int{1,2,3,4,5} var p *[5]int = &arr fmt.Println(p) // &[1 2 3 4 5] 不會(huì)直接打印數(shù)組指針指向的地址 fmt.Printf("%p", &arr) // 0xc00006c060 fmt.Printf("%p", &arr[0]) // 0xc00006c060 fmt.Printf("%p", p) // 0xc00006c060 fmt.Printf("%p", &arr[1]) // 0xc00006c068
- 數(shù)組指針操作數(shù)據(jù)元素:
(*)p[0]、p[0]
fmt.Println(*p) // [1 2 3 4 5] (*p)[0] = 100 // [] 操作符的優(yōu)先級(jí)高于 * //Go語(yǔ)言做了優(yōu)化梁只,使用簡(jiǎn)寫形式 p[1] = 200 fmt.Println(arr) // [100 200 3 4 5] len(p) // 5 通過(guò)指針變量獲取數(shù)組長(zhǎng)度
- 切片數(shù)組
- 與數(shù)組名不同缚柳,切片名本身就是一個(gè)地址,且這個(gè)地址就是切片存儲(chǔ)的堆區(qū)內(nèi)存地址搪锣;
slice := []int{1,2,3,4,5} fmt.Printf("%p", slice) // 0xc00006c060 fmt.Printf("%p", &slice[0]) // 0xc00006c060 p := &slice fmt.Printf("%p", p) // 0xc00004c420
-
&slice
獲取的是棧區(qū)中slice
變量的地址秋忙,而不是切片存儲(chǔ)在堆區(qū)的地址! - 通過(guò)指針操作切片時(shí)的查找過(guò)程:
p -> slice變量地址 -> slice指向的堆區(qū)地址
(*p)[0] = 200 fmt.Println(slice) // [200 2 3 4 5] fmt.Println(*p) // [200 2 3 4 5]
- Go沒(méi)有對(duì)指針訪問(wèn)切片做優(yōu)化淤翔,仍需要使用取地址操作符
*
- 這種沒(méi)有直接指向堆區(qū)地址的指針翰绊,稱為二級(jí)指針佩谷!
- 既然指針指向的是切片變量的地址旁壮,那么對(duì)指針使用
append()
導(dǎo)致切片擴(kuò)容時(shí),切片變量指向的堆區(qū)地址也會(huì)隨之變化谐檀!
slice := []int{1,2,3} fmt.Printf("%p", slice) // 0xc00006c060 p := &slice *p = append(*p, 6, 7, 8, 9) fmt.Printf("%p", slice) // 0xc00008a000 fmt.Println(slice) // [1 2 3 6 7 8 9]
- 手動(dòng)創(chuàng)建指針空間:
new(數(shù)據(jù)類型)
var p *int p = new(int) // 為指針變量在堆區(qū)創(chuàng)建一塊內(nèi)存空間 fmt.Println(p) // 0xc000056080 fmt.Println(*p) // 0
- 開辟的空間位于堆區(qū)抡谐,存儲(chǔ)的值是數(shù)據(jù)類型的默認(rèn)值,并返回內(nèi)存地址桐猬;
- Go語(yǔ)言中無(wú)需關(guān)心內(nèi)存空間的釋放麦撵,交由系統(tǒng)管理.
- 指針的傳遞是地址傳遞,形參可以改變實(shí)參的值;
- 指針數(shù)組/切片:元素為指針的數(shù)組/切片
a, b := 1, 2 var arr [2]*int arr[0] = &a arr[1] = &b fmt.Println(arr) // [0xc00000a098 0xc00000a0b0] var slice []*int slice = append(slice, &a, &b) fmt.Println(slice) // [0xc00000a098 0xc00000a0b0]
- 結(jié)構(gòu)體指針:也是指向結(jié)構(gòu)體的首地址免胃,還可以簡(jiǎn)化操作結(jié)構(gòu)體成員
type Student struct { id int name string } func main() { var stu Student = Student{1, "Alpa"} var p *Student p = &stu fmt.Printf("%p\n", &stu.id) // 0xc000004480 fmt.Printf("%p\n", p) // 0xc000004480 p.name = "Babel" // (*p).name = "Babel" fmt.Println(stu) // {1 Babel} }
- 多級(jí)指針
a := 10 p := &a // 一級(jí)指針 P2 := &p // 二級(jí)指針 P3 := &p2 // 三級(jí)指針
內(nèi)存模型
- 內(nèi)存是一塊連續(xù)的一維數(shù)組空間:
低地址 -> 高地址
音五,其中0 - 255
被系統(tǒng)所占用; - 對(duì)于一個(gè)應(yīng)用程序來(lái)說(shuō)羔沙,內(nèi)存可以簡(jiǎn)單分為四個(gè)區(qū)域躺涝,從低地址到高地址依次為:代碼區(qū)、數(shù)據(jù)區(qū)扼雏、堆區(qū)坚嗜、棧區(qū);
- 代碼區(qū):存儲(chǔ)計(jì)算指令信息诗充,只讀
- 數(shù)據(jù)區(qū):又可分為常量區(qū)苍蔬、初始化數(shù)據(jù)區(qū),未初始化數(shù)據(jù)區(qū)蝴蜓,其中常量區(qū)不允許顯示內(nèi)存地址碟绑,而結(jié)構(gòu)體就位于未初始化數(shù)據(jù)區(qū);
- 堆區(qū):
切片數(shù)據(jù)茎匠、string類型數(shù)據(jù)蜈敢、new()...
- 棧區(qū):
局部變量、函數(shù)調(diào)用時(shí)的信息
- 它們之間并不是相鄰的汽抚,中間還有一些小的區(qū)域抓狭,其中最高地址段分配給了注冊(cè)表。
面向?qū)ο?/h2>
Go語(yǔ)言中沒(méi)有類的概念造烁,但可以將結(jié)構(gòu)體比作類否过,結(jié)構(gòu)體成員比較類的屬性、方法惭蟋;
Go語(yǔ)言中也沒(méi)有繼承苗桂,但可以通過(guò) 匿名組合
來(lái)實(shí)現(xiàn)繼承的效果。
- 匿名字段告组,又叫匿名組合:用一個(gè)結(jié)構(gòu)體的名稱作為另一個(gè)結(jié)構(gòu)體的成員煤伟;
- 在初始化子結(jié)構(gòu)體時(shí),可以直接訪問(wèn)父結(jié)構(gòu)體的成員木缝;
type Person struct { name string age int } type Student struct { Person id int score float32 } func main() { var stu Student stu.id = 101 stu.name = "Mack" //直接訪問(wèn)父結(jié)構(gòu)體成員 stu.Person.age = 20 //間接訪問(wèn) stu.score = 60.5 fmt.Println(stu) //{{Mack 20} 101 60.5} }
- 定義時(shí)的初始化
var stu Student = Student{Person{"Jack", 17}, 102, 87}
- 但是便锨,如果子結(jié)構(gòu)體和父結(jié)體有同名成員,則采用就近原則:初始化父結(jié)構(gòu)體成員時(shí)我碟,必須指定父結(jié)構(gòu)體名稱放案;
type Person struct { name string age int } type Student struct { Person id int name string score float32 } func main() { var stu Student stu.id = 101 stu.name = "Mack" //子結(jié)構(gòu)體自己的name stu.Person.name = "Jason" //父結(jié)構(gòu)的name stu.age = 13 stu.score = 60.5 fmt.Println(stu) //{{Jason 13} 101 Mack 60.5} }
- 指針匿名字段:繼承父結(jié)構(gòu)體的指針
type Person struct { name string age int } type Student struct { *Person id int score float32 }
- 指針的默認(rèn)值是
nil
,稱為空指針矫俺,無(wú)法直接訪問(wèn)
var stu Student stu.name = "Mack" stu.Person.name = "Jason" //runtime error: invalid memory address or nil...
-
new()
初始化指針變量
var stu Student stu.Person = new(Person) stu.Person.name = "Mack" stu.age = 20 fmt.Println(stu.name, stu.age) // Mack 20
- 亦或者:先初始化父結(jié)構(gòu)體吱殉,再把地址賦與子結(jié)構(gòu)體的指針掸冤;
var p = Person{"Mack", 14} var stu Student stu.Person = &p //定義時(shí)初始化 var stu2 = Student{ &Person{"Mack", 14}, 103, 78.5 }
- 指針的默認(rèn)值是
- 支持多重繼承,但自己不允許繼承自己友雳,卻可以繼承自己的結(jié)構(gòu)體指針稿湿,這也是鏈表的實(shí)現(xiàn)原理;
type Person struct { *Person name string age int }
方法
- Go中的方法和函數(shù)是有明顯區(qū)別的
func (方法接收者) 方法名(參數(shù)列表) 返回值類型 { 方法體 }
- 根據(jù)數(shù)據(jù)類型綁定方法押赊,方法接收者也可以為方法傳遞參數(shù)缎罢;
type Integer int //為 int 定義別名 func (a Integer) Test(b Integer) Integer { return a+b } func main() { var c Integer = 3 r := c.Test(3) fmt.Println(r) // 6 }
- Go語(yǔ)言不允許方法直接使用數(shù)據(jù)類型,必須定義別名考杉!
- 方法接收者是一種類型策精,為這種類型綁定了方法之后,此類型的變量都具有該方法崇棠!
- 結(jié)構(gòu)體也是在為
struct
起別名咽袜,那么就可以為結(jié)構(gòu)體綁定方法;type Student struct { id int name string age int } func (s Student) print() { fmt.Println(s.name) } func (s Student) edit(name string) { s.name = name s.print() // 調(diào)用另一個(gè)方法 } func main() { s := Student{ 101, "Mart", 20} s.edit("Jose") // Jose }
- 結(jié)構(gòu)體指針訪問(wèn)成員時(shí)枕稀,支持簡(jiǎn)化操作询刹,訪問(wèn)方法時(shí)也支持簡(jiǎn)化操作;
func (s *Student) edit(name string) { s.name = name s.print() // (*s).edit("Jose") } func main() { s := Student{ 101, "Mart", 20} s.edit("Jose") // (&s).edit("Jose") var s2 *Student //空指針 s2 = new(Student) // 為指針s2 開啟空間 s2.edit("Jose") // Jose }
- 考慮到結(jié)構(gòu)體是值傳遞萎坷,所以都應(yīng)該使用結(jié)構(gòu)體指針作為接收者凹联;
- 結(jié)構(gòu)體中不允許出現(xiàn)同名方法,即使一個(gè)方法接收者是結(jié)構(gòu)體名
Student
哆档、另一個(gè)接收者是結(jié)構(gòu)體指針*Student
蔽挠,也不允許!
- 方法繼承:匿名字段也可以實(shí)現(xiàn)方法的繼承瓜浸,同名方法也遵循就近原則澳淑;
- 方法名允許與函數(shù)名重名,但在同級(jí)別文件中可以直接調(diào)用函數(shù)插佛,卻不能調(diào)用方法杠巡,必須先定義結(jié)構(gòu)體;
- 方法的本質(zhì)還是函數(shù)雇寇,類型都是
func()
氢拥,也都存儲(chǔ)在代碼區(qū)。
接口
- 定義
type 接口名 interface { 方法列表 //只有聲明锨侯,沒(méi)有具體實(shí)現(xiàn) }
- 對(duì)象(結(jié)構(gòu)體)必須實(shí)現(xiàn)所有的接口方法嫩海,接口指向?qū)ο蟮膬?nèi)存地址,通過(guò)接口調(diào)用對(duì)象實(shí)現(xiàn)的方法识腿,這就是多態(tài)出革;
type Humaner interface { say() } type Student struct { name string age int } func (s *Student) say() { fmt.Printf("Student: %s", s.name) } type Teacher struct { name string age int } func (t *Teacher) say() { fmt.Printf("Teacher: %s", t.name) } func main() { var stu Student = Student{ "Mack", 20 } var h Humaner h = &stu h.say() // Student: Mack h = &Teacher{ "Eason", 46 } h.say() // Teacher: Eason }
- 接口也可以通過(guò)匿名字段實(shí)現(xiàn)繼承,且接口中的形參可以省略參數(shù)名渡讼;
type Humaner interface { Say() } type Male interface { Humaner Dance(string) //形參可以省略參數(shù)名 } type Student struct { name string } func (s *Student) Say() { fmt.Printf("%s say", s.name) } func (s *Student) Dance(label string) { fmt.Printf("dance: %s", label) } func main() { var m Male m = &Student{ "Mack" } m.Say() // Mack say m.Dance("AAA") // dance: AAA var h Humaner h = &Student{ "Eason" } h.Say() // Eason say }
- 接口的轉(zhuǎn)換:允許父接口指向子接口骂束,那么父接口在調(diào)用方法時(shí),指向的是針對(duì)子接口的實(shí)現(xiàn)方法成箫;
h = m h.Say() // Mack say
- 空接口:
var a interface{}
- 空接口可以接收任意類型的數(shù)據(jù)展箱,但空接口的地址不變!
var a interface{} fmt.Printf("%p", &a) // 0xc0000501c0 a = 10 fmt.Printf("%p", &a) // 0xc0000501c0 a = "Hello Go" fmt.Printf("%p", &a) // 0xc0000501c0 a = false fmt.Printf("%p", &a) // 0xc0000501c0
- 空接口切片
var a []interface{} a = append(a, 12, "Hello", [3]int{1,2,3}) fmt.Println(a) // [12 Hello [1 2 3]] b := make([]interface{}, 3)
- 類型斷言:類型判斷
var b []interface{} b = append(a, 12, "Hello", [3]int{1,2,3}) for _,v:=range b { data,ok := v.(int) // 判斷元素v 是不是 int 類型 }
-
ok
表示判斷結(jié)果蹬昌,如果是混驰,則為true
,否則為false
; -
data
表示判斷類型的值皂贩,當(dāng)前判斷是不是int
類型栖榨,如果是,data
值為元素值明刷,否則為int
類型的默認(rèn)值0
;
for _,v:=range b { if data,ok := v.(int); ok { // ok 為 true 時(shí)婴栽,則進(jìn)入此分支 } else if data,ok := v.([3]int); ok { } }
-