結構體:
1、用來自定義復雜數據結構
2、struct里面可以包含多個字段(屬性)
3、struct類型可以定義方法,注意和函數的區分
4、strucr類型是值類型
5、struct類型可以嵌套
6、go語言中沒有class類型,只有struct類型
struct聲明:
type 標識符 struct{
field1 type
field2 type
}
例子:
type Student struct{
Name string
Age int
Score int
}
struct中字段訪問,和其他語言一樣,使用點
例子:
var stu Student //拿結構題定義一個變量
stu.Name=”tony”
stu.Age=18
stu.Score=20
fmt.Printf(“name=%s,age=%d,score=%d”,stu.Name,stu.Age,stu.Sore)
struct定義的三種形式 初始化的三種方式
a、var stu Student
b、var stu *Student=new(Student)
c、var stu *Student=&Student{}
其中b和c返回的都是指向結構體的指針,訪問形式如下:
a、stu.Name、stu.Age 和stu.Score 或者(*stu).Name、 (*stu).Age等
如果是指針形式可以用上面的普通的方式訪問,其實就自動轉化為指針訪問的形式
package main
import (
"fmt"
)
type Student struct{
Name string
Age int
score float32
}
func main(){
//聲明方式一
var stu Student
stu.Name="hua"
stu.Age=18
stu.score=80
//聲明方式二
var stu1 *Student =&Student{
Age:20,
Name:"hua",
}
//聲明方式三
var stu3 =Student{
Age:20,
Name:"hua",
}
fmt.Printf(stu1.Name)
fmt.Printf(stu3.Name)
}
struct內存布局

