区块链技术博客
www.b2bchain.cn

基础篇·第一章[13]·结构体和方法求职学习资料

本文介绍了基础篇·第一章[13]·结构体和方法求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

[TOC]

1. 定义

结构体,是GO语言中等效于“类”的数据结构。
基于结构体,GO语言同样可以实现“类”的“封装”、“继承”、“多态”,且更加简洁灵活。
结构体,是GO提供的可自定义内部字段的基本类型,且是值类型(仅声明时零值为各字段的零值),这点和数组相似。

通过关键字type ... struct
注意“内存对齐”可参考阅读恰到好处的内存对齐

type 结构体名 struct {     字段名 字段类型     字段名 字段类型     … } 如: type student struct {     name, addr string     age int     isMarried bool }  var stu student fmt.Printf("%vn", stu)

2. 初始化

2.1 先声明再实例化

func main() {     var stu1  student     stu1.name = "stu1"     stu1.addr = "addr1"     stu1.age = 21     stu1.isMarried = false      fmt.Printf("%#vn", stu1) }

2.1 声明即初始化

2.1.1 按字段值顺序

这种方式,必须严格按照字段顺序赋值,否则赋值就可能错位或者出错了。

func main() {     stu1 := student{ "stu1", "addr1", 21, false}     fmt.Printf("%#vn", stu1) }

2.1.1 按”KV”方式

这种方式类似于map的键值方式,字段赋值的先后顺序就相对自由了。

func main() {     stu1 := student{         addr: "addr1",         name: "stu1",         isMarried: false,                 age: 21,     }      fmt.Printf("%#vn", stu1)

3. 结构体指针

结构体是值类型,所以在传递时并不高效。通常我们就会使用结构体的指针进行传递处理。
方式1:通过关键字new可以得到结构体的指针。
方式2:通过&取地址符也可以。

stu5 := new(student) fmt.Printf("%#vn", stu5)  stu6 := &student{} fmt.Printf("%#vn", stu6)

4. 匿名结构体

场景:
1)组织分散的数据来进行序列化操作;

aStu := struct{     Name string `json:"userName"`     Age int `json:"userAge"`     addr string `json:"userAddr"`     IsMarried bool `json:"isMarried"` }{     name,age,addr,false, } aStuBytes, err:=json.Marshal(aStu) if err!=nil{     fmt.Println("json.Marshal error:", err)     return } fmt.Printf("to rsp json str:n %vn", string(aStuBytes))

2)缩小操作数据的范围;

type student struct {     name, addr string     age int     isMarried bool }  //假设DB接口返回的是stu结构体 stu := student{     addr: "addr1",     name: "stu1",     isMarried: false,     age: 21, } //实际要应答的只需要其中部分数据 aStu := struct {     Name string `json:"userName"`     IsMarried bool `json:"isMarried"` }{     stu.name, stu.isMarried, } aStuBytes, err:=json.Marshal(aStu) if err!=nil{     fmt.Println("json.Marshal error:", err)     return } fmt.Printf("to rsp json str:n %vn", string(aStuBytes))

5. 方法和接受者

GO语言中没有“类”的概念,也就没有“类方法”的概念。
但是,GO提供了更加灵活高效的“接收者”概念。
“接收者”要和具体的函数“绑定”,这样的函数就叫“方法”。
“接收者”是某种具体的类型或类型指针。
注意事项: 非本地类型不能定义方法,即不能给别的包的类型定义方法。

type Student struct {     Name string     Addr string     Age int } // 类似于“构造函数” func NewStudent(name, addr string, age int) *Student {     return &Student{         name,         addr,         age,     } } func (s *Student) SetAge(age int) {     s.Age = age } student := NewStudent("student", "addr", 20) fmt.Printf("%#vn", student) student.SetAge(30) fmt.Printf("%#vn", student)

5.1 类型和类型指针接收者

