专业网站制作地址,wordpress 在线教学,东莞房地产网站建设,专业的南京网站建设Go-知识struct 1. struct 的定义1.1 定义字段1.2 定义方法 2. struct的复用3. 方法受体4. 字段标签4.1 Tag是Struct的一部分4.2 Tag 的约定4.3 Tag 的获取 githupio地址#xff1a;https://a18792721831.github.io/
1. struct 的定义
Go 语言的struct与Java中的class类似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,tageValue2name string tagKey:tagValue1,tageValue2age 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,tageValue2name string tagKey:tagValue1,tageValue2 tagKey1:tagValue1,tageValue2age 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只能操作已有的字段和函数不能动态的增加或者减少字段和函数。 除了使用第三方库借助上述语法自己也可以定义需要的操作比如判空。