什么是结构体
在 Go 语言中, 并没有类/对象/继承等经典面向对象语言概念, 如: class extends 等关键字都是没有的
结构体类似于C语言中的结构体, 用于描述一组数据的结构, 将这一组数据看做一个整体, 一个自定义的数据类型
虽然没有经典的 calss 等关键字, 但是使用 struct 可以模拟面向对象的部分特性(封装 继承 多态)
定义结构体
type 结构体名称 struct { 结构体字段 结构体字段的数据类型 }
go
package main
import "fmt"
// 定义一个描述学生数据的结构体
// 将这一组数据看做一个整体(一个独立的类型)
type Student struct {
id string // 学号
name string // 姓名
age int // 年龄
subject string // 专业
}
func main() {
// 初始化结构体
zs := Student{
id: "1001",
name: "张三",
age: 18,
subject: "计算机网络与应用",
}
// 值:{1001 张三 18 计算机网络与应用} 类型:main.Student
fmt.Printf("值:%v \t 类型:%T \n", zs, zs)
}初始化结构体
- 直接初始化结构体并赋值
- 初始化多个结构体并赋值
- 使用
new方法初始化结构体
go
package main
import "fmt"
type Student struct {
id string // 学号
name string // 姓名
age int // 年龄
subject string // 专业
}
func main() {
// 1.直接初始化结构体并赋值
zs := Student{
id: "1001",
name: "张三",
age: 18,
subject: "计算机网络与应用",
}
// 值:{1001 张三 18 计算机网络与应用} 类型:main.Student
fmt.Printf("值:%v \t 类型:%T \n", zs, zs)
// 2.初始化多个结构体
studentList := []Student{
{
id: "1002",
name: "李四",
age: 19,
subject: "计算机网络与应用",
},
{
id: "1003",
name: "王五",
age: 20,
subject: "计算机网络与应用",
},
}
// 值:[{1002 李四 19 计算机网络与应用} {1003 王五 20 计算机网络与应用}]
// 类型:[]main.Student
fmt.Printf("值:%v \t 类型:%T \n", studentList, studentList)
// 3.使用 new 方法初始化结构体
// 返回的是一个指针,指向一个初始化后的结构体
z6 := new(Student)
z6.id = "1004"
z6.name = "赵六"
z6.age = 21
z6.subject = "计算机网络"
// 值:&{1004 赵六 21 计算机网络} 类型:*main.Student
fmt.Printf("值:%v \t 类型:%T \n", z6, z6)
}结构体的存储原理
unsafe.Sizeof可以获取一个结构体的占用空间大小
go
package main
import (
"fmt"
"unsafe"
)
type A struct {
a int8 // 1 byte
b bool // 1 byte
c int32 // 4 byte
}
type B struct {
a int8 // 1 byte
b int32 // 4 byte
c bool // 1 byte
}
func main() {
a := A{1, true, 2}
fmt.Println("a 占用内存大小:", unsafe.Sizeof(a)) // 8
b := B{1, 2, true}
fmt.Println("b 占用内存大小:", unsafe.Sizeof(b)) // 12
}😡
为什么同样的结构体, 仅仅是字段名和类型的顺序不同, 占用内存大小就不一样了?
- 开辟 4 个字节的内存空间, 如果字段能够存下则直接存
- 如果存不下, 那么就在再次辟 4 个字节的内存空间

