1. 程式人生 > >Go基礎之--結構體和方法

Go基礎之--結構體和方法

結構體的定義

結構體是將零個或者多個任意型別的命令變數組合在一起的聚合資料型別。
每個變數都叫做結構體的成員。

其實簡單理解,Go語言的結構體struct和其他語言的類class有相等的地位,但是GO語言放棄了包括繼承在內的大量面向物件的特性,只保留了組合這個基礎的特性。
所有的Go語言型別除了指標型別外,都可以有自己的方法。

先通過一個下的例子理解struct,下面的這個例子用於定義一個student的struct,這個機構體有以下屬性:Name,Age,Sex,Score,分別表示這個學生的名字,年齡,性別和成績。

package main

import (
    "fmt"
)

type Student struct {
    Name string
    Age int
    Sex string
    Score int
}

func testStruct(){
    var stu Student
    stu.Name = "小A"
    stu.Age = 23
    stu.Sex = "man"
    stu.Score = 100
    fmt.Printf("name:%s age:%d score:%d sex:%s\n",stu.Name,stu.Age,stu.Score,stu.Sex)
    fmt.Printf("%+v\n",stu)
    fmt.Printf("%v\n",stu)
    fmt.Printf("%#v\n",stu)
}

func main(){
    testStruct()
}

執行結果如下:

上面的這個例子中演示了定義一個struct,並且為這個struct的屬性賦值,以及獲取這個struct的屬性值

關於Go中的struct:

  1. 用於定義複雜的資料結構
  2. struct裡面可以包含多個欄位(屬性),欄位可以是任意型別
  3. struct型別可以定義方法(注意和函式的區別)
  4. struct型別是值型別
  5. struct型別可以巢狀
  6. Go語言沒有class型別,只有struct型別

定義一個struct

struct宣告:

type 識別符號 struct {
field1 type
field2 type
}

例子:
type Student struct {
Name string
age int
}

struct中欄位的訪問,和其他語言一樣使用“.”點這個符號
var stu Student
stu.Name = "tom"
stu.Age = 18

賦值的時候我們是通過stu.Name同樣的我們訪問的時候也是通過stu.Name

struct定義的三種形式

type Student struct {
Name string
age int
}
對於上面這個結構體,我們定義的三種方式有:

  1. var stu student
  2. var stu *Student = new(Student)
  3. var stu *Student = &Student

上面三種方法中,方法2和方法3的效果是一樣的,返回的都是指向結構體的指標,訪問的方式如下:
stu.Name,stu.Age
(*stu).Name,(*stu).Age而這種方法中可以換成上面的方法直接通過stu.Name訪問
這裡是go替我們做了轉換了,當我們通過stu.Name訪問訪問的時候,go會先判斷stu是值型別還是指標型別如果是指標型別,會替我們改成(*stu).Name

struct中所有欄位的記憶體是連續的

Go 中的struct沒有建構函式,一般通過工廠模式來解決,通過下面例子理解:

package main

import (
    "fmt"
)

type Student struct{
    Name string
    Age int
}

func NewStudent(name string,age int) *Student{
    return &Student {
        Name:name,
        Age:age,
    }
}

func main(){
    s := NewStudent("tom",23)
    fmt.Println(s.Name)
}

struct中的tag

我們可以為struct中的每個欄位,寫上一個tag。這個tag可以通過反射的機制獲取到,最常用的場景就是json序列化和反序列化

下面先寫一個正常我們序列化的例子:

package main

import (
    "fmt"
)

type Student struct{
    Name string
    Age int
}

func NewStudent(name string,age int) *Student{
    return &Student {
        Name:name,
        Age:age,
    }
}

func main(){
    s := NewStudent("tom",23)
    fmt.Println(s.Name)
}

執行結果如下:

注意:這裡有個問題是我們在定義struct中的欄位的時候如:Name,Age都是首字母大寫的,這樣你json序列化的時候才能訪問到,如果是小寫的,json包則無法訪問到,所以就像上述的結果一樣,序列化的結果也是首字母大寫的,但是我就是想要小寫怎麼辦?這裡就用到了tag,將上述的程式碼更改為如下,序列化的結果就是小寫的了:

package main

import (
    "fmt"
    "encoding/json"
)

type Student struct{
    Name string `json:"name"`
    Age int `json:"age"`
}

func main(){
    var stu Student
    stu.Name = "tom"
    stu.Age = 23

    data,err := json.Marshal(stu)
    if err != nil{
        fmt.Printf("json marshal fail fail error:%v",err)
        return
    }
    fmt.Printf("json data:%s\n",data)
}

這裡多說一個小知識就是如果我們想要把json後的資料反序列化到struct,其實方法也很簡單,只需要在上述的程式碼後面新增如下:

