結構體的定義
結構體是將零個或者多個任意類型的命令變量組合在一起的聚合數據類型。
每個變量都叫做結構體的成員。
其實簡單理解,Go語言的結構體struct和其他語言的類class有相等的地位,但是GO語言放棄了包括繼承在內的大量面向對象的特性,只保留了組合這個基礎的特性。
所有的Go語言類型除了指針類型外,都可以有自己的方法。
先通過一個下的例子理解struct。
package main import "fmt" type Student struct { Name string Age int Sex string Score int } func main() { var stu Student stu.Name = "zhangsan" stu.Age = 12 stu.Sex = "male" stu.Score = 90 fmt.Printf("name:%s\n age:%d\n sex:%s\n score:%d\n", stu.Name, stu.Age, stu.Sex, stu.Score) fmt.Printf("%v\n", stu) fmt.Printf("%+v\n", stu) fmt.Printf("%#v\n", stu) }
關於Go中的struct:
- 用於定義復雜的數據結構
- struct里面可以包含多個字段(屬性),字段可以是任意類型
- struct類型可以定義方法(注意和函數的區別)
- struct類型是值類型
- struct類型可以嵌套
- 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
}
對於上面這個結構體,我們定義的三種方式有:
- var stu student
- var stu *Student = new(Student)
- 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() { stu := newStudent("tom", 22) fmt.Println(stu) fmt.Println(stu.Name) }
struct中的tag
我們可以為struct中的每個字段,寫上一個tag。這個tag可以通過反射的機制獲取到,最常用的場景就是json序列化和反序列化
下面先寫一個正常我們序列化的例子:
package main import ( "encoding/json" "fmt" ) type Student struct { Name string Age int } func main() { var stu Student stu.Name = "tom" stu.Age = 22 data, err := json.Marshal(stu) if err != nil { fmt.Printf("json marshal fail,fail message is %v\n", err) return } fmt.Printf("json marshal result is %s\n", data) }
運行結果如下:
json marshal result is {"Name":"tom","Age":22}
注意:這里有個問題是我們在定義struct中的字段的時候如:Name,Age都是首字母大寫的,這樣你json序列化的時候才能訪問到,如果是小寫的,json包則無法訪問到,所以就像上述的結果一樣,序列化的結果也是首字母大寫的,但是我就是想要小寫怎么辦?這里就用到了tag,將上述的代碼更改為如下,序列化的結果就是小寫的了:
package main import ( "encoding/json" "fmt" ) type Student struct { Name string `json:"name"` Age int `json:"age"` } func main() { var stu Student stu.Name = "tom" stu.Age = 22 data, err := json.Marshal(stu) if err != nil { fmt.Printf("json marshal fail is %v\n", err) return } fmt.Printf("json marshal result %s\n", data) //反序列化到struct var stu2 Student err = json.Unmarshal(data, &stu2) if err != nil { fmt.Printf("unmarshal fail is %v\n", err) return } fmt.Printf("unmarshal result is %+v\n", stu2) }
可以看到將json反序列化成結構的方式是
json.Unmarsha
結構體的比較
如果結構體的全部成員都是可以比較的,那么結構體也是可以比較的,那樣的話,兩個結構體將可以使用==或!=運算符進行比較。相等比較運算符將比較兩個機構體的每個成員
如下面例子:
package main import "fmt" type Pointer struct { x int y int } func main() { p1 := Pointer{1, 2} p2 := Pointer{2, 3} p3 := Pointer{1, 2} fmt.Printf("p1==p2:%t\n", p1 == p2) fmt.Printf("p1==p3:%t\n", p1 == p3) }
匿名字段
結構體中字段可以沒有名字
下面是一個簡單的例子:
package main import "fmt" type Student struct { Name string Age int int } func main() { var stu Student stu.Name = "tom" stu.Age = 22 stu.int = 111 fmt.Printf("%+v\n", stu) }
可能上面的這里例子看了之后感覺貌似也沒啥用,其實,匿名字段的用處可能更多就是另外一個功能(其他語言叫繼承),例子如下:
package main import "fmt" type People struct { Name string Age int } type Student struct { People Score int } func main() { var stu Student // stu.People.Name = "tom"//是下面的簡寫 // stu.People.Age = 22 stu.Name = "tom" stu.Age = 22 stu.Score = 100 fmt.Printf("%+v\n", stu) }
注意:關於字段沖突的問題,我們在People中定義了一個Name字段,在Student中再次定義Name,這個時候,我們通過s.Name獲取的就是Student定義的Name字段
如下:
package main import "fmt" type People struct { Name string Age int Sex string } type Student struct { //字段沖突,重新定義一個Sex覆蓋就行 Sex int //放在People的上邊或下邊都行 People } func main() { var stu Student stu.Name = "tom" stu.Age = 22 stu.Sex = 1 fmt.Printf("%+v\n", stu) }
方法
首先強調一下: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 Pointer struct { X float64 Y float64 } func (p *Pointer) ScaleBy(f float64) { p.X *= f p.Y *= f } func main() { //method 1 p1 := &Pointer{2, 3} p1.ScaleBy(3) fmt.Printf("p1:%f\n", *p1) //method 2 p2 := Pointer{2, 3} p2r2 := &p2 p2r2.ScaleBy(3) fmt.Printf("p2:%f\n", p2) //method 3 p3 := Pointer{2, 3} (&p3).ScaleBy(3) fmt.Printf("p3:%f\n", p3) //method 4 //相對來說方法2和方法3有點笨拙 //方法4,go語言這里會自己判斷p是一個Point類型的變量, //並且其方法需要一個Point指針作為指針接收器,直接可以用下面簡單的方法 p4 := Pointer{2, 3} p4.ScaleBy(3) fmt.Printf("p4:%f\n", p4) }
上面例子中最后一種方法,編譯器會隱式的幫我們用&p的方法去調用ScaleBy這個方法
當然這種簡寫方法只適用於變量,包括struct里面的字段,如:p.X
轉自https://www.cnblogs.com/zhaof/p/8244542.html