例子:
package main
import(
"fmt"
)
type Student struct{
Name string
Age int
score float32
}
func main(){
var stu Student
stu.Name="hua"
stu.Age=18
stu.score=80
fmt.Print(stu)
fmt.Printf("Name:%p\n",&stu.Name)
fmt.Printf("Age:%p\n",&stu.Age)
fmt.Printf("score:%p\n",&stu.score)
}
{hua 18 80}Name:0xc04204a3a0
Age:0xc04204a3b0
score:0xc04204a3b8
這里int32是4字節,64是8字節
鏈表的定義:
type Student struct{
name string
next* Student
}
每個節點包含下一個節點的地址,這樣把所有的節點串起來,通常把鏈表中的每一個節點叫做鏈表頭
遍歷到最后一個元素的時候有個特點,就是next這個指針指向的是nil,可以從這個特點來判斷是否是鏈表結束
單鏈表的特點:只有一個字段指向后面的結構體
單鏈表只能從前往后遍歷
雙鏈表的特點:有兩個字段,分別指向前面和后面的結構體
雙鏈表可以雙向遍歷
鏈表操作:
1、生成鏈表及遍歷鏈表操作
package main
import (
"fmt"
)
type Student struct{
Name string
Age int
Score float32
next *Student
}
func main(){
var head Student
head.Name="hua"
head.Age=18
head.Score=80
var stu1 Student
stu1.Name="stu1"
stu1.Age=20
stu1.Score=100
head.next=&stu1
//遍歷
var p *Student=&head //生成p指針,指向head
for p!=nil{ //這里p就是head結構體,所以要從第一個遍歷
fmt.Println(*p)
p=p.next
}
}
D:\project>go build go_dev / example/example3
D:\project>example3.exe
{hua 18 80 0xc042078060} 這里第三個值指向的是下一個結構體
{stu1 20 100 <nil>}
上面的程序不規范,修改如下:
package main
import (
"fmt"
)
type Student struct{
Name string
Age int
Score float32
next *Student
}
func trans(p *Student){
for p!=nil { //這里p就是head結構體,所以要從第一個遍歷
fmt.Println(*p)
p = p.next
}
}
func main(){
var head Student
head.Name="hua"
head.Age=18
head.Score=80
var stu1 Student
stu1.Name="stu1"
stu1.Age=20
stu1.Score=100 //這里默認第二個鏈表為nil
head.next=&stu1
//var p *Student=&head
trans(&head) //生成p指針,指向head
}
插入鏈表的方法:
1、尾部插入法,就在鏈表的尾部插入結構體
代碼如下:
package main
import(
"fmt"
)
type Student struct{
Name string
Age int
Score float32
next *Student
}
func trans(p *Student){
for p!=nil { //這里p就是head結構體,所以要從第一個遍歷
fmt.Println(*p)
p = p.next
}
}
func main() {
var head Student
head.Name = "hua"
head.Age = 18
head.Score = 80
var stu1 Student
stu1.Name = "stu1"
stu1.Age = 20
stu1.Score = 100
var stu2 Student
stu2.Name="stu2"
stu2.Age=22
stu2.Score=90
head.next=&stu1
stu1.next=&stu2
trans(&head)
}
2、尾部循環插入
package main
import (
"fmt"
"math/rand"
)
type Student struct{
Name string
Age int
Score float32
next *Student
}
func trans(p *Student){
for p!=nil { //這里p就是head結構體,所以要從第一個遍歷
fmt.Println(*p)
p = p.next
}
}
//尾部循環插入數據
func trans2(tail *Student){
for i:=0;i<10;i++{
stu:=&Student{
Name:fmt.Sprintf("stu%d",i),
Age:rand.Intn(100),
Score:rand.Float32()*100,
}
//注意下面的是指針
tail.next=stu //鏈表是指向下一個
tail=stu //更新最后一個鏈表
}
}
func main(){
var head Student
head.Name="hua"
head.Age=18
head.Score=100
//下面這兩個都是根據head這個鏈表結構體產生
trans2(&head)
trans(&head)
}
3、頭部插入
1、注意給指針分配內存空間
2、用指針的方式才可以分配內存,如果用變量就不行(牽扯到地址就用指針)
package main
import(
"fmt"
"math/rand"
)
type Student struct{
Name string
Age int
Score float32
next *Student
}
func trans(p *Student){
for p!=nil{
fmt.Println(*p)
p=p.next
}
}
func main(){
//因為這是指針,所以要給指針分配空間,下面是給指針分配內存空間的兩種方法
//var head *Student=&Student{}
var head *Student=new(Student)
head.Name="hua"
head.Age=18
head.Score=100
//從頭插入
for i:=0;i<10;i++{
stu:=Student{
Name:fmt.Sprintf("stu%d",i),
Age:rand.Intn(100),
Score:rand.Float32()*100,
}
//頭部插入必須要傳遞指針才可以,首先頭部插入一個,鏈表指向第一個,但是插入的鏈表地址被覆蓋
stu.next=head
head=&stu //指針賦值
}
trans(head)
}
頭部插入和尾部插入的區別是
頭部插入:需要用指針的方式來插入
尾部插入:直接插入就可以了(變量形式插入)
優化代碼
package main
import(
"fmt"
"math/rand"
)
type Student struct{
Name string
Age int
Score float32
next *Student
}
//打印函數
func trans(p *Student){
for p!=nil{
fmt.Println(*p)
p=p.next
}
}
//這里的head是指針變量副本,這里要接受指針的指針,head1 * Student是指針變量的副本
func insertHead(head1 **Student){
//從頭插入
for i:=0;i<10;i++ {
stu := Student{
Name: fmt.Sprintf("stu%d", i),
Age: rand.Intn(100),
Score: rand.Float32() * 100,
}
//因為參數是指針的指針,所以這里要傳遞指針的指針,
stu.next = *head1
*head1 = &stu
}
}
func main(){
var head *Student=new(Student)
head.Name="hua"
head.Age=18
head.Score=100
//因為這個函數要改變指針變量的值,所以要傳遞指針的地址進去
insertHead(&head)
trans(head)
}
理解:
如下:這里的insertHead中的head1是head的副本,開始head1和head是指向同一個內存地址,當head1=&stu的時候head1的地址,也就是head的副本的地址就變化了,但是head還是沒有變化的。所以要改變指針的地址,也就是head的地址,這里函數必須要傳遞指針的指針才可以,在指針的基礎之上多加一個*,
func insertHead(head1 **Student){}
然后傳遞的時候要傳遞指針的地址,如 insertHead(* head)
小結: 要改變指針變量的值,就要傳遞指針的指針進去
指針還有二級指針,三級指針等

