Go 编程 | 连载 17 - 结构体方法

简介: Go 编程 | 连载 17 - 结构体方法

一、结构体方法

Go 不是面向对象编程的语言,没有类和对象的概念,结构体就类似于面向对象编程中的类,类有方法,结构体也有方法,但是结构体的方法是放在结构体外的。

结构体方法实现了面向对象中的 封装 特性,达到封装数据和封装方法的效果。

结构体方法的定义与普通函数的定义差别在于比普通函数多了一个 函数的接收者 的概念,也就是该方法要绑定的结构体。

// s 为函数接收者
func(s structName) funcName(para1, paraType) (retVal retType){
    // function body
}
复制代码
func main() {
   tesla := Tesla{"Model 3", 298000.0}
   // 通过实例化的结构体调用方法
   tesla.printTeslaInfo()
}
type Tesla struct {
   Name string
   Price float64
}
// 定义结构体方法
func (t Tesla) printTeslaInfo(){
   fmt.Println("实例化 Tesla 结构体的 Name 属性值:", t.Name, ",价格为:", t.Price) 
}
复制代码

执行上述代码,输出结果如下:

实例化 Tesla 结构体的 Name 属性值: Model 3 ,价格为: 298000
复制代码

除了通过实例化结构体调用方法,也可以通过结构体直接调用方法,这种调用方式需要将实例化结构体作为参数

Tesla.printTeslaInfo(tesla)
复制代码

再定义一个修改结构体属性的 set 方法

func main() {
   tesla := Tesla{"Model 3", 298000.0}
   // 调用 set 方法修改结构体价格
   tesla.setPrice(330000.0)
   fmt.Println(tesla) // {Model 3 298000}
}
func (t Tesla) setPrice(price float64) {
   // 修改结构体价格
   t.Price = price
}
复制代码

根据输出结果可以确定 set 方法修改结构体的 Price 属性失败,这是因为结构体是值传递,作为函数参数是结构体的副本,并不是原始的结构体。

这时要将函数接收者改为结构体指针类型,才能真正实现修改结构体属性。修改 setPrice 方法为如下代码:

func (t *Tesla) setPrice(price float64) {
   // 修改结构体价格
   t.Price = price
}
复制代码

使用结构体指针有两种情况:

  • 当你想改变结构体属性的时候
  • 当结构体非常大的时候

关于结构体方法需要注意的是:

  • 结构体和结构体方法必须在同一个包中
  • 内置的 int 类型不能添加结构体方法

内置的 int 绑定方法可以自定义一个 底层为 int 的数据类型,然后再绑定方法。

综上,结构体方法实现了面向对象的第一个特性 封装

二、结构体 继承

严格来说 Go 语言是不支持继承的,但是可以通过结构体 组合 或者 内嵌结构体 来实现继承特性。

首先定义 HumanStudent 两个结构体,代码如下:

type Human struct {
   Name string
   Age int
   Gender string
}
type Student struct {
   Human Human
   Grade string
   SchoolAddress string
}
复制代码

接着分别给两个结构体绑定方法,输出实例化结构体的信息

func (h Human) HumanInfo(){
   fmt.Printf("Human Info, Name:%v, Age: %v, Gender: %v\n", h.Name, h.Age, h.Gender)
}
func (s Student) StudentInfo(){
   fmt.Printf("Student-Human Info, Name:%v, Age: %v, Gender: %v\n", s.Human.Name, s.Human.Age, s.Human.Gender)
   fmt.Printf("Student Info, Grade:%v, SchoolAddress: %v\n", s.Grade, s.SchoolAddress)
}
复制代码

Student 结构体中嵌套了一个 Human 结构体,在输出 Student 结构体中的 Human 结构体属性的时候,通过 s.Human.Name 来输出,这种方式能不能成功输出?通过实例化结构体来调用方法验证一下。

func main() {
   h := Human{"tony", 12, "Male"}
   s := Student{h, "五年级", "NYC"}
   h.HumanInfo()
   s.StudentInfo()
}
复制代码

执行上述代码,输出结果如下:

Human Info, Name:tony, Age: 12, Gender: Male
Student-Human Info, Name:tony, Age: 12, Gender: Male
Student Info, Grade:五年级, SchoolAddress: NYC
复制代码

根据输出结果可以确定 s.Human.Name 这种形式是可以时候输出内嵌的结构体的信息的,但是其实还有一种匿名嵌套,既可以省略中间结构体的名字直接调用嵌套结构体的属性。

func main() {
   h := Human{"tony", 12, "Male"}
   s := Student{h, "五年级", "NYC"}
   h.HumanInfo()
   s.StudentInfo()
}
type Student struct {
   // 匿名嵌套
   Human
   Grade string
   SchoolAddress string
}
func (s Student) StudentInfo(){
   // 匿名嵌套,可以省略内嵌结构体的名称 
   fmt.Printf("Student-Human Info, Name:%v, Age: %v, Gender: %v\n", s.Name, s.Age, s.Gender)
   fmt.Printf("Student Info, Grade:%v, SchoolAddress: %v\n", s.Grade, s.SchoolAddress)
}
复制代码

