go語言中沒有像類的概念,可是能夠經過結構體struct實現oop(面向對象編程)。struct的成員(也叫屬性或字段)能夠是任何類型,如普通類型、複合類型、函數、map、interface、struct等,因此咱們能夠理解爲go語言中的「類」。node
在定義struct成員時候區分大小寫,若首字母大寫則該成員爲公有成員(對外可見),不然是私有成員(對外不可見)。python
type struct_variable_type struct { member member_type member member_type ..... member member_type } //示例 type Student struct { name string age int Class string }
聲明與初始化編程
var stu1 Student var stu2 *Student= &Student{} //簡寫stu2 := &Student{} var stu3 *Student = new(Student) //簡寫stu3 := new(Student)
在struct中,不管使用的是指針的方式聲明仍是普通方式,訪問其成員都使用".",在訪問的時候編譯器會自動把 stu2.name 轉爲 (*stu2).name。json
struct分配內存使用new,返回的是指針。數據結構
struct沒有構造函數,可是咱們能夠本身定義「構造函數」。函數
struct是咱們本身定義的類型,不能和其餘類型進行強制轉換。oop
package main import "fmt" type Student struct { name string age int Class string } func main() { var stu1 Student stu1.age = 22 stu1.name = "wd" stu1.Class = "class1" fmt.Println(stu1.name) //wd var stu2 *Student = new(Student) stu2.name = "jack" stu2.age = 33 fmt.Println(stu2.name,(*stu2).name)//jack jack var stu3 *Student = &Student{ name:"rose",age:18,Class:"class3"} fmt.Println(stu3.name,(*stu3).name) //rose rose }
如下是經過工廠模式自定義構造函數方法佈局
package main import "fmt" type Student struct { name string age int Class string } func Newstu(name1 string,age1 int,class1 string) *Student { return &Student{name:name1,age:age1,Class:class1} } func main() { stu1 := Newstu("wd",22,"math") fmt.Println(stu1.name) // wd }
tag能夠爲結構體的成員添加說明或者標籤便於使用,這些說明能夠經過反射獲取到。spa
在前面提到了,結構體中的成員首字母小寫對外不可見,可是咱們把成員定義爲首字母大寫這樣與外界進行數據交互會帶來極大的不便,此時tag帶來了解決方法。指針
type Student struct { Name string "the name of student" Age int "the age of student" Class string "the class of student" }
應用場景示例,json序列化操做:
package main import ( "encoding/json" "fmt" ) type Student struct { Name string `json:"name"` Age int `json:"age"` } func main() { var stu = Student{Name:"wd",Age:22} data,err := json.Marshal(stu) if err != nil{ fmt.Println("json encode failed err:",err) return } fmt.Println(string(data)) //{"name":"wd","age":22} }
結構體中,每一個成員不必定都有名稱,也容許字段沒有名字,即匿名成員。
匿名成員的一個重要做用,能夠用來實現oop中的繼承。
同一種類型匿名成員只容許最多存在一個。
當匿名成員是結構體時,且兩個結構體中都存在相同字段時,優先選擇最近的字段。
package main import "fmt" type Person struct { Name string Age int } type Student struct { score string Age int Person } func main() { var stu = new(Student) stu.Age = 22 //優先選擇Student中的Age fmt.Println(stu.Person.Age,stu.Age)// 0,22 }
當結構體中的成員也是結構體時,該結構體就繼承了這個結構體,繼承了其全部的方法與屬性,固然有多個結構體成員也就是多繼承。
訪問父結構中屬性也使用「.」,可是當子結構體中存在和父結構中的字段相同時候,只能使用:"子結構體.父結構體.字段"訪問父結構體中的屬性,如上面示例的stu.Person.Age
繼承結構體可使用別名,訪問的時候經過別名訪問,以下面示例man1.job.Salary:
package main import "fmt" type Person struct { Name string Age int } type Teacher struct { Salary int Classes string } type man struct { sex string job Teacher //別名,繼承Teacher Person //繼承Person } func main() { var man1 = new(man) man1.Age = 22 man1.Name = "wd" man1.job.Salary = 8500 fmt.Println(man1,man1.job.Salary) //&{ {8500 } {wd 22}} 8500 }
go語言中的方法是做用在特定類型的變量上,所以自定義的類型均可以有方法,不只僅是在結構體中。
go中的方法和傳統的類的方法不太同樣,方法和類並不是組織在一塊兒,傳統的oop方法和類放在一個文件裏面,而go語言只要在同一個包裏就可,可分散在不一樣文件裏。go的理念就是數據和實現分離,引用官方說法:「Methods are not mixed with the data definition (the structs): they are orthogonal to types; representation(data) and behavior (methods) are independent」
方法的調用經過recv.methodName(),其訪問控制也是經過大小寫區分。
方法定義,其中recv表明方法做用的結構體:
func (recv type) methodName(parameter_list) (return_value_list) { … }
package main import "fmt" type Person struct { Name string Age int } func (p Person) Getname() string{ //p表明結構體自己的實列,相似python中的self,這裏p能夠寫爲self fmt.Println(p.Name) return p.Name } func main() { var person1 = new(Person) person1.Age = 22 person1.Name = "wd" person1.Getname()// wd }
當有告終構的方法時候,咱們能夠本身定義其初始化方法,因爲結構體是值類型,因此咱們使用指針才能改變其存儲的值。
package main import "fmt" type Person struct { Name string Age int } func (self *Person) init(name string ,age int){ self.Name = name self.Age = age } func main() { var person1 = new(Person) person1.init("wd",22) //(&person1).init("wd",22) fmt.Println(person1)//&{wd 22} }
若是實現告終構體中的String方法,在使用fmt打印時候會調用該方法,相似與python中的__str__方法.
package main import "fmt" type Person struct { Name string Age int } func (self *Person) String() string{ return self.Name } func main() { var person1 = new(Person) person1.Name = "wd" person1.Age = 22 fmt.Println(person1)// wd }
go中的結構體內存佈局和c結構體佈局相似,每一個成員的內存分佈是連續的,在如下示例中經過反射進行進一步說明:
package main import ( "fmt" "reflect" ) type Student struct { Name string Age int64 wight int64 high int64 score int64 } func main() { var stu1 = new(Student) fmt.Printf("%p\n",&stu1.Name) fmt.Printf("%p\n",&stu1.Age) fmt.Printf("%p\n",&stu1.wight) fmt.Printf("%p\n",&stu1.high) fmt.Printf("%p\n",&stu1.score) typ := reflect.TypeOf(Student{}) fmt.Printf("Struct is %d bytes long\n", typ.Size()) // We can run through the fields in the structure in order n := typ.NumField() for i := 0; i < n; i++ { field := typ.Field(i) fmt.Printf("%s at offset %v, size=%d, align=%d\n", field.Name, field.Offset, field.Type.Size(), field.Type.Align()) } } //結果 0xc42007a180 0xc42007a190 0xc42007a198 0xc42007a1a0 0xc42007a1a8 Struct is 48 bytes long Name at offset 0, size=16, align=8 Age at offset 16, size=8, align=8 wight at offset 24, size=8, align=8 high at offset 32, size=8, align=8 score at offset 40, size=8, align=8
在以上結果中,能夠看到內存地址的偏移老是以8字節偏移(使用的是int64,恰好是8字節),在觀察其內存地址,也是連續的,因此go語言中的結構體內存佈局是連續的。以下圖:
鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。
鏈表由一系列結點(鏈表中每個元素稱爲結點)組成,結點能夠在運行時動態生成。每一個結點包括兩個部分:一個是存儲數據元素的數據域,另外一個是存儲下一個結點地址的指針域。
鏈表有不少種不一樣的類型:單向鏈表,雙向鏈表以及循環鏈表。
下面以單鏈表爲例,使用go語言實現:
單鏈表:每一個節點包含下一個節點的地址,這樣把全部節點都串起來的鏈式數據數據結構叫作鏈表,一般把鏈表中的第一個節點叫作表頭。
使用struct定義單鏈表:
爲了方便,數據區域這裏使用int
type Node struct { data int next *node }
鏈表的遍歷是經過移動指針進行遍歷,當指針到最好一個節點時,其next指針爲nil
package main import "fmt" type Node struct { data int next *Node } func Shownode(p *Node){ //遍歷 for p != nil{ fmt.Println(*p) p=p.next //移動指針 } } func main() { var head = new(Node) head.data = 1 var node1 = new(Node) node1.data = 2 head.next = node1 var node2 = new(Node) node2.data = 3 node1.next = node2 Shownode(head) } //{1 0xc42000e1e0} //{2 0xc42000e1f0} //{3 <nil>}
單鏈表的節點插入方法通常使用頭插法或者尾插法。
頭插法:每次插入在鏈表的頭部插入節點。
package main import "fmt" type Node struct { data int next *Node } func Shownode(p *Node){ //遍歷 for p != nil{ fmt.Println(*p) p=p.next //移動指針 } } func main() { var head = new(Node) head.data = 0 var tail *Node tail = head //tail用於記錄頭節點的地址,剛開始tail的的指針指向頭節點 for i :=1 ;i<10;i++{ var node = Node{data:i} node.next = tail //將新插入的node的next指向頭節點 tail = &node //從新賦值頭節點 } Shownode(tail) //遍歷結果 } //{9 0xc42007a240} //{8 0xc42007a230} //{7 0xc42007a220} //{6 0xc42007a210} //{5 0xc42007a200} //{4 0xc42007a1f0} //{3 0xc42007a1e0} //{2 0xc42007a1d0} //{1 0xc42007a1c0} //{0 <nil>}
尾插法:每次插入節點在尾部,這也是咱們較爲習慣的方法。
package main import "fmt" type Node struct { data int next *Node } func Shownode(p *Node){ //遍歷 for p != nil{ fmt.Println(*p) p=p.next //移動指針 } } func main() { var head = new(Node) head.data = 0 var tail *Node tail = head //tail用於記錄最末尾的節點的地址,剛開始tail的的指針指向頭節點 for i :=1 ;i<10;i++{ var node = Node{data:i} (*tail).next = &node tail = &node } Shownode(head) //遍歷結果 } //{0 0xc42007a1c0} //{1 0xc42007a1d0} //{2 0xc42007a1e0} //{3 0xc42007a1f0} //{4 0xc42007a200} //{5 0xc42007a210} //{6 0xc42007a220} //{7 0xc42007a230} //{8 0xc42007a240} //{9 <nil>}