如何选择方法的接收者是用类型本身还是类型的指针呢?
可从几下几个方面进行维度参考:
1)安全:方法是否允许或是否需要修改接收者源值?如果需要,接收器必须是一个指针。
2)效率:如果接收者本身是个很大的数据结构,那么显然也是需要考虑保证安全之下的性能开销的(拷贝代价比较大)。
3)风格:如果类型的某些方法必须有指针接收器,那么其余的方法也应该有指针接收器,保持一致性。
另外对于基本类型、切片和小结构等类型,值接收器是非常廉价的。
因此除非方法的语义需要指针,那么值接收器是最高效和清晰的。
在 GC 方面,也不需要过度关注。

6. 空结构体

形式:struct{}
空结构体,不占用内存空间。这样可以在一定程度上减少内存使用,特别是在消息管道开辟数量到达一定量级之后。
这就涉及到在 Go 语言中 ”宽度“ 的概念,宽度描述了一个类型的实例所占用的存储空间的字节数。
宽度是一个类型的属性。在 Go 语言中的每个值都有一个类型,值的宽度由其类型定义,并且总是 8 bits 的倍数。在 Go 语言中我们可以借助 unsafe.Sizeof 方法,来获取:

var v struct{} fmt.Println(unsafe.Sizeof(v))  // 0

6.1 实现方法“分组”结构化

在该场景下,使用空结构体从多维度来考量是最合适的,易拓展,省空间,最结构化。

//公共方法的"组合" 趋于接口形式 如: type T bool func (s *T) Call() 即使bool的开销已经比较小,但是仍然还会占据一点点空间。 这种时候换空结构体上场,这样也便于未来针对该类型进行公共字段等的增加。如下: type PubComm struct{} func (s *PubComm)  Conn() {     fmt.Println("connect ...") } func (s *PubComm)  Send() {     fmt.Println("send ...") } func (s *PubComm)  Recv() {     fmt.Println("recv ...") }  func main() {     var pubcomm PubComm     pubcomm.Conn()     pubcomm.Send()     pubcomm.Recv() }

6.2 实现”Set”类型

在 Go 语言的标准库中并没有提供集合(Set)的相关实现,因此一般在代码中我们图方便,会直接用 map 来替代。但有个问题,就是集合类型的使用,只需要用到 key(键),不需要 value(值)。
这就是空结构体大展身手的场景了

type MySet map[string]struct{} func (ms *MySet) Append(key string) {     (*ms)[key] = struct{}{} } func (ms *MySet) Delete(key string) {     delete(*ms, key) } func (ms *MySet) Exist(key string) bool {     _, ok := (*ms)[key]     return  ok } func main() {         mySet := MySet{}     fmt.Printf("%vn", unsafe.Sizeof(mySet))     mySet.Append("k1")     mySet.Append("k2")     mySet.Append("k3")     mySet.Append("k4")     fmt.Printf("%#v n", mySet)     mySet.Delete("k2")     fmt.Printf("%#v n", mySet)     fmt.Printf("%#v n", mySet.Exist("k4")) } 空结构体作为占位符,不会额外增加不必要的内存开销,很方便的就是解决了。

6.3 实现空通道协同或状态控制。

在 Go channel 的使用场景中,常常会遇到通知型 channel,其不需要发送任何数据,只是用于协调 Goroutine 的运行,用于流转各类状态或是控制并发情况。

如下: syncCh := make(chan struct{}) go func() {     fmt.Println("子进程处理中...")     time.Sleep(time.Second*3)     close(syncCh) }()  <-syncCh fmt.Println("父进程收到子进程退出信号")

7. 匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
本质:匿名字段并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
基础篇·第一章[13]·结构体和方法