再次调用 main 方法,输出结果保持不变。

当匿名嵌套的结构体的属性名和当前结构体中的属性名有雷同的情况下,优先取当前结构体中的属性的值,为了区分同名属性,可以加上嵌套的结构体名。

三、结构体标签

结构体的字段除了名称和类型之外,还可以添加 标签 tag ,tag 是一个附属于结构体的字符串,使用反引号 `` 表示,是一个重要的标记。

以 JSON 序列化为例,将实例化的结构体序列化为 JSON 格式字符串时,需要将 JSON 字符串的 Key 改为小写,这时就需要用到 json 标签。

保持 Student 结构体不变,给 Human 结构体增加 json 标签

func main() {
   h := Human{"tony", 12, "Male"}
   s := Student{h, "五年级", "NYC"}
   // 序列化 Human 的实例化结构体
   hJson, _ := json.Marshal(h)
   fmt.Println(string(hJson))
   // 序列化 Student 的实例化结构体
   sJson, _ := json.Marshal(s)
   fmt.Println(string(sJson))
}
type Human struct {
   Name string `json:"name"`
   Age int `json:"age"`
   Gender string `json:"gender"`
}
复制代码

执行上述代码,输出结果如下:

{"name":"tony","age":12,"gender":"Male"}
{"Human":{"name":"tony","age":12,"gender":"Male"},"Grade":"五年级","SchoolAddress":"NYC"}
复制代码

通过输出结果可以确定,Human 结构体增加的 json 标签中的 name 标签值可以将结构体字段从 Name 变为 name

当然也有一些其他的标签比如 orm 标签,改标签可以限制结构体映射到数据库表时表字段的限制,比如 字段名、最大长度 max_length、最小长度 min_lengts、最大值 max 以及最小值 min 等。

通过 reflect 标准库识别结构体中每个字段上定义的 tag

func main() {
   h := Human{"tony", 12, "Male"}
   // 反射获取标签属性
   humanTag := reflect.TypeOf(h)
   for i := 0; i < humanTag.NumField(); i++ {
      field := humanTag.Field(i)
      jsonTag := field.Tag.Get("json")
      fmt.Printf("%d, %v, (%v), tag: %v\n", i+1, field.Name, field.Type.Name(), jsonTag)
   }
}
复制代码

执行上述代码,输出结果如下:

1, Name, (string), tag: name
2, Age, (int), tag: age
3, Gender, (string), tag: gender



相关文章
|
13天前
|
存储 Rust Go
Go nil 空结构体 空接口有什么区别?
本文介绍了Go语言中的`nil`、空结构体和空接口的区别。`nil`是预定义的零值变量,适用于指针、管道等类型;空结构体大小为0,多个空结构体实例指向同一地址;空接口由`_type`和`data`字段组成,仅当两者均为`nil`时,空接口才为`nil`。
Go nil 空结构体 空接口有什么区别?
|
7天前
|
数据采集 监控 Java
go语言编程学习
【11月更文挑战第3天】
23 7
|
13天前
|
Unix Linux Go
go进阶编程:Golang中的文件与文件夹操作指南
本文详细介绍了Golang中文件与文件夹的基本操作,包括读取、写入、创建、删除和遍历等。通过示例代码展示了如何使用`os`和`io/ioutil`包进行文件操作,并强调了错误处理、权限控制和路径问题的重要性。适合初学者和有经验的开发者参考。
|
21天前
|
Java 大数据 Go
Go语言:高效并发的编程新星
【10月更文挑战第21】Go语言:高效并发的编程新星
44 7
|
1月前
|
SQL 关系型数据库 MySQL
Go语言项目高效对接SQL数据库:实践技巧与方法
在Go语言项目中,与SQL数据库进行对接是一项基础且重要的任务
54 11
|
29天前
|
Go 数据处理 调度
Go语言中的并发模型:解锁高效并行编程的秘诀
本文将探讨Go语言中独特的并发模型及其在现代软件开发中的应用。通过深入分析 Goroutines 和 Channels,我们将揭示这一模型如何简化并行编程,提升应用性能,并改变开发者处理并发任务的方式。不同于传统多线程编程,Go的并发方法以其简洁性和高效性脱颖而出,为开发者提供了一种全新的编程范式。
|
2月前
|
Go
Go to Learn Go之结构体
Go to Learn Go之结构体
37 5
|
2月前
|
大数据 Shell Go
GO方法与自定义类型
本文详细介绍了 Go 语言中的自定义数据类型与方法。不同于传统的面向对象编程语言,Go 通过结构体 (`struct`) 和方法 (`method`) 来扩展自定义类型的功能。文章解释了如何定义结构体、创建方法,并探讨了值接收器与指针接收器的区别及应用场景。此外,还介绍了方法的可见性以及接收器的命名惯例。通过具体示例,帮助读者更好地理解和应用这些概念。
|
1月前
|
并行计算 算法 搜索推荐
探索Go语言的高并发编程与性能优化
【10月更文挑战第10天】探索Go语言的高并发编程与性能优化
|
2月前
|
存储 缓存 Go
go语言编程系列(五)
go语言编程系列(五)