Go 入门(04):结构体与字段——定义、初始化、嵌套
更新时间:2026-09-01。本文是
languages/go/beginner/入门层第 4 篇,接 数组、切片与 map。struct 是 Go 里组合数据的基础,没有 class、没有继承,靠 struct + 方法 + 接口组合出你要的类型。本篇讲基础。
本文要回答的问题
- Go 怎么定义结构体?字段声明语法是什么?
- 结构体初始化有几种方式?零值机制在这里怎么工作?
- 结构体嵌套怎么写?为什么 Go 没有继承?
- 结构体赋值是值拷贝吗?什么时候用指针?
一、基本定义
Go 用 type ... struct 定义结构体:
type User struct {
Name string
Age int
}语法:
type 名字 struct定义新类型- 字段是"名字 类型",同一类型可以写一行
Name, Nickname string - 首字母大写 = 导出(包外可见,可访问),小写 = 包内私有——这就是 Go 的访问控制
创建结构体变量:
// 方式一:var 声明,自动零值初始化
var u User // Name = "", Age = 0
// 方式二:字面量初始化
u1 := User{Name: "Alice", Age: 25} // 带字段名,顺序无所谓
u2 := User{"Bob", 20} // 不带字段名,必须按声明顺序(不推荐,可读性差)
// 方式三:指针
u3 := new(User) // *User,指向零值初始化的 User
u3.Name = "Charlie"二、零值机制再回顾
结构体的零值是:每个字段都被初始化为该类型的零值。所以即使你不赋值,每个字段也有合法值,不会是垃圾:
type Config struct {
Port int
Enable bool
Path string
}
var c Config
// c.Port = 0, c.Enable = false, c.Path = ""
// 全部合法,没有未初始化垃圾这就是 Go 零值设计的好处:struct 声明就能用,很多时候连构造函数都不需要。
三、访问字段与修改
用 . 访问字段:
u := User{Name: "Alice", Age: 25}
fmt.Println(u.Name) // Alice
u.Age = 26 // 修改指针访问字段不用写 ->,Go 自动解引用:
p := &u
fmt.Println(p.Name) // 直接写 .Name,Go 自动解引用,不用 p->Name
p.Age = 27比 C 方便,少写一个箭头。
四、结构体嵌套
Go 没有继承,但支持结构体嵌套(也叫嵌入),可以实现"继承"的部分效果:
type Person struct {
Name string
Age int
}
type Student struct {
Person // 嵌入 Person,没有名字
School string
}嵌入后,外层结构体直接"继承"内层字段,可以直接访问:
s := Student{
Person: Person{Name: "Alice", Age: 20},
School: "PKU",
}
fmt.Println(s.Name) // 直接访问,不用 s.Person.Name
fmt.Println(s.Person.Name) // 也可以这么写嵌入和普通字段的区别:
- 普通字段:
Per Person→ 必须通过s.Per.Name访问 - 嵌入:
Person→ 字段直接提升到外层,可以s.Name直接访问
Go 的设计哲学:用组合代替继承,嵌入就是组合在语法上的支持。
五、结构体是值类型
Go 的结构体是值类型,赋值会完整拷贝:
u1 := User{Name: "Alice", Age: 25}
u2 := u1 // 完整拷贝,两个独立副本
u2.Name = "Bob"
fmt.Println(u1.Name) // Alice,不受影响
fmt.Println(u2.Name) // Bob如果你想共享同一个结构体,修改能让调用者看到,就得用指针:
func modifyAge(u *User, newAge int) {
u.Age = newAge // 修改的是原结构体
}
modifyAge(&u1, 30)
fmt.Println(u1.Age) // 30这和 C 的值传递语义完全一致:
- 值传递 → 拷贝一份,函数改副本不影响原变量
- 传指针 → 拷贝指针,指向同一个结构体,可以修改原结构体
六、结构体比较
如果结构体所有字段都是可比较的,那么结构体也是可比较的:
a := User{Name: "Alice", Age: 25}
b := User{Name: "Alice", Age: 25}
fmt.Println(a == b) // true,所有字段相等如果结构体里有不可比较的字段(比如 slice、map),那么整个结构体不可比较,不能用 ==。
七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 字面量不写字段名 | 加字段顺序变了,代码直接错 | 总是写字段名 Name: |
| 嵌套误以为是继承 | Go 就是组合,不是继承,方法不会"重写" | 理解组合语义 |
| 大结构体传值 | 每次调用都拷贝一大块,性能差 | 大结构体传指针 |
| 修改原结构体不传指针 | 函数里改了,外面没变化 | 需要修改就传指针 |
相关与延伸
下一篇:指针——Go 指针 vs C 指针;结构体加方法就是面向对象的基础,见 方法——值接收者 vs 指针接收者;结构体内存布局和对齐,进阶层会讲,对应 C 结构体对齐。
一句话总结
Go 结构体:type Name struct { 字段 } 定义,首字母大写导出;声明即零值,每个字段都是合法值;指针访问自动解引用,不用 ->;用嵌入实现组合代替继承;结构体是值类型,赋值拷贝,要修改共享就传指针。