Go-知識struct
- 1. struct 的定義
- 1.1 定義字段
- 1.2 定義方法
- 2. struct的復用
- 3. 方法受體
- 4. 字段標簽
- 4.1 Tag是Struct的一部分
- 4.2 Tag 的約定
- 4.3 Tag 的獲取
githupio地址:https://a18792721831.github.io/
1. struct 的定義
Go 語言的struct與Java中的class類似,可以定義字段和方法,但是不能繼承。
1.1 定義字段
struct定義字段非常簡單,只需要將字段寫在struct的結構中,比如:
type tes struct {a intName string
}
需要注意的是,在Go里面,訪問權限是通過name的大小寫指定的,小寫表示包內可見,如果是大寫則表示包外可見。
所以上面的struct如果用Java翻譯:
class tes {private int a;public String Name;
}
同樣的,如果創建的struct想讓包外可見,那么必須是大寫開頭。
type Tes struct{id intName string
}
1.2 定義方法
在Go里面一般不會區分函數和方法,或者更好理解的話,可以認為方法是受限的函數,限制了函數調用者,那么就是方法。
定義方法:
func (a tes) test() {fmt.Println(a.id)
}
同樣的,上述方法包內可見。
func (a tes) Test() {fmt.Println(a.Name)
}
上述方法雖然包外可見,但是沒有意義,因為tes
是包內可見,如果沒有對外提供函數,那么是沒有意義的。
如果想保證安全,可以使用包內可見的struct
配合包內字段加包外方法,另外額外提供包外可見的struct
獲取函數,實現類似于Java的可見性控制。
package testype person struct {id intname stringage int
}func (this *person) GetId() int {return this.id
}func (this *person) GetName() string {return this.name
}func (this *person) GetAge() int {return this.age
}func (this *person) SetId(id int) {this.id = id
}func (this *person) SetName(name string) {this.name = name
}func (this *person) SetAge(age int) {this.age = age
}func NewPerson() *person {return &person{}
}func NewPersonWithId(id int) *person {return &person{id: id}
}func NewPersonWithName(name string) *person {return &person{name: name}
}
因為Go不支持函數重載,所以需要用不同的函數名字區分。
上述代碼實際上就是一個基本的JavaBean的實現。
但是實際使用上,基本上對外可見的字段都是直接用.
來訪問和賦值的。
在使用上,struct是否對外可見,則和編碼風格相關,業務系統一般不會考慮封閉性,基本上struct都是可見的;而第三方包等為了保證安全性,則會將部分struct設置為包內可見,在結合interface
來保證擴展性。
2. struct的復用
在其他編程語言中,使用繼承或組合實現代碼的復用。
而Go語言中沒有繼承,只能使用組合實現復用。
比較特別的是,在Go語言中,組合復用的struct可以認為拷貝了被組合的struct的字段到需要的struct中。
type Man struct {personsex string
}func (this *Man) ToString() string {return fmt.Sprintf("id=%d, name=%s, age=%d, sex=%s\n", this.person.id, this.person.name, this.person.age, this.sex)
}func (this *Man) GetToString() string {return fmt.Sprintf("id=%d, name=%s, age=%d, sex=%s\n", this.id, this.name, this.age, this.sex)
}
在struct中組合其他struct,相當于是創建了一個同名的隱式字段,在使用的時候,可以指明隱式字段,也可以不指明隱式字段。
想一想,在Java中,如果當前class和父class中有同名的字段,那么在使用父類中的字段時,需要使用super
指明使用的是父類中的字段。
同理的,當struct中有一個id,那么在使用的時候,可以使用隱式字段指明:
type Man struct {id intpersonsex string
}func (this *Man) GetSuperId() int {return this.person.id
}func (this *Man) GetManId() int {return this.id
}
隱式字段如果顯示的定義了,那么就無法像使用自己的字段一樣使用內嵌字段了:
type Woman struct {person personsex string
}func (this *Woman) ToString() string {return fmt.Sprintf("id=%d, name=%s, age=%d, sex=%s\n", this.person.id, this.person.name, this.person.age, this.sex)
}func (this *Woman) GetString() string {return fmt.Sprintf("id=%d, name=%s, age=%d, sex=%s\n", this.id)
}
如果還像使用自己的字段一樣使用內嵌字段,就會找不到
3. 方法受體
方法本質上還是函數,只是限制了函數的調用者。
那么你有沒有好奇,為什么上面的例子中,方法的調用者都是指針類型而不是struct類型,這有什么區別?
type person struct {id intname stringage int
}func (this *person) SetIdPtr(id int) {this.id = id
}func (this person) SetId(id int) {this.id = id
}func (this *person) GetIdPtr() int {return this.id
}func (this person) GetId() int {return this.id
}func TestPerson(t *testing.T) {p := person{id: 1,name: "zhangsan",age: 10,}fmt.Printf("%+v\n", p)p.SetId(2)fmt.Printf("%+v\n", p)p.SetIdPtr(3)fmt.Printf("%+v\n", p)p.id = 4fmt.Printf("%+v\n", p.GetIdPtr())p.id = 5fmt.Printf("%+v\n", p.GetId())
}
沒錯,區別在于是否會影響原數據。
函數調用過程中,會將函數壓入調用棧,在入棧過程中,會對函數參數進行拷貝。
在Java中,如果是基本類型參數,那么拷貝值,如果是復雜類型參數,那么拷貝指針。
在Go語言中,可以由程序員指定,如果方法調用者是指針,那么表示方法可以修改外部數據,如果方法調用者是struct,那么不會修改外部數據。
如果是數據的讀取,那么不管是指針還是struct,都能讀取到數據。
在換一個角度看,方法的調用者,在方法調用的時候,也進行了參數拷貝,所以可以認為方法調用者就是一個特殊的參數。
type person struct {id intname stringage int
}func (this person) GetNameS() string {return this.name
}func GetName(this *person) string {return this.name
}func TestPerson(t *testing.T) {p := person{id: 1,name: "zhangsan",age: 10,}fmt.Println(p.GetNameS())fmt.Println(GetName(&p))
}
運行都能獲取到結果
只是無法使用.
的方式觸發了。
4. 字段標簽
在Go語言的struct的字段后面,可以使用標簽。
type person struct {id int `tagKey:"tagValue1,tageValue2"`name string `tagKey:"tagValue1,tageValue2"`age int `tagKey:"tagValue1,tageValue2"`
}
4.1 Tag是Struct的一部分
Tag用于標識字段的額外屬性,類似注釋。標準庫reflect
包中提供了操作Tag的方法。
// A StructField describes a single field in a struct.
type StructField struct {// Name is the field name.Name string// PkgPath is the package path that qualifies a lower case (unexported)// field name. It is empty for upper case (exported) field names.// See https://golang.org/ref/spec#Uniqueness_of_identifiersPkgPath stringType Type // field typeTag StructTag // field tag stringOffset uintptr // offset within struct, in bytesIndex []int // index sequence for Type.FieldByIndexAnonymous bool // is an embedded field
}
而StructTag
其實就是字符串:
4.2 Tag 的約定
Tag本質上是個字符串,那么任何字符串都是合法的,但是在實際使用中,有一個約定:key:"value.."
格式,如果有多個,中間用空格區分。
type person struct {id int `tagKey:"tagValue1,tageValue2" tagKey1:"tagValue1,tageValue2"`name string `tagKey:"tagValue1,tageValue2" tagKey1:"tagValue1,tageValue2"`age int `tagKey:"tagValue1,tageValue2" tagKey1:"tagValue1,tageValue2"`
}
key: 必須是非空字符串,字符串不能包含控制字符、空格、引號、冒號。
value: 以雙引號標記的字符串。
key和value之間使用冒號分割,冒號前后不能有空格。
多個key-value之間用空格分割。
key一般用于表示用途,value一般表示控制指令。
比如:
json:"name,omitempty"
表示json
轉換的時候,使用name
作為名字,如果字段值為空,那么json
轉換該字段的時候忽略。
4.3 Tag 的獲取
在reflect
的StructField
提供了Get
和Lookup
方法:
比如獲取上面person
的Tag
func TestPerson(t *testing.T) {p := person{id: 1,name: "zhangsan",age: 10,}st := reflect.TypeOf(p)stf, ok := st.FieldByName("id")if !ok {fmt.Println("not found")return}nameTag := stf.Tagfmt.Printf("tagKey=%s\n", nameTag.Get("tagKey"))tagValue, ok := nameTag.Lookup("tagKey1")if !ok {fmt.Println("not found")return}fmt.Printf("tagKey1=%s\n", tagValue)
}
在Java中有一個非常強大,也經常使用的插件lombok
,通過在class的字段上添加注解,進而實現一些控制方法。
區別在于,lombok
是在編譯時,通過操作字節碼,實現方法的寫入,而Tag是在運行時,通過反射賦值。
所以Tag只能操作已有的字段和函數,不能動態的增加或者減少字段和函數。
除了使用第三方庫,借助上述語法,自己也可以定義需要的操作比如判空。