結構體的定義
結構體是将零個或者多個任意類型的指令變量組合在一起的聚合資料類型。
每個變量都叫做結構體的成員。
其實簡單了解,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:
- 用于定義複雜的資料結構
- 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定義的三種形式
對于上面這個結構體,我們定義的三種方式有:
- 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(){
s := NewStudent("tom",23)
fmt.Println(s.Name)
}
struct中的tag
我們可以為struct中的每個字段,寫上一個tag。這個tag可以通過反射的機制擷取到,最常用的場景就是json序列化和反序列化
下面先寫一個正常我們序列化的例子:
package main
import (
"fmt"
"encoding/json"
)
type Student struct{
Name string
Age int
}
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)
}
注意:這裡有個問題是我們在定義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)
結構體的比較
如果結構體的全部成員都是可以比較的,那麼結構體也是可以比較的,那樣的話,兩個結構體将可以使用==或!=運算符進行比較。相等比較運算符将比較兩個機構體的每個成員
如下面例子:
package main
import (
"fmt"
)
type Point struct{
x int
y int
}
func main(){
p1 := Point{1,2}
p2 :=Point{2,3}
p3 := Point{1,2}
fmt.Println(p1==p2) //false
fmt.Println(p1==p3) //true
}
匿名字段
結構體中字段可以沒有名字
下面是一個簡單的例子:
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
所有的努力都值得期許,每一份夢想都應該灌溉!