type Student struct {     Name string     Addr string     Age int     float32 //匿名字段 }  // 类似于“构造函数” func NewStudent(name, addr string, age int) *Student {     return &Student{         name,         addr,         age,         0.0,     } }  func (s *Student) SetAge(age int) {     s.Age = age } student := NewStudent("student", "addr", 20) fmt.Printf("%#vn", student) student.SetAge(30) fmt.Printf("%#vn", student)  输出结果: &main.Student{Name:"student", Addr:"addr", Age:20, float32:0} &main.Student{Name:"student", Addr:"addr", Age:30, float32:0}

8. 嵌套结构体

嵌套,即一个结构体中包含另一个结构体或结构体指针;这种嵌套特性,可以类似理解为其他语言的“继承”。
嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。
当访问结构体成员时会先在结构体中查找该字段,找不到再去嵌套的匿名字段中查找。

// Factor 厂商 type Factor struct {     Name string `json:"name"`     Addr string `json:"addr"`     CrtTime time.Time `json:"crt_time"` } // Model 型号 type Model struct {     Name string `json:"name"`     CrtTime time.Time `json:"crt_time"` } // Car 车 type Car struct {     *Factor `json:"factor"`//通过嵌套匿名结构体实现继承     *Model `json:"model"`//通过嵌套匿名结构体实现继承 } BmwX5 := Car{     &Factor{Name: "宝马", Addr: "北京", CrtTime: time.Now()},     &Model{Name: "X5", CrtTime: time.Now()}, } fmt.Printf("%#vn", BmwX5.Addr) //本身没有Addr字段,继续找到嵌套的匿名结构体中。 fmt.Printf("%#v %#vn", BmwX5.Factor.Name, BmwX5.Model.Name)//有同名字段时需具体指定所属结构体  carBytes, err:=json.Marshal(&BmwX5) if err!=nil{     fmt.Printf("json.Marshal error", err)     return } fmt.Printf("json str: %vn", string(carBytes))

9. 结构体字段的可见性

结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

aStu := struct{     Name string `json:"userName"`     Age int `json:"userAge"`     addr string `json:"userAddr"` //这个字段json无法解析,小写不可见     IsMarried bool `json:"isMarried"` }{     name,age,addr,false, } aStuBytes, err:=json.Marshal(aStu) if err!=nil{     fmt.Println("json.Marshal error:", err)     return } fmt.Printf("to rsp json str:n %vn", string(aStuBytes))

基础篇·第一章[13]·结构体和方法
注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

10. 结构体引用类型字段的使用注意事项

引用类型字段的赋值和修改需要注意相互影响。
建议使用时使用内置copy函数处理接收的指针参数。

// 有坑的用法 func (s *Student) SetHobby(hobbies []string) {     s.Hobby = hobbies }  // NewStudent 类似于“构造函数” func NewStudent(name, addr string, age int) *Student {     return &Student{         name,         addr,         age,         0.0,         nil,     } } student := NewStudent("student", "addr", 20) fmt.Printf("%#vn", student)  hobbies := []string{"football", "basketball", "music"} student.SetHobby(hobbies) fmt.Printf("%#vn", student)  hobbies[1] = "movie" fmt.Printf("%#vn", student) //因为hobbies的变动而导致student信息的改变  //正确做法 func (s *Student) SetHobbies(hobbies []string) {     s.Hobby = make([]string, len(hobbies))     copy(s.Hobby, hobbies) } student.SetHobbies(hobbies)

11. 结构体是否可以比较?

暂时先引用一篇煎鱼大佬的文章
结构体比较

[TOC]

1. 定义

结构体,是GO语言中等效于“类”的数据结构。
基于结构体,GO语言同样可以实现“类”的“封装”、“继承”、“多态”,且更加简洁灵活。
结构体,是GO提供的可自定义内部字段的基本类型,且是值类型(仅声明时零值为各字段的零值),这点和数组相似。

通过关键字type ... struct
注意“内存对齐”可参考阅读恰到好处的内存对齐

type 结构体名 struct {     字段名 字段类型     字段名 字段类型     … } 如: type student struct {     name, addr string     age int     isMarried bool }  var stu student fmt.Printf("%vn", stu)

2. 初始化

2.1 先声明再实例化

func main() {     var stu1  student     stu1.name = "stu1"     stu1.addr = "addr1"     stu1.age = 21     stu1.isMarried = false      fmt.Printf("%#vn", stu1) }

2.1 声明即初始化

2.1.1 按字段值顺序

这种方式,必须严格按照字段顺序赋值,否则赋值就可能错位或者出错了。

func main() {     stu1 := student{ "stu1", "addr1", 21, false}     fmt.Printf("%#vn", stu1) }

2.1.1 按”KV”方式

这种方式类似于map的键值方式,字段赋值的先后顺序就相对自由了。

func main() {     stu1 := student{         addr: "addr1",         name: "stu1",         isMarried: false,                 age: 21,     }      fmt.Printf("%#vn", stu1)

3. 结构体指针

结构体是值类型,所以在传递时并不高效。通常我们就会使用结构体的指针进行传递处理。
方式1:通过关键字new可以得到结构体的指针。
方式2:通过&取地址符也可以。

stu5 := new(student) fmt.Printf("%#vn", stu5)  stu6 := &student{} fmt.Printf("%#vn", stu6)

4. 匿名结构体

场景:
1)组织分散的数据来进行序列化操作;

aStu := struct{     Name string `json:"userName"`     Age int `json:"userAge"`     addr string `json:"userAddr"`     IsMarried bool `json:"isMarried"` }{     name,age,addr,false, } aStuBytes, err:=json.Marshal(aStu) if err!=nil{     fmt.Println("json.Marshal error:", err)     return } fmt.Printf("to rsp json str:n %vn", string(aStuBytes))

2)缩小操作数据的范围;

type student struct {     name, addr string     age int     isMarried bool }  //假设DB接口返回的是stu结构体 stu := student{     addr: "addr1",     name: "stu1",     isMarried: false,     age: 21, } //实际要应答的只需要其中部分数据 aStu := struct {     Name string `json:"userName"`     IsMarried bool `json:"isMarried"` }{     stu.name, stu.isMarried, } aStuBytes, err:=json.Marshal(aStu) if err!=nil{     fmt.Println("json.Marshal error:", err)     return } fmt.Printf("to rsp json str:n %vn", string(aStuBytes))

5. 方法和接受者

GO语言中没有“类”的概念,也就没有“类方法”的概念。
但是,GO提供了更加灵活高效的“接收者”概念。
“接收者”要和具体的函数“绑定”,这样的函数就叫“方法”。
“接收者”是某种具体的类型或类型指针。
注意事项: 非本地类型不能定义方法,即不能给别的包的类型定义方法。

type Student struct {     Name string     Addr string     Age int } // 类似于“构造函数” func NewStudent(name, addr string, age int) *Student {     return &Student{         name,         addr,         age,     } } func (s *Student) SetAge(age int) {     s.Age = age } student := NewStudent("student", "addr", 20) fmt.Printf("%#vn", student) student.SetAge(30) fmt.Printf("%#vn", student)

5.1 类型和类型指针接收者

如何选择方法的接收者是用类型本身还是类型的指针呢?
可从几下几个方面进行维度参考:
1)安全:方法是否允许或是否需要修改接收者源值?如果需要,接收器必须是一个指针。
2)效率:如果接收者本身是个很大的数据结构,那么显然也是需要考虑保证安全之下的性能开销的(拷贝代价比较大)。
3)风格:如果类型的某些方法必须有指针接收器,那么其余的方法也应该有指针接收器,保持一致性。
另外对于基本类型、切片和小结构等类型,值接收器是非常廉价的。
因此除非方法的语义需要指针,那么值接收器是最高效和清晰的。
在 GC 方面,也不需要过度关注。

