vault backup: 2026-06-07 12:14:39
This commit is contained in:
@@ -1,118 +1,151 @@
|
||||
# Go语言结构体
|
||||
---
|
||||
tags: [go, golang, go基础语法, 结构体]
|
||||
create time: 2026-06-07 15:00
|
||||
---
|
||||
|
||||
有时候内置的基本类型并不能满足我们的业务需求,我们需要一些复合结构。比如我们想要描述一个学生,1个学生既有学号,年龄,性别,分数等这些属性,而单一的数据类型往往只能描述其中一个属性,我们想要描述这个学生,就需要把这些属性都要描述出来,这个时候就需要用到结构体了。
|
||||
# Go 语言结构体
|
||||
|
||||
## 概述
|
||||
|
||||
Go 没有类(class),但通过**结构体(struct)**实现数据的组合。本文介绍结构体的定义、初始化、成员访问,以及嵌入(匿名字段)机制。
|
||||
|
||||
## 正文
|
||||
|
||||
### 为什么 Go 不用 class?
|
||||
|
||||
在 Java / C++ 中,你用 `class` 同时定义**数据**和**行为**。Go 把它们拆开了:`struct` 只负责数据,`method`(方法)是后来绑定到 struct 上的。这种设计让数据结构和方法可以独立演化。
|
||||
|
||||
> [!question] ❓ 思考
|
||||
> 如果一个 Student 有 Name、Age、Score 三个属性,用基本类型能表示吗?为什么需要组合?
|
||||
|
||||
### 定义与初始化
|
||||
|
||||
#### 定义
|
||||
|
||||
## 结构体定义
|
||||
像很多其他的高级语言一样,go语言也支持结构体来定义复合数据类型
|
||||
定义方式如下:
|
||||
```go
|
||||
type Student struct {
|
||||
ID int
|
||||
Name string
|
||||
Age int
|
||||
ID int
|
||||
Name string
|
||||
Age int
|
||||
Score int
|
||||
}
|
||||
}
|
||||
```
|
||||
上述方法定义了一个`Student`类型的结构体,`Student`包含四个属性,分别是`string`类型的`Name`和`int`类型的`ID`,`Age`和`Score`。
|
||||
|
||||
## 结构体初始化
|
||||
### 键值对初始化
|
||||
在初始化的时候以属性:值的方式完成,如果有的属性不写,则为默认值
|
||||
字段声明格式为 `名称 类型`,同类型的可以合并:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Student struct {
|
||||
ID int
|
||||
Name string
|
||||
Age int
|
||||
ID int
|
||||
Name string
|
||||
Age int
|
||||
Score int
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
st := Student{
|
||||
ID : 100,
|
||||
Name : "zhangsan",
|
||||
Age : 18,
|
||||
Score : 98,
|
||||
}
|
||||
fmt.Printf("学生st: %v\n", st)
|
||||
// 等价于
|
||||
type Student struct {
|
||||
ID, Age int
|
||||
Name string
|
||||
Score int
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
学生st: {100 zhangsan 18 98}
|
||||
```
|
||||
|
||||
### 值列表初始化
|
||||
在初始化的时候直接按照属性顺序以属性值来初始化,看下面例子
|
||||
#### 初始化方式
|
||||
|
||||
**键值对初始化**(推荐,清晰明了):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Student struct {
|
||||
ID int
|
||||
Name string
|
||||
Age int
|
||||
Score int
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
st := Student{
|
||||
101,
|
||||
"lisi",
|
||||
20,
|
||||
97,
|
||||
}
|
||||
fmt.Printf("学生st: %v\n", st)
|
||||
st := Student{
|
||||
ID: 100,
|
||||
Name: "zhangsan",
|
||||
Age: 18,
|
||||
Score: 98,
|
||||
}
|
||||
fmt.Printf("%v\n", st) // {100 zhangsan 18 98}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
学生st: {101 lisi 20 97}
|
||||
```
|
||||
**注意:以值列表的方式初始化结构体,值列表的个数必须等于结构体属性个数,且要按顺序,否则会报错**
|
||||
|
||||
## 结构体成员访问
|
||||
使用点号 `.` 操作符来访问结构体的成员,`.` 前可以是结构体变量或者结构体指针
|
||||
**位置初始化**(按字段顺序,字段少时可用):
|
||||
|
||||
```go
|
||||
package main
|
||||
st := Student{101, "lisi", 20, 97}
|
||||
```
|
||||
|
||||
import "fmt"
|
||||
> [!tip] 💡 零值初始化
|
||||
> 只写 `Student{}` 会创建一个所有字段为零值的结构体:
|
||||
> ```go
|
||||
> st := Student{} // {0 "" 0 0}
|
||||
> ```
|
||||
|
||||
type Student struct {
|
||||
ID int
|
||||
Name string
|
||||
Age int
|
||||
Score int
|
||||
**new() vs 直接初始化**:
|
||||
|
||||
```go
|
||||
st1 := &Student{} // ✅ 推荐:简洁,可直接赋值字段
|
||||
st2 := new(Student) // 等价于 &Student{},返回 *Student
|
||||
```
|
||||
|
||||
> [!note] 📝 new(T) 的作用
|
||||
> `new(T)` 分配零值内存并返回 `*T`。它与 `&T{}` 功能等价,但后者更灵活——可以直接内联初始化部分字段。
|
||||
|
||||
### 成员访问
|
||||
|
||||
使用 `.` 操作符。指针类型的 struct 也可以直接用 `.` 访问字段(Go 自动解引用):
|
||||
|
||||
```go
|
||||
st := Student{Name: "zhangsan", Age: 18}
|
||||
fmt.Println(st.Name) // zhangsan
|
||||
|
||||
ptr := &st
|
||||
fmt.Println(ptr.Name) // zhangsan(Go 自动解引用,等价于 (*ptr).Name)
|
||||
```
|
||||
|
||||
### 结构体嵌套(组合)
|
||||
|
||||
Go 没有继承,但通过**组合**实现类似效果:
|
||||
|
||||
```go
|
||||
type Address struct {
|
||||
City string
|
||||
Postal string
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Address // 匿名嵌入(anonymous field)
|
||||
}
|
||||
|
||||
func main() {
|
||||
st1 := Student{
|
||||
ID : 100,
|
||||
Name : "zhangsan",
|
||||
Age : 18,
|
||||
Score : 98,
|
||||
}
|
||||
fmt.Printf("学生1的姓名是: %s\n", st1.Name)
|
||||
|
||||
st2 := &Student{
|
||||
ID : 101,
|
||||
Name : "lisi",
|
||||
Age : 20,
|
||||
Score : 97,
|
||||
}
|
||||
fmt.Printf("学生2的分数是: %d\n", st2.Score)
|
||||
}
|
||||
```
|
||||
运行结果:
|
||||
```
|
||||
学生1的姓名是: zhangsan
|
||||
学生2的分数是: 97
|
||||
p := Person{
|
||||
Name: "Alice",
|
||||
Address: Address{City: "Beijing"},
|
||||
}
|
||||
fmt.Println(p.City) // 直接访问嵌入字段!
|
||||
```
|
||||
|
||||
> [!info] ℹ️ 嵌入 vs 普通字段
|
||||
> - **匿名嵌入**(无字段名):外层可以直接访问内层字段,模拟"继承"
|
||||
> - **命名字段**:需要通过字段名访问,如 `p.Address.City`
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A["Person"] --> B["Name: string"]
|
||||
A --> C["Age: int"]
|
||||
A --> D["Address ← 嵌入"]
|
||||
D --> E["City: string"]
|
||||
D --> F["Postal: string"]
|
||||
```
|
||||
|
||||
### 结构体作为函数参数
|
||||
|
||||
> [!warning] ⚠️ 值拷贝陷阱
|
||||
> Go 的结构体是**值类型**,传入函数时会被完整拷贝。修改函数内的参数不会影响原始 struct。
|
||||
|
||||
```go
|
||||
func ChangeName(s Student) {
|
||||
s.Name = "modified" // 只修改了副本
|
||||
}
|
||||
|
||||
func ChangeNamePtr(s *Student) {
|
||||
s.Name = "modified" // 通过指针修改原值
|
||||
}
|
||||
```
|
||||
|
||||
> [!tip] 💡 大结构体传指针
|
||||
> 结构体较大时,传递 `*T` 而非 `T` 可以避免不必要的内存拷贝。即使你不打算修改它,传指针也是常见的性能优化手段。
|
||||
|
||||
Reference in New Issue
Block a user