结构体
结构体是 Go 中的一种复合数据类型,用于将多个不同类型的数据组合在一起,形成一个新的数据类型。日常开发里,用结构体描述「一个人」「一辆车」「一条订单」这类对象非常常见。
定义结构体
用 type + struct 定义一种新类型:
type 结构体名 struct {
字段名 类型
字段名 类型
字段名 类型
}
实操:定义一个 Person,包含姓名、年龄和爱好:
package main
import "fmt"
type Person struct {
Name string
Age int
Hobby []string
}
func main() {
person := Person{
Name: "John",
Age: 20,
Hobby: []string{"reading", "swimming"},
}
fmt.Println(person)
}
Person{...} 是字面量初始化:按字段名赋值,顺序可以随便写,没写的字段会是该类型的零值。
字段读写
通过 . 访问和修改字段:
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "张三", Age: 18}
fmt.Println(p.Name) // 张三
p.Age = 20
fmt.Println(p.Age) // 20
}
几种初始化方式
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// 1. 按字段名初始化(推荐,可读性好)
p1 := Person{Name: "张三", Age: 18}
// 2. 按字段顺序初始化(字段多时容易写错,不推荐)
p2 := Person{"李四", 20}
// 3. 只给部分字段赋值,其余为零值
p3 := Person{Name: "王五"} // Age 为 0
// 4. 先声明再赋值(全部为零值)
var p4 Person
p4.Name = "赵六"
p4.Age = 22
// 5. 取地址,得到 *Person
p5 := &Person{Name: "钱七", Age: 25}
fmt.Println(p1, p2, p3, p4, p5)
}
指针形式 p5 访问字段时,Go 会自动解引用,写 p5.Name 即可,不必写 (*p5).Name。
结构体嵌套
一个结构体里可以包含另一个结构体类型的字段,用来组合更复杂的数据:
package main
import "fmt"
type Car struct {
Brand string
Model string
Year int
}
type Person struct {
Name string
Age int
Hobby []string
Car Car
}
func main() {
person := Person{
Name: "John",
Age: 20,
Hobby: []string{"reading", "swimming"},
Car: Car{
Brand: "Toyota",
Model: "Camry",
Year: 2020,
},
}
fmt.Println(person)
fmt.Println(person.Car.Brand) // Toyota
}
这里 Car 是命名字段,访问时要写 person.Car.Brand。
结构体嵌入
如果字段类型前不写字段名,就是嵌入(匿名字段)。嵌入后,被嵌入类型的字段和方法会「提升」到外层,可以直接用:
package main
import "fmt"
type Car struct {
Brand string
Model string
Year int
}
type Person struct {
Name string
Age int
Car // 嵌入:没有字段名,只有类型
}
func main() {
person := Person{
Name: "John",
Age: 20,
Car: Car{
Brand: "Toyota",
Model: "Camry",
Year: 2020,
},
}
// 提升后可以直接访问
fmt.Println(person.Brand) // Toyota
// 也可以走完整路径
fmt.Println(person.Car.Model) // Camry
}
嵌套是「有名字的组合」;嵌入是「把另一个类型的能力提升过来」。两者都能组合数据,嵌入更适合表达「is-a / 带有某种能力」的关系。
方法
方法是「绑在某个类型上的函数」。接收者写在 func 和函数名之间:
func (接收者 类型) 方法名(参数列表) 返回值 {
// ...
}
值接收者
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) Introduce() {
fmt.Printf("我叫 %s,今年 %d 岁\n", p.Name, p.Age)
}
func main() {
p := Person{Name: "张三", Age: 18}
p.Introduce() // 我叫 张三,今年 18 岁
}
值接收者拿到的是副本,方法里改字段不会影响原变量。
指针接收者
需要修改结构体本身时,用指针接收者:
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p *Person) GrowUp() {
p.Age++
}
func (p Person) Introduce() {
fmt.Printf("我叫 %s,今年 %d 岁\n", p.Name, p.Age)
}
func main() {
p := Person{Name: "张三", Age: 18}
p.GrowUp()
p.Introduce() // 我叫 张三,今年 19 岁
}
调用时写 p.GrowUp() 即可,Go 会自动取地址。习惯上:要改状态用指针接收者;只读、小结构体可以用值接收者。同一类型的方法最好统一风格,不要有的用值、有的用指针混着来。
匿名结构体
不提前 type 定义,直接在用的地方写 struct { ... },适合只临时用一次的场景:
package main
import "fmt"
func main() {
person := struct {
Name string
Age int
}{
Name: "John",
Age: 20,
}
fmt.Println(person.Name, person.Age)
}
配置、单次返回、测试数据等「用完就丢」的结构,用匿名结构体很方便。若多处复用,还是起个名字更清晰。
泛型结构体
Go 1.18 起,结构体也可以带类型参数:
package main
import "fmt"
type Person[T string | int] struct {
Name string
Age int
Phone T
}
func main() {
person := Person[string]{
Name: "John",
Age: 20,
Phone: "010-10086",
}
person2 := Person[int]{
Name: "John",
Age: 20,
Phone: 10086,
}
fmt.Println(person)
fmt.Println(person2)
}
Person[T string | int] 表示 Phone 只能是 string 或 int。创建时要写明具体类型,如 Person[string]、Person[int]。
作业
- 定义与初始化:定义
Book结构体(书名、作者、页数),用至少两种方式创建实例并打印。 - 嵌套:定义
Address(城市、街道)和User(姓名、地址),创建用户并打印「姓名住在城市的街道」。 - 嵌入:把上一题的
Address改成嵌入到User,用提升后的字段直接打印城市。 - 方法:给
Book写值接收者方法Info()打印信息;再写指针接收者方法AddPages(n int)增加页数,调用后验证页数有变。 - 综合:定义
Rectangle(宽、高),写方法Area()和Perimeter()返回面积和周长,并在main里验证。