6. 空结构体

形式:struct{}
空结构体,不占用内存空间。这样可以在一定程度上减少内存使用,特别是在消息管道开辟数量到达一定量级之后。
这就涉及到在 Go 语言中 ”宽度“ 的概念,宽度描述了一个类型的实例所占用的存储空间的字节数。
宽度是一个类型的属性。在 Go 语言中的每个值都有一个类型,值的宽度由其类型定义,并且总是 8 bits 的倍数。在 Go 语言中我们可以借助 unsafe.Sizeof 方法,来获取:

var v struct{} fmt.Println(unsafe.Sizeof(v))  // 0

6.1 实现方法“分组”结构化

在该场景下,使用空结构体从多维度来考量是最合适的,易拓展,省空间,最结构化。

//公共方法的"组合" 趋于接口形式 如: type T bool func (s *T) Call() 即使bool的开销已经比较小,但是仍然还会占据一点点空间。 这种时候换空结构体上场,这样也便于未来针对该类型进行公共字段等的增加。如下: type PubComm struct{} func (s *PubComm)  Conn() {     fmt.Println("connect ...") } func (s *PubComm)  Send() {     fmt.Println("send ...") } func (s *PubComm)  Recv() {     fmt.Println("recv ...") }  func main() {     var pubcomm PubComm     pubcomm.Conn()     pubcomm.Send()     pubcomm.Recv() }

