探索Go语言中结构体和方法的使用

一、前言

结构体是 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)的首字母大小写规则会影响字段的可见性和访问权限。

特点如下:

  1. 如果一个结构体字段的首字母是大写字母(例如FirstName),那么这个字段是可导出的,可以被其他包中的代码访问。这种字段具有公共的访问权限。
  2. 如果一个结构体字段的首字母是小写字母(例如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代码。

相关推荐
郝同学的测开笔记1 天前
云原生探索系列(十二):Go 语言接口详解
后端·云原生·go
一点一木2 天前
WebAssembly:Go 如何优化前端性能
前端·go·webassembly
千羽的编程时光2 天前
【CloudWeGo】字节跳动 Golang 微服务框架 Hertz 集成 Gorm-Gen 实战
go
27669582923 天前
阿里1688 阿里滑块 231滑块 x5sec分析
java·python·go·验证码·1688·阿里滑块·231滑块
Moment5 天前
在 NodeJs 中如何通过子进程与 Golang 进行 IPC 通信 🙄🙄🙄
前端·后端·go
唐僧洗头爱飘柔95275 天前
(Go基础)变量与常量?字面量与变量的较量!
开发语言·后端·golang·go·go语言初上手
黑心萝卜三条杠5 天前
【Go语言】深入理解Go语言:并发、内存管理和垃圾回收
google·程序员·go
不喝水的鱼儿5 天前
【LuatOS】基于WebSocket的同步请求框架
网络·websocket·网络协议·go·luatos·lua5.4
微刻时光6 天前
程序员开发速查表
java·开发语言·python·docker·go·php·编程语言
lidenger6 天前
服务认证-来者何人
后端·go