🤔
为什么固定开辟4个直接内存空间, 而不是根据数据类型动态的开辟刚刚好所需要的内存空间
为了内存对齐
🤔
为什么需要内存对齐?
- 性能提升: 现代CPU将内存视为块(例如 4、8、16字节)访问, 访问对齐数据只需一次内存操作, 访问未对齐数据可能需要两次, 并进行额外处理
- 硬件要求: 某些硬件平台(如ARM)强制要求数据必须对齐, 否则会引发硬件异常, 导致程序崩溃
嵌套的结构体
go
package main
import "fmt"
type Address struct {
province string // 省份
city string // 城市
area string // 区域
detail string // 街道楼层门牌号详情
}
type LogisticsInfo struct {
orderId int // 订单id
productName string // 商品名称
address Address // 收件人地址信息
}
func main() {
info := LogisticsInfo{
orderId: 1001,
productName: "HUAWEI Meta80 Pro",
address: Address{
province: "湖南省",
city: "衡阳市",
area: "蒸湘区",
detail: "解放路幸福小区5栋2单元2801室",
},
}
// 值:{1001 HUAWEI Meta80 Pro {湖南省 衡阳市 蒸湘区 解放路幸福小区5栋2单元2801室}}
// 类型:main.LogisticsInfo
fmt.Printf("值:%v \n类型:%T \n", info, info)
}给结构体绑定方法
注意点
由于在Go语言中, 默认情况下 struct 是值传递, 要改变结构体的值必须引用传递(也就是必须传递指针)
go
package main
import "fmt"
type Student struct {
id int
name string
age int
}
// 获取值: 使用值/引用都可以, 因为都可以获取到
// 虽然可以可以通过值来获取值, 但为了风格统一
// 还是推荐使用: 引用传递的方式来获取值
func (s Student) getId() int {
return s.id
}
func (s *Student) getName() string {
// 推荐
return s.name
}
// 修改值: 必须使用引用传递
func (s Student) setName1(name string) {
// 错误示范
s.name = name
}
func (s *Student) setName(name string) {
s.name = name
}
func main() {
zs := Student{id: 1001, name: "张三", age: 18}
//// 读 ///
id := zs.getId()
fmt.Printf("id:%d\n", id)
name := zs.getName()
fmt.Printf("name:%s\n", name)
//// 写 ///
// 并没有改变, 因为是值传递, 函数修改的是"副本"
zs.setName1("张仨")
fmt.Printf("数据:%v 类型:%T\n", zs, zs)
// name字段被修改, 因为是引用传递, 函数修改的是"原件"
zs.setName("张3")
fmt.Printf("数据:%v 类型:%T\n", zs, zs)
}泛型结构体
与泛型函数类似, 在定义时并不确定类型的具体类型, 只有在初始化时才确定字段的具体类型
go
package main
import "fmt"
// 栈 数据结构
type Stack[T any] struct {
elements []T
}
func (s *Stack[T]) Size() int {
return len(s.elements)
}
func (s *Stack[T]) Push(value T) {
s.elements = append(s.elements, value)
}
func (s *Stack[T]) Pop() T {
lastIndex := s.Size() - 1
lastElement := s.elements[lastIndex]
s.elements = s.elements[:lastIndex]
return lastElement
}
func main() {
s := Stack[int]{}
s.Push(1)
s.Push(2)
s.Push(3)
fmt.Printf("数据:%v \n", s)
fmt.Printf("类型:%T \n", s)
fmt.Println(s.Pop())
fmt.Println(s.Pop())
fmt.Println(s.Pop())
}go
package main
import "fmt"
type Node[T any] struct {
value T
prev *Node[T]
next *Node[T]
}
// 双向链表 数据结构
type LinkedList[T any] struct {
head *Node[T]
tail *Node[T]
}
func (ll *LinkedList[T]) Append(value T) {
newNode := &Node[T]{value: value}
if ll.head == nil {
// 空链表, 新节点既是头也是尾
ll.head = newNode
ll.tail = newNode
return
}
// 将新节点添加到尾部
ll.tail.next = newNode
newNode.prev = ll.tail
ll.tail = newNode
}
func (ll *LinkedList[T]) ToString() string {
if ll.head == nil {
return "[]"
}
str := ""
node := ll.head
// 处理第一个节点
str += fmt.Sprintf("[%v]", node.value)
node = node.next
// 处理后续节点
for node != nil {
str += fmt.Sprintf(" -> [%v]", node.value)
node = node.next
}
return str
}
func main() {
list := &LinkedList[int]{}
list.Append(2)
list.Append(4)
list.Append(6)
fmt.Println(list.ToString()) // 输出: [2] -> [4] -> [6]
}