6.2 实现”Set”类型

在 Go 语言的标准库中并没有提供集合(Set)的相关实现,因此一般在代码中我们图方便,会直接用 map 来替代。但有个问题,就是集合类型的使用,只需要用到 key(键),不需要 value(值)。
这就是空结构体大展身手的场景了

type MySet map[string]struct{} func (ms *MySet) Append(key string) {     (*ms)[key] = struct{}{} } func (ms *MySet) Delete(key string) {     delete(*ms, key) } func (ms *MySet) Exist(key string) bool {     _, ok := (*ms)[key]     return  ok } func main() {         mySet := MySet{}     fmt.Printf("%vn", unsafe.Sizeof(mySet))     mySet.Append("k1")     mySet.Append("k2")     mySet.Append("k3")     mySet.Append("k4")     fmt.Printf("%#v n", mySet)     mySet.Delete("k2")     fmt.Printf("%#v n", mySet)     fmt.Printf("%#v n", mySet.Exist("k4")) } 空结构体作为占位符,不会额外增加不必要的内存开销,很方便的就是解决了。

6.3 实现空通道协同或状态控制。

在 Go channel 的使用场景中,常常会遇到通知型 channel,其不需要发送任何数据,只是用于协调 Goroutine 的运行,用于流转各类状态或是控制并发情况。

如下: syncCh := make(chan struct{}) go func() {     fmt.Println("子进程处理中...")     time.Sleep(time.Second*3)     close(syncCh) }()  <-syncCh fmt.Println("父进程收到子进程退出信号")

7. 匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
本质:匿名字段并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
基础篇·第一章[13]·结构体和方法