刪除鏈表
刪除指定節點:
思路:
1、遍歷,
2、遍歷當前節點的上個節點的next等於當前節點的下一個節點,這個節點就刪除了
3、如果第一次沒有找到,那么就往后移動位置,即當前節點的上級節點等於當前節點,當前節點的下一個節點賦值給當前節點,
4、下面這個代碼是有問題的,主要是這是一個副本。頭部插入會有問題
代碼:
package main
import (
"fmt"
"math/rand"
)
type Student struct{
Name string
Age int
Score float32
next *Student //指向下一個節點
}
func trans(p *Student) {
for p!=nil{
fmt.Println(*p)
p=p.next
}
}
//頭部插入
func insertHead(head **Student){
//從頭插入
for i:=0;i<10;i++ {
stu := Student{
Name: fmt.Sprintf("stu%d", i),
Age: rand.Intn(100),
Score: rand.Float32() * 100,
}
//因為參數是指針的指針,所以這里要傳遞指針的指針,
stu.next = *head
*head = &stu
}
}
func delNode(p * Student){
//臨時變量保存上一個節點
var prev *Student=p
/*
遍歷鏈表
1、首先判斷當前鏈表節點是否等於要刪除的鏈表,如果是那么把當前鏈表節點上一個節點等於
當前鏈表節點的下一個節點
2、如果沒有找到,那么當前鏈表節點就等於上個節點,當前鏈表節點就指向下個節點,也就是往后移動位置
*/
for p!=nil{
if p.Name=="stu6"{
prev.next=p.next
break
}
//如果沒有找到,那么p就等於上個節點,p就指向下個節點
prev=p
p=p.next
}
}
func main(){
var head *Student=new(Student)
head.Name="hua"
head.Age=18
head.Score=100
insertHead(&head)
delNode(head)
trans(head)
}
怎么在上面stu6后面插入一個節點?
思路:
1、首先生成一個節點,讓這個節點的下一個節點等於stu6的下一個節點
2、再讓stu6的下一個節點指向插入的這個節點
package main
import (
"fmt"
"math/rand"
)
type Student struct{
Name string
Age int
Score float32
next *Student //指向下一個節點
}
func trans(p *Student) {
for p!=nil{
fmt.Println(*p)
p=p.next
}
}
//頭部插入
func insertHead(head **Student){
//從頭插入
for i:=0;i<10;i++ {
stu := Student{
Name: fmt.Sprintf("stu%d", i),
Age: rand.Intn(100),
Score: rand.Float32() * 100,
}
//因為參數是指針的指針,所以這里要傳遞指針的指針,
stu.next = *head
*head = &stu
}
}
func delNode(p * Student){
//臨時變量保存上一個節點
var prev *Student=p
/*
遍歷鏈表
1、首先判斷當前鏈表節點是否等於要刪除的鏈表,如果是那么把當前鏈表節點上一個節點等於
當前鏈表節點的下一個節點
2、如果沒有找到,那么當前鏈表節點就等於上個節點,當前鏈表節點就指向下個節點,也就是往后移動位置
*/
for p!=nil{
if p.Name=="stu6"{
prev.next=p.next
break
}
//如果沒有找到,那么p就等於上個節點,p就指向下個節點
prev=p
p=p.next
}
}
//在stu5后面插入一個鏈表
func addNode(p *Student,newNode * Student){
for p!=nil{
if p.Name=="stu5"{
newNode.next=p.next
p.next=newNode
break
}
p=p.next
}
}
func main(){
var head *Student=new(Student)
head.Name="hua"
head.Age=18
head.Score=100
insertHead(&head)
delNode(head)
trans(head)
var newNode *Student=new(Student)
newNode.Name="stu1000"
newNode.Age=18
newNode.Score=100
addNode(head,newNode)
trans(head)
}
雙向鏈表
定義 type Student struct{
Name sring
next * Student
prevn * Student
}
如果有兩個指針分別指向前一個節點和后一個節點,我們叫做雙鏈表
二叉樹
定義:
type Student struct{
Name string
left * Student
right *Student
}
如果每個節點有兩個指針分別用來指向左子樹和右子樹,我們把這樣的結構叫做二叉樹
對於二叉樹,要用到廣度優先或者深度優先的遞歸算法
下面是二叉樹的類型圖,下面的stu2的右邊的孩子也可以是為nil,然后stu3如果沒有孩子就叫做葉子節點,stu02是stu01的子樹

