Go(5 [struct | tag | 方法 | 繼承 ])

Go struct

  1. 用來自定義複雜數據結構

  2. struct裏面可以包含多個字段(屬性),字段可以是任意類型

  3. struct類型可以定義方法,注意和函數的區別

  4. struct是值類型,也就是,賦值的時候,就是copy一份,不會修改原來的值, 

  5. struct類型可以嵌套

  6. Go中沒有class類型,只有struct類型

  7. make ⽤來分配mapslicechannel類型的內存,new用來分配值類型的內存

Struct定義:

聲明:

type 標識符 struct{
    
    filed1 int
    filed2 string
}

訪問:和python一樣,使用點的方法來訪問

定義一個 Student struct

type Test struct{
   A int
   B int
}

type Student struct{
   //定義類型
   Age int
   Name string
   Sex string
   Grader string
   Score int

   test Int
   //Test 是struct類型
   sitrstruct Test
   //指針類型
   c *int
}
func testStruct()  {
var s Student
//因爲這裏s已經定義了。所以不需要:=這種方式了
s.Age =18
s.Name = "zcq"
s.Score = 100
s.Sex = "main"
s.Grader = "36"
//訪問
s.sitrstruct.A = 100
//當struct內部有指針類型的時候,默認是空,直接賦值會報錯,需要new創建
s.c =new(int)
//指針賦值
*(s.c) = 100

//打印結果
fmt.Printf("name:%s,age:%d,,c:%d\n",s.Name,s.Age,*(s.c))

fmt.Printf("%+v\n",s)
//值類型, 就是改變內容的值,就是copy一份,不會影響原來變量的值

s.Name = "rch"


fmt.Printf("name:%s\n",s.Name)

//s裏面的地址賦值給s1,那s1和s是指向同一個內存地址,所以用s1來修改指針,那s也會變

s1:=s
*(s1.c) = 200
fmt.Printf("name:%s,age:%d,,c:%d\n",s.Name,s.Age,*(s.c))

}

Go Struct 三種定義方式:

  1. var stu Student

    1. 這種方式操作起來代碼量多

  2. var stu *Student = new(Student)

    1. 使用new的方式創建,這種方式,stu就是一個指針類型,

    2. stu .Score = 100
      //s2,s3的score值都改變了
      s3:=stu
      1. //需要注意的是:*(s3).Score = 200 是標準訪問形式,但是當你s3.Score  這樣訪問也是的, 那是因爲go會檢測s3是值類型,還是指針類型,如果是值類型,那go就幫你轉換爲*(s3) 這樣的形式


  3. var stu *Student = &Student{}

    1. 分配內存空間,這種方式可以如果需要初始化一些值,可以直接寫在{} 中

其中方法2,3 返回的都是指向結構體的指針,訪問形式:

    stu.Name、 stu.Age和stu.Score或者 (*stu).Name、 (*stu).Age

自定義類型:結構體是用戶單獨定義的類型,不能和其他類型進⾏強制轉換

type StudentA struct{
   Number int
}

type Stu StudentA

func main()  {
   var a StudentA
   a.Number = 20
   //需要使用自定義類型強制轉換
   var b Stu
   b=Stu(a)
   fmt.Printf("%v",b)

}

Struct內存佈局

內存佈局:struct中的所有字段在內存是連續的,

代碼:


package main

import "fmt"

//內存佈局是連續的
type Point struct{
   x int
   y int
}
type Rect struct{
   //p1,p2內存地址連續的
   p1 Point
   p2 Point
}

type RectA struct{
   //p1,p2 指向的是一個內存地址
   //那這2個的內存地址不是連續的
   p1 *Point
   p2 *Point

}

func main()  {
   //r1不用分配內存,因爲定義完了後,內存就已經分配好了,沒有指針類型字段
   var r1 Rect
   //r2裏面是2個指針類型,所以需要分配內存,就需要new
   var r2 RectA
   r2.p1 = new(Point)
   r2.p2 = new(Point)


   //r1的內存佈局
   fmt.Printf("ADD:%p\n",&r1.p1.x)
   fmt.Printf("ADD:%p\n",&r1.p1.y)
   fmt.Printf("ADD:%p\n",&r1.p2.x)
   fmt.Printf("ADD:%p\n",&r1.p2.y)
   fmt.Println()

   //r2的內存佈局
   fmt.Printf("ADD:%p\n",&r2.p1.x)
   fmt.Printf("ADD:%p\n",&r2.p1.y)
   fmt.Printf("ADD:%p\n",&r2.p2.x)
   fmt.Printf("ADD:%p\n",&r2.p2.y)
}


Go struct 構造函數

go struct 沒有構造函數,那就需要自己實現,一般可以使用工廠函數,來解決這個問題

type School struct{
   Name string
   Addr string
}

func NewSchool(name,addr string) (*School)  {
   //& 內存地址, new也是一個意思
   //實例化struct
   return &School{
      Name:name,
      Addr:addr,
   }
   //或者可以用new
   //第二種寫法
   //p:=new(School)
   //p.Name = name
   //p.Age = age
   //return p
}
func main()  {
   s:=School{Name:"aa",Addr:"assaas"}
   fmt.Printf(s)
}

Struct 中的方法

  1. 訪問控制:通過大小寫控制,小寫不能被其他包外面調用

  2. 方法可以作用在特定類型上

  3. 函數可以隨意調用

  4. 函數傳參是副本的調用

    

給struct添加方法

實例代碼:

type People struct{
   Name string
   Score int
   all int
   string
}

type Student struct{
   Name string
   Age int
   //使用了匿名字段,實現了繼承
   People

   all int
}
//給people這個struct添加了 Format方法
func (p *People) Format() string {
   return fmt.Sprintf("name=%s,age=%d",p.Name,p.Score)
}