type Student struct {     Name string     Addr string     Age int     float32 //匿名字段 }  // 类似于“构造函数” func NewStudent(name, addr string, age int) *Student {     return &Student{         name,         addr,         age,         0.0,     } }  func (s *Student) SetAge(age int) {     s.Age = age } student := NewStudent("student", "addr", 20) fmt.Printf("%#vn", student) student.SetAge(30) fmt.Printf("%#vn", student)  输出结果: &main.Student{Name:"student", Addr:"addr", Age:20, float32:0} &main.Student{Name:"student", Addr:"addr", Age:30, float32:0}

8. 嵌套结构体

嵌套,即一个结构体中包含另一个结构体或结构体指针;这种嵌套特性,可以类似理解为其他语言的“继承”。
嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。
当访问结构体成员时会先在结构体中查找该字段,找不到再去嵌套的匿名字段中查找。

// Factor 厂商 type Factor struct {     Name string `json:"name"`     Addr string `json:"addr"`     CrtTime time.Time `json:"crt_time"` } // Model 型号 type Model struct {     Name string `json:"name"`     CrtTime time.Time `json:"crt_time"` } // Car 车 type Car struct {     *Factor `json:"factor"`//通过嵌套匿名结构体实现继承     *Model `json:"model"`//通过嵌套匿名结构体实现继承 } BmwX5 := Car{     &Factor{Name: "宝马", Addr: "北京", CrtTime: time.Now()},     &Model{Name: "X5", CrtTime: time.Now()}, } fmt.Printf("%#vn", BmwX5.Addr) //本身没有Addr字段,继续找到嵌套的匿名结构体中。 fmt.Printf("%#v %#vn", BmwX5.Factor.Name, BmwX5.Model.Name)//有同名字段时需具体指定所属结构体  carBytes, err:=json.Marshal(&BmwX5) if err!=nil{     fmt.Printf("json.Marshal error", err)     return } fmt.Printf("json str: %vn", string(carBytes))

9. 结构体字段的可见性

结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

aStu := struct{     Name string `json:"userName"`     Age int `json:"userAge"`     addr string `json:"userAddr"` //这个字段json无法解析,小写不可见     IsMarried bool `json:"isMarried"` }{     name,age,addr,false, } aStuBytes, err:=json.Marshal(aStu) if err!=nil{     fmt.Println("json.Marshal error:", err)     return } fmt.Printf("to rsp json str:n %vn", string(aStuBytes))

基础篇·第一章[13]·结构体和方法
注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

10. 结构体引用类型字段的使用注意事项

引用类型字段的赋值和修改需要注意相互影响。
建议使用时使用内置copy函数处理接收的指针参数。

// 有坑的用法 func (s *Student) SetHobby(hobbies []string) {     s.Hobby = hobbies }  // NewStudent 类似于“构造函数” func NewStudent(name, addr string, age int) *Student {     return &Student{         name,         addr,         age,         0.0,         nil,     } } student := NewStudent("student", "addr", 20) fmt.Printf("%#vn", student)  hobbies := []string{"football", "basketball", "music"} student.SetHobby(hobbies) fmt.Printf("%#vn", student)  hobbies[1] = "movie" fmt.Printf("%#vn", student) //因为hobbies的变动而导致student信息的改变  //正确做法 func (s *Student) SetHobbies(hobbies []string) {     s.Hobby = make([]string, len(hobbies))     copy(s.Hobby, hobbies) } student.SetHobbies(hobbies)

11. 结构体是否可以比较?

暂时先引用一篇煎鱼大佬的文章
结构体比较

[TOC]

1. 定义

结构体,是GO语言中等效于“类”的数据结构。
基于结构体,GO语言同样可以实现“类”的“封装”、“继承”、“多态”,且更加简洁灵活。
结构体,是GO提供的可自定义内部字段的基本类型,且是值类型(仅声明时零值为各字段的零值),这点和数组相似。

通过关键字type ... struct
注意“内存对齐”可参考阅读恰到好处的内存对齐

type 结构体名 struct {     字段名 字段类型     字段名 字段类型     … } 如: type student struct {     name, addr string     age int     isMarried bool }  var stu student fmt.Printf("%vn", stu)

2. 初始化

2.1 先声明再实例化

func main() {     var stu1  student     stu1.name = "stu1"     stu1.addr = "addr1"     stu1.age = 21     stu1.isMarried = false      fmt.Printf("%#vn", stu1) }

2.1 声明即初始化

2.1.1 按字段值顺序

这种方式,必须严格按照字段顺序赋值,否则赋值就可能错位或者出错了。

func main() {     stu1 := student{ "stu1", "addr1", 21, false}     fmt.Printf("%#vn", stu1) }

2.1.1 按”KV”方式

这种方式类似于map的键值方式,字段赋值的先后顺序就相对自由了。

func main() {     stu1 := student{         addr: "addr1",         name: "stu1",         isMarried: false,                 age: 21,     }      fmt.Printf("%#vn", stu1)

3. 结构体指针

结构体是值类型,所以在传递时并不高效。通常我们就会使用结构体的指针进行传递处理。
方式1:通过关键字new可以得到结构体的指针。
方式2:通过&取地址符也可以。

stu5 := new(student) fmt.Printf("%#vn", stu5)  stu6 := &student{} fmt.Printf("%#vn", stu6)

4. 匿名结构体

场景:
1)组织分散的数据来进行序列化操作;

aStu := struct{     Name string `json:"userName"`     Age int `json:"userAge"`     addr string `json:"userAddr"`     IsMarried bool `json:"isMarried"` }{     name,age,addr,false, } aStuBytes, err:=json.Marshal(aStu) if err!=nil{     fmt.Println("json.Marshal error:", err)     return } fmt.Printf("to rsp json str:n %vn", string(aStuBytes))

2)缩小操作数据的范围;

type student struct {     name, addr string     age int     isMarried bool }  //假设DB接口返回的是stu结构体 stu := student{     addr: "addr1",     name: "stu1",     isMarried: false,     age: 21, } //实际要应答的只需要其中部分数据 aStu := struct {     Name string `json:"userName"`     IsMarried bool `json:"isMarried"` }{     stu.name, stu.isMarried, } aStuBytes, err:=json.Marshal(aStu) if err!=nil{     fmt.Println("json.Marshal error:", err)     return } fmt.Printf("to rsp json str:n %vn", string(aStuBytes))

5. 方法和接受者

GO语言中没有“类”的概念,也就没有“类方法”的概念。
但是,GO提供了更加灵活高效的“接收者”概念。
“接收者”要和具体的函数“绑定”,这样的函数就叫“方法”。
“接收者”是某种具体的类型或类型指针。
注意事项: 非本地类型不能定义方法,即不能给别的包的类型定义方法。

type Student struct {     Name string     Addr string     Age int } // 类似于“构造函数” func NewStudent(name, addr string, age int) *Student {     return &Student{         name,         addr,         age,     } } func (s *Student) SetAge(age int) {     s.Age = age } student := NewStudent("student", "addr", 20) fmt.Printf("%#vn", student) student.SetAge(30) fmt.Printf("%#vn", student)

5.1 类型和类型指针接收者

如何选择方法的接收者是用类型本身还是类型的指针呢?
可从几下几个方面进行维度参考:
1)安全:方法是否允许或是否需要修改接收者源值?如果需要,接收器必须是一个指针。
2)效率:如果接收者本身是个很大的数据结构,那么显然也是需要考虑保证安全之下的性能开销的(拷贝代价比较大)。
3)风格:如果类型的某些方法必须有指针接收器,那么其余的方法也应该有指针接收器,保持一致性。
另外对于基本类型、切片和小结构等类型,值接收器是非常廉价的。
因此除非方法的语义需要指针,那么值接收器是最高效和清晰的。
在 GC 方面,也不需要过度关注。