代碼:
下面采用遞歸的形式進行遍歷二叉樹,下面是深度優先的原理
如果要采取廣度優先,那么每次遍歷的時候就要把結果放到隊列里面
前序遍歷:是從根節點開始遍歷的
package main import( "fmt" ) //聲明二叉樹 type Student struct{ Name string Age int Score float32 left *Student right *Student } func trans(root *Student){ if root==nil{ return } fmt.Println(root) //遞歸遍歷左子樹 trans(root.left) //遞歸然后遍歷右子樹 trans(root.right) } func main(){ //初始化root定點 var root *Student=new(Student) root.Name="stu01" root.Age=18 root.Score=100 root.left=nil //初始化 root.right=nil var left1 *Student=new(Student) left1.Name="stu02" left1.Age=19 left1.Score=100 //把這個節點插入到root的左邊 root.left=left1 var right1 *Student=new(Student) right1.Name="stu04" right1.Age=19 right1.Score=100 //把這個節點插入到root的右邊 root.right=right1 var left02 *Student=new(Student) left02.Name="stu03" left02.Age=18 left02.Score=100 //把這個節點插入到left1的左邊 left1.left=left02 trans(root) } /* 下面結果分別是,Name Age Score 然后左邊和右邊的地址 &{stu01 18 100 0xc042082090 0xc0420820c0} &{stu02 19 100 0xc0420820f0 <nil>} &{stu03 18 100 <nil> <nil>} &{stu04 19 100 <nil> <nil>} */ 中序遍歷:先遍歷左子樹,然后遍歷根節點,然后遍歷右節點 package main import( "fmt" ) //聲明二叉樹 type Student struct{ Name string Age int Score float32 left *Student right *Student } func trans(root *Student){ if root==nil{ return } //中序遍歷 trans(root.left) fmt.Println(root) trans(root.right) /* 結果 &{stu03 18 100 <nil> <nil>} &{stu02 19 100 0xc0420820f0 <nil>} &{stu01 18 100 0xc042082090 0xc0420820c0} &{stu04 19 100 <nil> <nil>} */ } func main(){ //初始化root定點 var root *Student=new(Student) root.Name="stu01" root.Age=18 root.Score=100 root.left=nil //初始化 root.right=nil var left1 *Student=new(Student) left1.Name="stu02" left1.Age=19 left1.Score=100 //把這個節點插入到root的左邊 root.left=left1 var right1 *Student=new(Student) right1.Name="stu04" right1.Age=19 right1.Score=100 //把這個節點插入到root的右邊 root.right=right1 var left02 *Student=new(Student) left02.Name="stu03" left02.Age=18 left02.Score=100 //把這個節點插入到left1的左邊 left1.left=left02 trans(root) } 后序遍歷:首先遍歷左子樹,然后遍歷右子樹,最后遍歷根節點 package main import( "fmt" ) //聲明二叉樹 type Student struct{ Name string Age int Score float32 left *Student right *Student } func trans(root *Student){ if root==nil{ return } //后序遍歷 trans(root.left) trans(root.right) fmt.Println(root) /* 結果 &{stu03 18 100 <nil> <nil>} &{stu02 19 100 0xc04206e0f0 <nil>} &{stu04 19 100 <nil> <nil>} &{stu01 18 100 0xc04206e090 0xc04206e0c0} */ } func main(){ //初始化root定點 var root *Student=new(Student) root.Name="stu01" root.Age=18 root.Score=100 root.left=nil //初始化 root.right=nil var left1 *Student=new(Student) left1.Name="stu02" left1.Age=19 left1.Score=100 //把這個節點插入到root的左邊 root.left=left1 var right1 *Student=new(Student) right1.Name="stu04" right1.Age=19 right1.Score=100 //把這個節點插入到root的右邊 root.right=right1 var left02 *Student=new(Student) left02.Name="stu03" left02.Age=18 left02.Score=100 //把這個節點插入到left1的左邊 left1.left=left02 trans(root) }
結構體與方法
結構體是用戶單獨定義的類型,不能和其他類型進行強制轉換
type Student struct{
Number int
}
type Stu Student //alias 別名 type 變量 類型 這個是定義類型的別名
var a Student
a=Student{30}
var b Stu
a=b //這樣賦值錯誤
a=Student(b) //這樣才可以
上面這兩個Stu和Student是別名關系,但是這兩個字段一樣,並不是同一個類型,因為是type定義的
如:
package main
import (
"fmt"
)
type integer int
func main(){
//賦值給誰呢么類型,就要強制轉換成什么類型
var i integer=1000
fmt.Println(i)
var j int=100
//這里i是自定義的類型, j是int類型,所以賦值的時候要強制轉換,如下
j=int(i) //i如果賦值給j應該強制轉換為int類型
i=integer(j) //j如果想復制給i必須轉換為integer類型
fmt.Println(i)
fmt.Println(j)
}
工廠模式
golang中的struct沒有構造函數,一般可以使用工廠模式來解決這個問題
Package model
type student Struct{
Name string
Age int
}
func NewStudent(name string,age int)*Student{
return &Student{ //創建實例
Name:name
Age:age
}
}
Package main
S:new(student)
S:model.NewStudent(“tony”,20)
再次強調
make用來創建map,slice ,channel
new 用來創建值類型
struct中的tag
我們可以為strct中的每一個字段,協商一個tag,這個tag可以通過反射機制獲取到,最常用的場景就是json序列化和反序列化
type student struct{
Name string “this is name field” //每個字段寫一個說明,作為這個字段的描述
Age int “this is age field”
}
json打包
json.Marshal()
注意:
json打包的時候,
1、必須要把結構體中的字段大寫,才可以
下面是程序聲明打包初始化的兩種方式
package main
import(
"encoding/json"
"fmt"
)
type Student struct{
Name string `json:"Student_name"`
age int `json:"student_age"`
score int `json:score`
}
func main(){
//聲明
var stu Student=Student{
Name:"stu01",
age:10,
score:100,
}
data,err:=json.Marshal(stu) //打包,返回值為byte
if err!=nil{
fmt.Println("json encode stu faild,err",err)
return
}
fmt.Println(string(data)) //把byte轉化成string
}
//{"Student_name":"stu01"}
也可以下面的方式書寫
package main
import(
"encoding/json"
"fmt"
)
type Student struct{
Name string `json:"Student_name"`
age int `json:"student_age"`
score int `json:score`
}
func main(){
//初始化
var stu *Student=new(Student)
stu.Name="stu01"
data,err:=json.Marshal(stu) //打包,返回值為byte
if err!=nil{
fmt.Println("json encode stu faild,err",err)
return
}
fmt.Println(string(data)) //把byte轉化成string
}
//{"Student_name":"stu01"}
匿名字段
結構體 中字段可以沒有名字,叫做匿名字段
type Car struct{
Name string
Age int
}
type Train struct{
Car //匿名字段
Start time.Time //有名字段
int //匿名字段
}
匿名字段要怎么訪問呢?
package main
import (
"fmt"
"time"
)
type Cart struct{
name string
age int
}
type Train struct{
Cart
int
strt time.Time
}
func main(){
var t Train
//正規寫法
t.Cart.name="001"
t.Cart.age=11
//上面的正規寫法可以縮寫成下面的寫法
t.name="001"
t.age=11
t.int=200
fmt.Println(t)
}
匿名字段沖突處理

