Golang 结构体

news/2024/12/22 19:30:54/

前言

在 Go 语言中,结构体(struct)是一种自定义的数据类型,将多个不同类型的字段(fields)组合在一起
结构体通常用于模拟真实世界对象的属性和行为

定义结构体

可以使用 type 关键字和 struct 关键字来定义一个结构体:

type Person struct {Name stringAge  int
}

在这个示例中,我们定义了一个名为 Person 的结构体,它有两个字段:Name 是 string 类型,Age 是 int 类型

常见的还有匿名结构体,看例子就明白了:

stu := struct{ name string }{"Allen"}
fmt.Println(stu.name) // Allen

实例化

创建结构体的实例(或对象)的过程称为实例化,可以通过结构体类型声明新的变量:

func main() {// 实例化结构体p := Person{Name: "Alice", Age: 30}// 访问结构体字段fmt.Println(p.Name) // 输出 "Alice"fmt.Println(p.Age)  // 输出 30
}

在这个示例中,p 是 Person 类型的变量,我们使用结构体字面量来初始化它的字段

结构体指针

可以使用 & 符号创建指向结构体的指针。通过指针,可以访问或修改结构体的字段:

func main() {// 创建指向 Person 结构体的指针p := &Person{Name: "Bob", Age: 25}// 通过指针访问结构体的字段fmt.Println(p.Name) // 输出 "Bob"fmt.Println(p.Age)  // 输出 25// 通过指针修改结构体的字段p.Age = 26fmt.Println(p.Age)  // 输出 26
}

p 是一个指向 Person 结构体的指针。即使我们使用了指针,我们仍然可以使用点操作符(.)来访问或修改字段,这是因为 Go 语言提供了指针的隐式解引用

结构体指针,它们用于直接访问或修改结构体实例的字段和方法,而不是通过副本。这在以下情况中很有用:

  • 当你需要在方法或函数中修改结构体的字段时
  • 当结构体很大,传递指针比复制整个结构体更高效时
  • 当你希望确保结构体的所有实例共享相同的数据时,例如,当多个变量需要指向同一个结构体实例以便可以同步状态变化

结构体方法

可以为结构体定义方法。方法是一种附加到特定类型(如结构体)的函数。方法的定义与普通函数类似,但它在函数名称之前有一个额外的参数,称为接收器(receiver),它指定了方法所附加的类型

func (p Person) SayHello() {fmt.Printf("Hi, my name is %s and I am %d years old.\n", p.Name, p.Age)
}func main() {p := Person{Name: "Eve", Age: 22}p.SayHello() // 输出 "Hi, my name is Eve and I am 22 years old."
}

我们为 Person 结构体定义了一个 SayHello 方法,该方法可以通过 Person 类型的任何实例来调用

结构体字段标签

结构体字段可以通过字段标签(field tags)提供元数据。这些标签可以被用于多种用途,例如序列化和反序列化 JSON 数据、配置数据库字段映射以及进行验证等