//給Student這個struct添加了 方法
func (p *Student) Format() string {
   return fmt.Sprintf("name=%s,age=%d",p.Name,p.Score)
}

func mian(){
    var s Student
    //訪問父類中的方法
    ret :=s.People.Format()
    fmt.Printf(ret)
    
    var d People
    //訪問d中的方法
    res := d.Format()
    fmt.Printf("11111",res)
    
}

三種函數接收方式

  1. 定義函數:

    1. func Add(a, b int) int {
         return a + b
      }
      func testInt()  {
         c := Add(100, 200)
         fmt.Println(c)
      }


  2. 值類型接受方式:

    1. type Int int
      //這種寫法,前面(i Int) 是接受者參數
      //在函數的前面加了自定義類型,和變量
      //那Bdd的接受者就是i了, 那Bdd也就是Int類型的一個方法了,
      func (i Int) Bdd(a, b int) int {
         return a + b
      }
      func main(){  
      var a Int
      d := a.Bdd(100, 200)
      fmt.Println(d)
      }


  3. 指針類型接收方式:

    1. func (i *Int) Cdd(a, b int) {
         //這裏需要做強制轉換,因爲a,b是小寫int, 但i是大寫int, 那賦值就需要強制轉換
         *i = Int(a + b)
         //這樣a+b的結果就存在i這個變量裏面了
         return
      }
      var e Int
      //傳遞給Cdd的只是e的拷貝,如果想修改e的值,那傳遞的時候,需要傳遞e的指針地址
      e.Cdd(200, 300) //&(e).Cdd(200,300) 也是一樣的, 因爲e調用的時候,發現要傳一個地址,那go會自動的轉換成&(e),你可以不用寫&,因爲go幫你做了
      fmt.Println(e)

    

在來一列struct 添加方法栗子

栗子1:

type Student struct{
   Name string
   Age int
}

//struct也是值類型,所以當要修改值,傳遞給函數的時候,就需要傳遞指針類型
func (s *Student) SiteSet(name string,age int){
   s.Name = name
   s.Age = age
}
func teststrcut()  {
   //這樣就給struct定義了方法
   var s Student
   s.SiteSet("abc",20)
   fmt.Println(s)
}

栗子2:

func NewSchool(name,addr string) (*School)  {
   return &School{
      Name:name,
      Addr:addr,
   }

func (s *School) getaddr() string  {
   return s.Addr
}
func (s *School) Getaddr() string  {
   return s.Addr
}

s :=model.NewSchool("北京","海淀")
//調用了實例中的方法
fmt.Printf("school_name:%s",s.Getname())

fmt.Printf("school_name:%s",s.Getaddr())

Struct 中的tag

我們可以爲struct中的每個字段,寫上⼀個tag。這個tag可以通過反射的機制獲取到,最常⽤的場景就是json序列化和反序列化

如下代碼中,Zcq做了tag標記,那json就可以通過反射方式匹配值,json會序列化結構體裏面的tag,

  1. key:json 後寫的值

  2. value: 賦值的參數


在struct中 首字母如果是小寫,那就是私有的,只能在main包裏面訪問
做了tag,那json序列化後的key 可以自定義,解決了:在特殊情況下,必須用小寫,但是在go裏面小寫命名的值,json訪問不到的情況,完美
type Student struct{
   Name string
   Age int
   Sex string
   ceshi string
   //做了tag標記, 還可以寫多個值, 往後會學到怎麼取這個值
   Zcq string `json:"rch";db:"name"`
}
func main(){
    
    s.Zcq = "zcq"
}
>>>
{"Name":"ZCQ","Age":12,"Sex":"mem","rch":"zcq"}


Json序列化/反序列化

  1. 序列化

    1. //Marshal 序列化
      //data 是一個bytes的切片,
      //Marshl是封裝
      data,err :=json.Marshal(s)
      if err != nil{
         fmt.Printf("json.Marshal---error",err)
         return
      }


  2. 反序列化

    1. var s1 Student
      //Unmarshal :反序列化
      //需要傳入地址的值,直接去修改值,如果傳入的是s1,那傳入的是一個副本,
      //err這裏不需要:= 了,因爲上面已經聲明瞭
      err =json.Unmarshal(data,&s1)
      if err!=nil{
         fmt.Printf("json.Unmarshal---error",err)
         return
      
      }
      fmt.Printf("s1%#v\n",s1)

匿名字段/繼承

定義:結構體中字段可以沒有名字,即匿名字段,  

注意:不能存在相同的名字

type People struct{
   Name string
   Score int
   all int
   //沒有名字,那就是匿名字段,不能存在相同名字
   string
}

繼承

注意:在繼承中,如果父類和子類都有同一個方法,那麼優先訪問子類中的方法,

type Student struct{
   Name string
   Age int
   //  People 是上面代碼中的struct,  
   //使用了匿名字段,實現了繼承
   People

   //如果都有相同的字段,那默認會先找本身student裏面的字段
   //相同持有相同字段的話,就需要s.People.Name 纔可以訪問到
   all int
}
func test1()  {
   var s Student
   s.Name = "abc"
   s.Age = 100
   s.string = "11"
   ////如果字段沒有名字,就用它的類型,來操作這個字段
   //s.int = 2000
   //s.People.Name = 100
   //繼承中可以直接訪問
   s.Score = 100
   fmt.Printf("%#v\n",s)
}


func testMethod()  {
   var s Student
   //因爲s 繼承了People,而且又給People添加了一個方法,所以可以直接訪問到這個方法
   s.Age = 200
   s.People.Name = "anc"
   //訪問父類中的方法
   ret :=s.People.Format()
   //訪問子類中的方法s.Format()

   fmt.Printf(ret)

}


    


    



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章