一、前言
结构体是 Go 语言中重要的数据类型之一,用于组织和表示复杂的数据结构。结构体方法则允许我们为结构体类型定义特定的行为,使代码更加模块化和可维护。在本文中,我们将探讨结构体的各个方面,以及如何使用结构体方法来操作结构体数据。
二、内容
2.1 结构体初体验
在Go
中,结构体(struct)是由一系列字段(fields)组成的数据类型,每个字段可以具有不同的数据类型,但每个字段的数据类型在结构体内必须明确定义,并且这些字段的类型可以是相同的,也可以是不同的。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
举个例子:
go
package main
import "fmt"
type Person struct {
FirstName string
LastName string
Age int
IsMarried bool
}
func main() {
person1 := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
IsMarried: false,
}
person2 := Person{
FirstName: "Alice",
LastName: "Smith",
Age: 25,
IsMarried: true,
}
fmt.Println(person1)
fmt.Println(person2)
}
运行结果:
bash
{John Doe 30 false}
{Alice Smith 25 true}
在上述示例中,Person
结构体包含了不同的字段,包括了字符串,整数和布尔值类型的字段。每个字段都有自己的数据类型,但它们都组成了一个结构体类型,即 Person
。
数组(array)在Go中也是一种数据类型,它存储相同类型的数据元素,并且数组的长度是固定的。不同于结构体,数组的元素类型必须相同,无法在同一个数组中存储不同类型的数据。
2.2 结构体的定义与使用
结构体的定义格式如下:
go
type struct_name struct {
field_name data_type
field_name data_type
...
field_name data_type
}
在上述格式中,struct_name
是结构体的类型名称,而大括号内包含了一个或多个成员(字段)定义,每个成员定义包括成员的名称和数据类型。
一旦定义了结构体类型,您可以使用它来声明结构体变量,语法如下:
go
val_name := struct_name{value1, value2...valuen}
或者,您可以使用键值对的方式来初始化结构体变量,语法如下:
go
val_name := struct_name{key1: value1, key2: value2..., keyn: valuen}
这两种方式都允许您为结构体的字段赋值。
举个例子:
go
package main
import "fmt"
// 定义结构体 Person
type Person struct {
FirstName string
LastName string
Age int
}
func main() {
// 方式一
person1 := Person{"John", "Doe", 30}
// 方式二
person2 := Person{
FirstName: "Alice",
LastName: "Smith",
Age: 25,
}
fmt.Println("person1", person1) // person1 {John Doe 30}
fmt.Println("person2", person2) // person2 {Alice Smith 25}
}
2.3 访问结构体对象
如果要访问结构体的成员字段,则需要使用点号.
操作符,将结构体变量与成员字段名称相结合。
举个例子:
go
package main
import "fmt"
type Person struct {
FirstName string
LastName string
Age int
}
func main() {
person := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
// 访问结构体成员字段并打印它们的值
fmt.Println("First Name:", person.FirstName)
fmt.Println("Last Name:", person.LastName)
fmt.Println("Age:", person.Age)
}
运行结果:
shell
First Name: John
Last Name: Doe
Age: 30
这就是访问结构体成员字段的标准方式。
点号操作符使我们能够获取和修改结构体中的各个字段的值。
2.4 结构体作为函数参数
像其他数据类型一样,结构体类型同样可以作为参数传递给函数。这使得我们可以将负责的数据结构传递给函数,并在函数内部对其进行操作或分析。
举个例子:
go
package main
import "fmt"
// 定义结构体
type Person struct {
FirstName string
LastName string
Age int
}
func main() {
// 创建结构体
person := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
// 将结构体作为参数传递给函数
PrintPersonDetails(person)
}
// PrintPersonDetails ,打印参数中 Person 结构体的对象
func PrintPersonDetails(p Person) {
fmt.Println("First Name:", p.FirstName)
fmt.Println("Last Name:", p.LastName)
fmt.Println("Age:", p.Age)
}
2.5 结构体指针
之前的文章讲过,Go中的数据传递严格来说都是值传递。
举个例子:
go
package main
import "fmt"
// 定义结构体
type Person struct {
FirstName string
LastName string
Age int
}
func main() {
// 创建一个结构体
person := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
// 将结构体指针作为参数传递给函数
ModifyPersonDetails(person)
// 查看 person 结构体的数据是否已被修改
fmt.Println("First Name:", person.FirstName)
fmt.Println("Last Name:", person.LastName)
fmt.Println("Age:", person.Age)
}
// 函数接受一个指向 Person 结构体的指针作为参数
func ModifyPersonDetails(p Person) {
p.FirstName = "Alice"
p.LastName = "Smith"
p.Age = 28
}
运行结果为:
shell
First Name: John
Last Name: Doe
Age: 30
可以看到,person
结构体的数据并没有发生变化。这就意味着,结构体在作为函数参数进行传递时,是按照值传递的规则,此时会将结构体的副本传递给函数,而不是原始结构体本身。
如果我们希望在函数内部修改原始结构体的数据,那么应该将结构体的指针传递给函数。
当我们将结构体的指针传递给函数时,函数将会操作原始结构体的引用,而不是其副本。
go
package main
import "fmt"
// 定义结构体
type Person struct {
FirstName string
LastName string
Age int
}
func main() {
// 创建一个结构体
person := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
// 将结构体指针作为参数传递给函数
ModifyPersonDetails(&person)
// 查看 person 结构体的数据是否已被修改
fmt.Println("First Name:", person.FirstName)
fmt.Println("Last Name:", person.LastName)
fmt.Println("Age:", person.Age)
}
// 函数接受一个指向 Person 结构体的指针作为参数
func ModifyPersonDetails(p *Person) {
p.FirstName = "Alice"
p.LastName = "Smith"
p.Age = 28
}
运行结果如下:
shell
First Name: Alice
Last Name: Smith
Age: 28
小结:
当我们将函数修改为接收指向某个结构体的指针作为参数,那么通过传递该结构体的内存地址,函数就可以直接修改原始结构体的数据。注意,我们可以使用
&
操作符来获取某结构体变量的地址。
2.6 字段可见性
在Go中,结构体字段(fields)的首字母大小写规则会影响字段的可见性和访问权限。
特点如下:
- 如果一个结构体字段的首字母是大写字母(例如
FirstName
),那么这个字段是可导出的,可以被其他包中的代码访问。这种字段具有公共的访问权限。 - 如果一个结构体字段的首字母是小写字母(例如
age
),那么这个字段是不可导出的,只能被定义该结构体的包内代码访问。这种字段具有私有的访问权限。
举个例子:
在 project
项目根目录下的otherpackage
包中有 Person
结构体定义:
go
package otherpackage
type Person struct {
FirstName string // 可导出字段
age int // 不可导出字段
}
那么,在其他包中是无法访问不可导出字段的。
比如:
go
package main
import (
"project/otherpackage"
"fmt"
)
func main() {
person := otherpackage.Person{
FirstName: "John",
// age: 30, // error: unknown field age in struct literal of type otherpackage.Person
}
fmt.Println(person.FirstName) // 可以访问可导出字段
// fmt.Println(person.age) // 无法访问不可导出字段
}
字段可见性规则有助于数据的封装和隐藏内部实现细节,是Go语言中的一种重要特性,有助于编写清晰、可维护和安全的代码。
2.7 结构体方法
Go 支持在结构体类型中定义方法 。
在Go中,结构体方法是一种用于关联特定类型(结构体)的函数。这些方法可以访问结构体的字段,并对其执行操作,或者执行其他与结构体相关的任务。
结构体方法是通过将函数与结构体类型关联来定义的。这样的函数被称为方法,它们在结构体类型的定义上定义。
结构体方法的语法如下:
go
func (receiver ReceiverType) MethodName(parameters) ReturnType {
// 方法的实现
}
在上述语法中,receiver
是一个参数,指定了方法将作用于哪个结构体类型。它可以是值接收者(receiver)或指针接收者(receiver)。
什么叫值接收者或指针接收者?
事实上,值接收者指的是结构体方法的接受者是结构体的一个副本,此时对结构体字段的修改是不会影响到原始结构体的。那么对于指针接受者来说,方法接收的是结构体的一个指针,那么此时对结构体字段的修改将会影响到原始结构体。
我们在方法内部可以通过接受者来访问结构体的字段,或者调用其他方法,那方法本身呢,是可以通过结构体变量或者结构体指针来调用的,具体看方法的接受者类型。
我们举个例子,看以下示例:
go
package main
import "fmt"
func main() {
// 创建一个 RectangleA 结构体实例
rectA := RectangleA{Width: 5, Height: 3}
fmt.Printf("initial value: Width-[%v] Height-[%v]\n", rectA.Width, rectA.Height)
fmt.Println("rectA.Area(): ", rectA.Area())
rectA.Change(2) // 调用值接收者方法
fmt.Printf("changed value: Width-[%v] Height-[%v]\n", rectA.Width, rectA.Height)
fmt.Println("----------------------------------------")
// 创建一个 RectangleB 结构体实例
rectB := RectangleB{Width: 5, Height: 3}
areaPB := &rectB // 创建指向 RectangleB 实例的指针
fmt.Printf("initial value: Width-[%v] Height-[%v]\n", areaPB.Width, areaPB.Height)
fmt.Println("areaPB.Area(): ", areaPB.Area())
areaPB.Change(2) // 调用指针接收者方法
fmt.Printf("changed value: Width-[%v] Height-[%v]\n", areaPB.Width, areaPB.Height)
}
// 结构体 A
type RectangleA struct {
Width float64 // 宽度
Height float64 // 长度
}
// 值接收者方法
func (ra RectangleA) Area() float64 {
return ra.Width * ra.Height
}
// 值接收者方法
func (ra RectangleA) Change(factor float64) {
ra.Width *= factor // ineffective assignment to field RectangleA.Width
ra.Height *= factor // ineffective assignment to field RectangleA.Height
}
// 结构体 B
type RectangleB struct {
Width float64 // 宽度
Height float64 // 长度
}
// 指针接收者方法
func (rb *RectangleB) Area() float64 {
return rb.Width * rb.Height
}
// 指针接收者方法
func (rb *RectangleB) Change(factor float64) {
rb.Width *= factor
rb.Height *= factor
}
运行结果:
bash
initial value: Width-[5] Height-[3]
rectA.Area(): 15
changed value: Width-[5] Height-[3]
----------------------------------------
initial value: Width-[5] Height-[3]
areaPB.Area(): 15
changed value: Width-[10] Height-[6]
可以看到,对于 Change
方法来说,如果接收的是结构体的指针,就可以直接修改原始数据,否则操作的就是结构体的副本。
关于结构体的方法命名来说,通常采用驼峰式命名风格,并且首字母大写,以表示它是公开的方法(可导出的)。
三、总结
结构体和结构体方法是Go语言中强大的工具,可用于创建复杂的数据结构并为其定义行为。本文详细介绍了结构体的定义、访问、可见性以及结构体方法的使用方法,希望读者通过本文的学习能够更好地利用这些特性来编写高效、可维护的Go代码。