6. 空结构体

形式:struct{}
空结构体,不占用内存空间。这样可以在一定程度上减少内存使用,特别是在消息管道开辟数量到达一定量级之后。
这就涉及到在 Go 语言中 ”宽度“ 的概念,宽度描述了一个类型的实例所占用的存储空间的字节数。
宽度是一个类型的属性。在 Go 语言中的每个值都有一个类型,值的宽度由其类型定义,并且总是 8 bits 的倍数。在 Go 语言中我们可以借助 unsafe.Sizeof 方法,来获取:

var v struct{} fmt.Println(unsafe.Sizeof(v))  // 0

6.1 实现方法“分组”结构化

在该场景下,使用空结构体从多维度来考量是最合适的,易拓展,省空间,最结构化。

//公共方法的"组合" 趋于接口形式 如: type T bool func (s *T) Call() 即使bool的开销已经比较小,但是仍然还会占据一点点空间。 这种时候换空结构体上场,这样也便于未来针对该类型进行公共字段等的增加。如下: type PubComm struct{} func (s *PubComm)  Conn() {     fmt.Println("connect ...") } func (s *PubComm)  Send() {     fmt.Println("send ...") } func (s *PubComm)  Recv() {     fmt.Println("recv ...") }  func main() {     var pubcomm PubComm     pubcomm.Conn()     pubcomm.Send()     pubcomm.Recv() }

6.2 实现”Set”类型

在 Go 语言的标准库中并没有提供集合(Set)的相关实现,因此一般在代码中我们图方便,会直接用 map 来替代。但有个问题,就是集合类型的使用,只需要用到 key(键),不需要 value(值)。
这就是空结构体大展身手的场景了

type MySet map[string]struct{} func (ms *MySet) Append(key string) {     (*ms)[key] = struct{}{} } func (ms *MySet) Delete(key string) {     delete(*ms, key) } func (ms *MySet) Exist(key string) bool {     _, ok := (*ms)[key]     return  ok } func main() {         mySet := MySet{}     fmt.Printf("%vn", unsafe.Sizeof(mySet))     mySet.Append("k1")     mySet.Append("k2")     mySet.Append("k3")     mySet.Append("k4")     fmt.Printf("%#v n", mySet)     mySet.Delete("k2")     fmt.Printf("%#v n", mySet)     fmt.Printf("%#v n", mySet.Exist("k4")) } 空结构体作为占位符,不会额外增加不必要的内存开销,很方便的就是解决了。

6.3 实现空通道协同或状态控制。