字段标签是在结构体字段声明后以字符串形式提供的,并且总是放在反引号 (`) 之间。一个字段可以有多个标签,每个标签通常由一个特定的库或框架解析

下面是一个 JSON 序列化的例子,我们定义了一个结构体并使用了 JSON 标签:

type Person struct {Name string `json:"name"`Age  int    `json:"age"`City string `json:"city,omitempty"`
}

在这个例子中,Person 结构体有三个字段:Name、Age 和 City。每个字段后面都跟有一个 JSON 标签。这些标签指示 encoding/json 标准库如何序列化和反序列化结构体到 JSON 格式

  • json:“name” 表明 JSON 对象中对应的键是 name
  • json:“age” 表明 JSON 对象中对应的键是 age
  • json:“city,omitempty” 表明 JSON 对象中对应的键是 city,并且如果 City 字段的值为零值(在这里是空字符串),则在序列化的 JSON 对象中省略该键

使用标准库的 encoding/json 包来序列化结构体时,这些标签就会发挥作用:

func main() {p := Person{Name: "Alice", Age: 30, City: "Wonderland"}jsonData, _ := json.Marshal(p)fmt.Println(string(jsonData)) // 输出: {"name":"Alice","age":30,"city":"Wonderland"}p = Person{Name: "Bob", Age: 25}jsonData, _ = json.Marshal(p)fmt.Println(string(jsonData)) // 输出: {"name":"Bob","age":25} 注意没有 city 字段
}

在这个序列化的例子中,omitempty 选项导致 City 字段在 Bob 的情况下被省略,因为它是空字符串

继承

是通过组合(composition)来实现的,而不是像在其他一些面向对象编程语言中那样直接使用继承关键字。Go 的设计哲学鼓励组合而不是继承,这意味着一个结构体可以包含(嵌入)另一个结构体的字段,从而能够使用嵌入结构体的方法和字段,实现类似继承的行为

这是一个使用结构体组合来实现继承行为的例子:

type Animal struct {Name string
}func (a *Animal) Speak() {fmt.Println(a.Name + " makes a noise.")
}type Dog struct {Animal // 嵌入 Animal 结构体
}func (d *Dog) Speak() {fmt.Println(d.Name + " barks.")
}func main() {dog := Dog{}dog.Name = "Fido"dog.Speak() // 输出: Fido barks.
}

在这里,Animal 是一个基本的结构体,有一个 Speak 方法。Dog 结构体通过嵌入 Animal 继承了它的字段和方法。然而,Dog 也定义了它自己的 Speak 方法,这展示了 Go 中的方法覆盖(类似于其他语言中的重写)

自定义类型

可以通过类型声明(type declaration)来定义一个新的自定义类型。自定义类型基于现有的类型,但它有自己的独立名称和方法,这可以使代码更加清晰和类型安全

以下是创建自定义类型的基本语法:

type MyCustomType ExistingType

MyCustomType 是新定义的类型名称,而 ExistingType 是已有的类型,可以是内置类型,如 int、string 等,也可以是复杂类型,如结构体、接口等

下面是几个自定义类型的例子:

  1. 基于内置类型的自定义类型:
// 定义一个基于 int 的自定义类型
type MyInt intfunc main() {var x MyInt = 5fmt.Println(x) // 输出: 5
}
  1. 基于结构体的自定义类型:
// 定义一个结构体
type Person struct {Name stringAge  int
}// 基于结构体的自定义类型
type Employee Personfunc main() {e := Employee{Name: "John", Age: 30}fmt.Println(e) // 输出: {John 30}
}
  1. 为自定义类型添加方法:
// 基于 float64 的自定义类型
type Distance float64// 为 Distance 类型定义一个方法
func (d Distance) String() string {return fmt.Sprintf("%f meters", d)
}func main() {var d Distance = 5.5fmt.Println(d.String()) // 输出: 5.500000 meters
}

定义自定义类型允许你在类型上附加方法,使其表现得更像面向对象编程中的类。此外,自定义类型通过类型名称来提供更多上下文,这有助于代码的可读性和维护性

关于类型别名(从 Go 1.9 版本开始支持类型别名)
类型别名在 Go 语言中是通过使用 = 符号在类型定义中引入的。它们在语义上与原始类型相同,而不是创建一个新的类型。类型别名主要用于代码重构,允许开发者逐步更改类型的名称而不破坏现有的代码

这是一个类型别名的示例:

package mainimport "fmt"// 定义一个新的类型
type MyOriginalInt int// 创建 MyOriginalInt 的别名
type MyIntAlias = MyOriginalIntfunc main() {var a MyOriginalInt = 6var b MyIntAlias = a // 因为是别名,所以这是合法的,其实就是 var b = afmt.Println(a, b) // 输出: 6 6
}

MyIntAlias 是 MyOriginalInt 的别名,所以它们可以互换使用。这意味着 MyIntAlias 的变量可以被视为 MyOriginalInt 类型的变量,反之亦然

类型别名的一个重要用途是在进行大规模重构时,特别是在为类型进行重命名时,它可以帮助保持代码库的向后兼容性。例如,如果一个库的公共类型名称需要更改,可以使用类型别名保持与旧代码的兼容性,同时推进新名称的使用


http://www.ppmy.cn/news/1298635.html

相关文章

【微服务】springcloud集成skywalking实现全链路追踪

目录 一、前言 二、环境准备 2.1 软件环境 2.2 微服务模块 2.3 环境搭建 2.3.1 下载安装包 2.3.2 解压并启动服务 2.3.3 访问web界面 三、搭建springcloud微服务 3.1 顶层公共依赖 3.2 用户服务模块 3.2.1 准备测试使用数据库 3.2.2 添加依赖 3.2.3 添加配置文件 …

golang学习-流程控制

if else 建议条件不用()包裹,if{}不能省略,{}中的{必须紧靠着条件 go语言中没有while循环,可以通过for 代替 age : 30if age > 18 {fmt.Println("我是大人")}//另一种写法if age : 99; age > 18 {fmt.Printf("年龄是%v&…

【温故而知新】JavaScript中内存泄露有那几种

一、概念 在JavaScript中,内存泄漏是指应用程序在不再需要使用某块内存时仍然保持对其的引用,导致内存不能被垃圾回收机制释放,最终导致内存占用过高,性能下降。 内存泄漏通常发生在以下情况: 全局变量:全…

vue element plus 快速开始

本节将介绍如何在项目中使用 Element Plus。 用法# 完整引入# 如果你对打包后的文件大小不是很在乎,那么使用完整导入会更方便。 // main.ts import { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import A…

[足式机器人]Part3 机构运动学与动力学分析与建模 Ch00-2(4) 质量刚体的在坐标系下运动

本文仅供学习使用,总结很多本现有讲述运动学或动力学书籍后的总结,从矢量的角度进行分析,方法比较传统,但更易理解,并且现有的看似抽象方法,两者本质上并无不同。 2024年底本人学位论文发表后方可摘抄 若有…

MATLAB对数据隔位抽取和插值的几种方法

对于串行的数据,有时我们需要转成多路并行的数据进行处理,抽取;或者是需要对数据进行隔点抽取,或对数据进行插值处理。此处以4倍抽取或插值为例,MATLAB代码实现。 文章目录 抽取方法一:downsample函数方法…

源码安装MySQL

目录 在windows端操作: 在Linux端操作: 前提:以配置本地yum仓库 在windows端操作: 第一步:登陆 https://www.mysql.com 网站,访问DOWNLOADS界面 ​ 第二步: 下拉界面找到 MySQL Commu…

构建自己的私人GPT-支持中文

上一篇已经讲解了如何构建自己的私人GPT,这一篇主要讲如何让GPT支持中文。 privateGPT 本地部署目前只支持基于llama.cpp 的 gguf格式模型,GGUF 是 llama.cpp 团队于 2023 年 8 月 21 日推出的一种新格式。它是 GGML 的替代品,llama.cpp 不再…