var stu2 Student
err = json.Unmarshal(data,&stu2)
if err != nil{
    fmt.Printf("json unmarshal fail fail error:%v",err)
    return
}
fmt.Printf("%+v\n",stu2)

結構體的比較

如果結構體的全部成員都是可以比較的,那麼結構體也是可以比較的,那樣的話,兩個結構體將可以使用==或!=運算子進行比較。相等比較運算子將比較兩個機構體的每個成員
如下面例子:

var stu2 Student
err = json.Unmarshal(data,&stu2)
if err != nil{
    fmt.Printf("json unmarshal fail fail error:%v",err)
    return
}
fmt.Printf("%+v\n",stu2)

匿名欄位

結構體中欄位可以沒有名字
下面是一個簡單的例子:

package main

import (
    "fmt"
)


type Student struct{
    Name string
    Age int
    int
}

func main(){
    var s Student
    s.Name = "tom"
    s.Age = 23
    s.int = 100
    fmt.Printf("%+v\n",s)
}

可能上面的這裡例子看了之後感覺貌似也沒啥用,其實,匿名欄位的用處可能更多就是另外一個功能(其他語言叫繼承),例子如下:

package main

import (
    "fmt"
)

type People struct{
    Name string
    Age int
}

type Student struct{
    People
    Score int
}

func main(){
    var s Student
    /*
    s.People.Name = "tome"
    s.People.Age = 23
    */
    //上面註釋的用法可以簡寫為下面的方法
    s.Name = "tom"
    s.Age = 23

    s.Score = 100
    fmt.Printf("%+v\n",s)
}

注意:關於欄位衝突的問題,我們在People中定義了一個Name欄位,在Student中再次定義Name,這個時候,我們通過s.Name獲取的就是Student定義的Name欄位

方法

首先強調一下:go中任何自定義型別都可以有方法,不僅僅是struct
注意除了:指標和interface

通過下面簡單例子理解:

package main

import (
    "fmt"
)

//這裡是我們普通定義的一個函式add
func add(a,b int) int {
    return a+b
}

type Int int

//這裡是對Int這個自定義型別定義了一個方法add
func (i Int) add(a,b int) int{
    return a+b
}
//如果想要把計算的結果賦值給i
func(j *Int) add2(a,b int){
    *j = Int(a+b)
    return
}

func main(){
    c := add(100,200)
    fmt.Println(c)
    var b Int
    res := b.add(10,100)
    fmt.Println(res)

    var sum Int
    sum.add2(20,20)
    fmt.Println(sum)

}

方法的定義:

func(receiver type)methodName(引數列表)(返回值列表){

}

下面是給一個結構體struct定義一個方法

package main

import (
    "fmt"
)


type Student struct{
    Name string
    Age int
}

func (stu *Student)Set(name string,age int){
    stu.Name = name
    stu.Age = age
}

func main(){
    var s Student
    s.Set("tome",23)
    fmt.Println(s)
}

注意:方法的訪問控制也是通過大小寫控制的

在上面這個例子中需要注意一個地方func (stu *Student)Set(name string,age int)這裡使用的是(stu *Student)而不是(stu Student)這裡其實是基於指標物件的方法

基於指標物件的方法

當呼叫一個函式時,會對其每個引數值進行拷貝,如果一個函式需要更新一個變數,或者函式的其中一個引數是在太大,我們希望能夠避免進行這種預設的拷貝,這種情況下我們就需要用到指標了,所以在上一個程式碼例子中那樣我們需要func (stu *Student)Set(name string,age int)來宣告一個方法

這裡有一個程式碼例子:

package main

import (
    "fmt"
)


type Point struct{
    X float64
    Y float64
}

func (p *Point) ScaleBy(factor float64){
    p.X *= factor
    p.Y *= factor
}

func main(){
    //兩種方法
    //方法1
    r := &Point{1,2}
    r.ScaleBy(2)
    fmt.Println(*r)

    //方法2
    p := Point{1,2}
    pptr := &p
    pptr.ScaleBy(2)
    fmt.Println(p)

    //方法3
     p2 := Point{1,2}
     (&p2).ScaleBy(2)
     fmt.Println(p2)

     //相對來說方法2和方法3有點笨拙
     //方法4,go語言這裡會自己判斷p是一個Point型別的變數,
     //並且其方法需要一個Point指標作為指標接收器,直接可以用下面簡單的方法
     p3 := Point{1,2}
     p3.ScaleBy(2)
     fmt.Println(p3)

}

上面例子中最後一種方法,編譯器會隱式的幫我們用&p的方法去呼叫ScaleBy這個方法
當然這種簡寫方法只適用於變數,包括struct裡面的欄位,如:p.X