在 Go channel 的使用场景中,常常会遇到通知型 channel,其不需要发送任何数据,只是用于协调 Goroutine 的运行,用于流转各类状态或是控制并发情况。

如下: syncCh := make(chan struct{}) go func() {     fmt.Println("子进程处理中...")     time.Sleep(time.Second*3)     close(syncCh) }()  <-syncCh fmt.Println("父进程收到子进程退出信号")

7. 匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
本质:匿名字段并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
基础篇·第一章[13]·结构体和方法

type Student struct {     Name string     Addr string     Age int     float32 //匿名字段 }  // 类似于“构造函数” func NewStudent(name, addr string, age int) *Student {     return &Student{         name,         addr,         age,         0.0,     } }  func (s *Student) SetAge(age int) {     s.Age = age } student := NewStudent("student", "addr", 20) fmt.Printf("%#vn", student) student.SetAge(30) fmt.Printf("%#vn", student)  输出结果: &main.Student{Name:"student", Addr:"addr", Age:20, float32:0} &main.Student{Name:"student", Addr:"addr", Age:30, float32:0}

8. 嵌套结构体

嵌套,即一个结构体中包含另一个结构体或结构体指针;这种嵌套特性,可以类似理解为其他语言的“继承”。
嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。
当访问结构体成员时会先在结构体中查找该字段,找不到再去嵌套的匿名字段中查找。

// Factor 厂商 type Factor struct {     Name string `json:"name"`     Addr string `json:"addr"`     CrtTime time.Time `json:"crt_time"` } // Model 型号 type Model struct {     Name string `json:"name"`     CrtTime time.Time `json:"crt_time"` } // Car 车 type Car struct {     *Factor `json:"factor"`//通过嵌套匿名结构体实现继承     *Model `json:"model"`//通过嵌套匿名结构体实现继承 } BmwX5 := Car{     &Factor{Name: "宝马", Addr: "北京", CrtTime: time.Now()},     &Model{Name: "X5", CrtTime: time.Now()}, } fmt.Printf("%#vn", BmwX5.Addr) //本身没有Addr字段,继续找到嵌套的匿名结构体中。 fmt.Printf("%#v %#vn", BmwX5.Factor.Name, BmwX5.Model.Name)//有同名字段时需具体指定所属结构体  carBytes, err:=json.Marshal(&BmwX5) if err!=nil{     fmt.Printf("json.Marshal error", err)     return } fmt.Printf("json str: %vn", string(carBytes))

9. 结构体字段的可见性

结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

aStu := struct{     Name string `json:"userName"`     Age int `json:"userAge"`     addr string `json:"userAddr"` //这个字段json无法解析,小写不可见     IsMarried bool `json:"isMarried"` }{     name,age,addr,false, } aStuBytes, err:=json.Marshal(aStu) if err!=nil{     fmt.Println("json.Marshal error:", err)     return } fmt.Printf("to rsp json str:n %vn", string(aStuBytes))

基础篇·第一章[13]·结构体和方法
注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

10. 结构体引用类型字段的使用注意事项

引用类型字段的赋值和修改需要注意相互影响。
建议使用时使用内置copy函数处理接收的指针参数。

// 有坑的用法 func (s *Student) SetHobby(hobbies []string) {     s.Hobby = hobbies }  // NewStudent 类似于“构造函数” func NewStudent(name, addr string, age int) *Student {     return &Student{         name,         addr,         age,         0.0,         nil,     } } student := NewStudent("student", "addr", 20) fmt.Printf("%#vn", student)  hobbies := []string{"football", "basketball", "music"} student.SetHobby(hobbies) fmt.Printf("%#vn", student)  hobbies[1] = "movie" fmt.Printf("%#vn", student) //因为hobbies的变动而导致student信息的改变  //正确做法 func (s *Student) SetHobbies(hobbies []string) {     s.Hobby = make([]string, len(hobbies))     copy(s.Hobby, hobbies) } student.SetHobbies(hobbies)

11. 结构体是否可以比较?

暂时先引用一篇煎鱼大佬的文章
结构体比较

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » 基础篇·第一章[13]·结构体和方法求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们