對於上面的1這里有優先原則:
縮寫形式,如果有兩個結構體中有相同的字段,會優先找本身的字段
對於上面的2,必須要手動的指定某個字段才可以,不然會報錯
方法:
golang中的方法是作用在特定類型的變量上,因此自定義類型,都可以有方法,而不僅僅是struct
定義: func (recevier type ) methodName(參數列表)(返回值列表){}
package main
import(
"fmt"
)
type Student struct{
Name string
Age int
Score int
sex int
}
func (p *Student) init(name string,age int,){
p.Name=name
p.Age=age
fmt.Println(p)
}
func (p Student) get() Student{
return p
}
func main(){
var stu Student
//由於這里傳遞指針才可以,正規寫法應該是下面
(&stu).init("stu",10)
//但是由於go做了優化,只有在結構體方法中才可以用下面的方法
stu.init("stu",10)
stu1:=stu.get()
fmt.Println(stu1)
}
/*
&{stu 10 0 0}
&{stu 10 0 0}
{stu 10 0 0}
*/
方法的調用這里需要注意兩點
1、任何自定義類型都有方法
2、在注意調用的時候的方法,注意指針才改變值
package main
import (
"fmt"
)
type integer int
func (p integer)print(){
fmt.Println(p)
}
//這里由於傳遞的是副本,所以無法改變值
func (p integer)set(b integer){
p=b
}
//這里直接傳遞的指針,所以可以改變
func (p *integer)get(b integer){
*p=b
}
func main(){
var a integer
a=100
a.print()
a.set(1000)
a.print()
//下面是(&a).get的縮寫形式
a.get(1000)
a.print()
}
方法的調用
type A struct{
a int
}
func (this A)test(){
fmt.Println(this.a)
}
var t A
t.test()
上面的this就是下面的t,通過上面方法中的參數this.A就能獲取當前結構體中的實例
方法和函數的區別:
1)函數調用 :function(variable,參數列表)
2)‘方法 variable.function(參數列表)
指針receiver vs值receiver
本質上和函數的值傳遞和地址傳遞是一樣的
方法的訪問控制,通過大小寫控制
繼承
如果一個struct潛逃了另一個匿名結構體,那么這個結構可以直接訪問匿名結構體的方法,從而實現了繼承
如:
package main
import (
"fmt"
"time"
)
type Cart struct{
name string
age int
}
type Train struct{
Cart
int
strt time.Time
age int
}
func main(){
var t Train
//正規寫法
t.Cart.name="001"
t.Cart.age=11
//上面的正規寫法可以縮寫成下面的寫法
t.name="001"
t.age=11
t.int=200
fmt.Println(t)
}
這里的Train繼承了Cart,Cart為父類,然后Train里面有Cart的所有的方法
下面是方法的繼承
package main
import (
"fmt"
)
type Car struct{
weight int
name string
}
func (p *Car) Run(){
fmt.Println("running")
}
type Bike struct{
Car
lunzi int
}
type Train struct{
Car
}
func main(){
var a Bike
a.weight=100
a.name="bike"
a.lunzi=2
fmt.Println(a) //{{100 bike} 2}
a.Run() //running
var b Train
b.weight=1000
b.name="train"
b.Run() //running
}
這里a和b都繼承了Car父類中的Run方法
總結,匿名函數可以繼承字段也可以繼承方法
組合和匿名函數
如果一個struct嵌套了另一個匿名結構體,那么這個結構體可以直接訪問匿名結構體的方法,從而實現了繼承
如果一個struct嵌套了另一個有名結構體,那么這個模式就叫做組合(一個結構體嵌套另一個結構體)
也可以說匿名字段是特殊的組合
package main
import (
"fmt"
)
type Car struct{
weight int
name string
}
func (p *Car) Run(){
fmt.Println("running")
}
type Train struct{
c Car
}
func main(){
var b Train
b.c.weight=1000
b.c.name="train"
b.c.Run() //running
}
如上就是組合
多重繼承
如果一個struct嵌套了多個匿名結構體,那么這個結構可以直接訪問多個匿名結構體的方法,從而實現了多重繼承
如果沖突的話,就需要帶上結構體的名字來訪問
實現String() 這是一個接口
如果一個變量實現了String()這個方法,那么fmt.Println默認會調用變量String()進行輸出
package main
import (
"fmt"
)
type Cart struct{
weight int
name string
}
type Train struct{
Cart
}
func (p *Cart) Run(){
fmt.Println("running")
}
func (p *Train)String() string{
str:=fmt.Sprintf("name=[%s] weight=[%d]",p.name,p.weight)
return str
}
func main(){
var b Train
b.weight=100
b.name="train"
b.Run()
//這個是字符串的接口所以需要格式化才會調用這個接口,這個是指針型的
fmt.Printf("%s",&b)
}
