1. 什么是结构体
结构体是Go语言中一种重要的复合数据类型,它可以将多个不同类型的数据组合在一起,形成一个有意义的整体。可以把结构体理解为现实世界中事物的抽象表示。
1.1 结构体的基本概念
想象我们要描述一个学生的信息:
- 姓名:字符串类型
- 年龄:整数类型
- 成绩:浮点数类型
- 是否在校:布尔类型
如果没有结构体,我们需要分别定义多个变量:
go
var name string = "张三"
var age int = 18
var score float64 = 95.5
var isEnrolled bool = true
这样很不方便管理和使用。而结构体可以将这些相关数据组织在一起:
go
type Student struct {
Name string
Age int
Score float64
IsEnrolled bool
}
1.2 为什么需要结构体
结构体的主要优势:
- 数据聚合:将相关的数据组织在一起
- 代码清晰:提高代码的可读性和可维护性
- 类型安全:创建专门的数据类型
- 面向对象:为后续的方法和接口打基础
2. 结构体的定义和声明
2.1 基本定义语法
go
// 结构体定义的基本语法
type 结构体名 struct {
字段名1 数据类型1
字段名2 数据类型2
字段名3 数据类型3
// ... 更多字段
}
2.2 结构体定义示例
go
package main
import "fmt"
// 定义学生结构体
type Student struct {
Name string // 姓名
Age int // 年龄
Score float64 // 成绩
IsEnrolled bool // 是否在校
}
// 定义图书结构体
type Book struct {
Title string // 书名
Author string // 作者
Price float64 // 价格
Pages int // 页数
ISBN string // ISBN编号
}
// 定义坐标点结构体
type Point struct {
X int // X坐标
Y int // Y坐标
}
func main() {
fmt.Println("结构体定义完成")
}
2.3 匿名结构体
go
package main
import "fmt"
func main() {
// 匿名结构体定义和使用
person := struct {
Name string
Age int
}{
Name: "李四",
Age: 25,
}
fmt.Printf("匿名结构体:%+v\n", person)
fmt.Printf("姓名:%s,年龄:%d\n", person.Name, person.Age)
// 匿名结构体切片
people := []struct {
Name string
City string
}{
{"王五", "北京"},
{"赵六", "上海"},
{"孙七", "广州"},
}
fmt.Println("人员列表:")
for _, p := range people {
fmt.Printf("姓名:%s,城市:%s\n", p.Name, p.City)
}
}
运行结果:
bash
匿名结构体:{Name:李四 Age:25}
姓名:李四,年龄:25
人员列表:
姓名:王五,城市:北京
姓名:赵六,城市:上海
姓名:孙七,城市:广州
3. 结构体的实例化
3.1 各种创建方式
go
package main
import "fmt"
type Student struct {
Name string
Age int
Score float64
}
func main() {
// 方式1:完整字段初始化
student1 := Student{
Name: "张三",
Age: 18,
Score: 95.5,
}
// 方式2:按字段顺序初始化(必须指定所有字段)
student2 := Student{"李四", 19, 88.0}
// 方式3:部分字段初始化(必须指定字段名)
student3 := Student{
Name: "王五",
Age: 20,
// Score使用默认值0.0
}
// 方式4:使用new函数创建(所有字段为零值)
student4 := new(Student)
student4.Name = "赵六"
student4.Age = 21
student4.Score = 92.5
// 方式5:使用&创建并初始化
student5 := &Student{
Name: "孙七",
Age: 17,
Score: 87.5,
}
fmt.Printf("学生1:%+v\n", student1)
fmt.Printf("学生2:%+v\n", student2)
fmt.Printf("学生3:%+v\n", student3)
fmt.Printf("学生4:%+v\n", *student4)
fmt.Printf("学生5:%+v\n", *student5)
}
运行结果:
bash
学生1:{Name:张三 Age:18 Score:95.5}
学生2:{Name:李四 Age:19 Score:88}
学生3:{Name:王五 Age:20 Score:0}
学生4:{Name:赵六 Age:21 Score:92.5}
学生5:{Name:孙七 Age:17 Score:87.5}
3.2 结构体的零值
go
package main
import "fmt"
type Person struct {
Name string
Age int
Height float64
IsActive bool
Scores []int
Info map[string]string
}
func main() {
// 声明结构体变量但不初始化
var person Person
fmt.Printf("结构体零值:%+v\n", person)
fmt.Printf("字符串零值:%q\n", person.Name)
fmt.Printf("整数零值:%d\n", person.Age)
fmt.Printf("浮点数零值:%f\n", person.Height)
fmt.Printf("布尔零值:%t\n", person.IsActive)
fmt.Printf("切片零值:%v,是否为nil:%t\n", person.Scores, person.Scores == nil)
fmt.Printf("map零值:%v,是否为nil:%t\n", person.Info, person.Info == nil)
}
运行结果:
bash
结构体零值:{Name: Age:0 Height:0 IsActive:false Scores:[] Info:map[]}
字符串零值:""
整数零值:0
浮点数零值:0.000000
布尔零值:false
切片零值:[],是否为nil:true
map零值:map[],是否为nil:true
4. 结构体字段访问和修改
4.1 字段访问
go
package main
import "fmt"
type Employee struct {
Name string
Age int
Salary float64
Department string
}
func main() {
// 创建员工实例
emp := Employee{
Name: "张三",
Age: 28,
Salary: 15000.0,
Department: "技术部",
}
// 访问字段
fmt.Printf("员工姓名:%s\n", emp.Name)
fmt.Printf("员工年龄:%d\n", emp.Age)
fmt.Printf("员工薪资:%.2f\n", emp.Salary)
fmt.Printf("所在部门:%s\n", emp.Department)
// 修改字段值
emp.Age = 29
emp.Salary = 16000.0
emp.Department = "研发部"
fmt.Printf("\n修改后信息:\n")
fmt.Printf("员工姓名:%s\n", emp.Name)
fmt.Printf("员工年龄:%d\n", emp.Age)
fmt.Printf("员工薪资:%.2f\n", emp.Salary)
fmt.Printf("所在部门:%s\n", emp.Department)
}
运行结果:
bash
员工姓名:张三
员工年龄:28
员工薪资:15000.00
所在部门:技术部
修改后信息:
员工姓名:张三
员工年龄:29
员工薪资:16000.00
所在部门:研发部
4.2 指针访问结构体字段
go
package main
import "fmt"
type Product struct {
Name string
Price float64
Stock int
}
func main() {
// 普通结构体变量
product1 := Product{Name: "手机", Price: 3999.0, Stock: 100}
// 结构体指针
product2 := &Product{Name: "电脑", Price: 8999.0, Stock: 50}
// 访问方式1:通过变量名直接访问
fmt.Printf("产品1 - 名称:%s,价格:%.2f,库存:%d\n",
product1.Name, product1.Price, product1.Stock)
// 访问方式2:通过指针访问(Go自动解引用)
fmt.Printf("产品2 - 名称:%s,价格:%.2f,库存:%d\n",
product2.Name, product2.Price, product2.Stock)
// 访问方式3:显式解引用
fmt.Printf("产品2显式解引用 - 名称:%s,价格:%.2f,库存:%d\n",
(*product2).Name, (*product2).Price, (*product2).Stock)
// 修改值
product1.Price = 3599.0
product2.Stock = 75 // 等价于 (*product2).Stock = 75
fmt.Printf("\n修改后:\n")
fmt.Printf("产品1价格:%.2f\n", product1.Price)
fmt.Printf("产品2库存:%d\n", product2.Stock)
}
运行结果:
bash
产品1 - 名称:手机,价格:3999.00,库存:100
产品2 - 名称:电脑,价格:8999.00,库存:50
产品2显式解引用 - 名称:电脑,价格:8999.00,库存:50
修改后:
产品1价格:3599.00
产品2库存:75
5. 结构体嵌套
5.1 基本嵌套
go
package main
import "fmt"
// 地址结构体
type Address struct {
Province string
City string
Street string
ZipCode string
}
// 公司结构体
type Company struct {
Name string
Address Address // 嵌套Address结构体
}
// 员工结构体
type Employee struct {
Name string
Age int
Company Company // 嵌套Company结构体
}
func main() {
// 创建嵌套结构体
employee := Employee{
Name: "张三",
Age: 30,
Company: Company{
Name: "科技有限公司",
Address: Address{
Province: "北京市",
City: "北京市",
Street: "中关村大街1号",
ZipCode: "100080",
},
},
}
// 访问嵌套字段
fmt.Printf("员工姓名:%s\n", employee.Name)
fmt.Printf("员工年龄:%d\n", employee.Age)
fmt.Printf("公司名称:%s\n", employee.Company.Name)
fmt.Printf("公司省份:%s\n", employee.Company.Address.Province)
fmt.Printf("公司城市:%s\n", employee.Company.Address.City)
fmt.Printf("公司街道:%s\n", employee.Company.Address.Street)
fmt.Printf("邮政编码:%s\n", employee.Company.Address.ZipCode)
// 修改嵌套字段
employee.Company.Address.Street = "中关村大街2号"
fmt.Printf("\n修改后街道:%s\n", employee.Company.Address.Street)
}
运行结果:
bash
员工姓名:张三
员工年龄:30
公司名称:科技有限公司
公司省份:北京市
公司城市:北京市
公司街道:中关村大街1号
邮政编码:100080
修改后街道:中关村大街2号
5.2 匿名嵌套(匿名字段)
go
package main
import "fmt"
type Person struct {
Name string
Age int
}
type Student struct {
Person // 匿名嵌套Person结构体
School string
Grade int
}
type Teacher struct {
Person // 匿名嵌套Person结构体
Subject string
Salary float64
}
func main() {
// 创建学生
student := Student{
Person: Person{Name: "小明", Age: 18},
School: "第一中学",
Grade: 12,
}
// 创建老师
teacher := Teacher{
Person: Person{Name: "王老师", Age: 35},
Subject: "数学",
Salary: 8000.0,
}
// 访问提升字段(可以直接访问嵌套结构体的字段)
fmt.Printf("学生姓名:%s\n", student.Name) // 等价于 student.Person.Name
fmt.Printf("学生年龄:%d\n", student.Age) // 等价于 student.Person.Age
fmt.Printf("学校:%s\n", student.School)
fmt.Printf("年级:%d\n", student.Grade)
fmt.Printf("\n老师姓名:%s\n", teacher.Name) // 等价于 teacher.Person.Name
fmt.Printf("老师年龄:%d\n", teacher.Age) // 等价于 teacher.Person.Age
fmt.Printf("科目:%s\n", teacher.Subject)
fmt.Printf("薪资:%.2f\n", teacher.Salary)
// 修改提升字段
student.Age = 19
teacher.Name = "李老师"
fmt.Printf("\n修改后学生年龄:%d\n", student.Age)
fmt.Printf("修改后老师姓名:%s\n", teacher.Name)
}
运行结果:
bash
学生姓名:小明
学生年龄:18
学校:第一中学
年级:12
老师姓名:王老师
老师年龄:35
科目:数学
薪资:8000.00
修改后学生年龄:19
修改后老师姓名:李老师
6. 结构体标签(Struct Tags)
6.1 基本标签使用
go
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
UserName string `json:"username"`
Email string `json:"email"`
Password string `json:"-"` // -表示忽略此字段
Active bool `json:"active,omitempty"` // omitempty表示空值时忽略
}
func main() {
user := User{
ID: 1,
UserName: "zhangsan",
Email: "zhangsan@example.com",
Password: "123456",
Active: true,
}
// 序列化为JSON
jsonData, err := json.Marshal(user)
if err != nil {
fmt.Printf("序列化错误:%v\n", err)
return
}
fmt.Printf("JSON数据:%s\n", jsonData)
// 反序列化
var newUser User
jsonStr := `{"id":2,"username":"lisi","email":"lisi@example.com","active":false}`
err = json.Unmarshal([]byte(jsonStr), &newUser)
if err != nil {
fmt.Printf("反序列化错误:%v\n", err)
return
}
fmt.Printf("反序列化结果:%+v\n", newUser)
}
运行结果:
bash
JSON数据:{"id":1,"username":"zhangsan","email":"zhangsan@example.com","active":true}
反序列化结果:{ID:2 UserName:lisi Email:lisi@example.com Password: Active:false}
6.2 多个标签示例
go
package main
import (
"encoding/json"
"fmt"
)
type Product struct {
ID int `json:"id" db:"product_id"`
Name string `json:"name" db:"product_name"`
Price float64 `json:"price" db:"price"`
Description string `json:"description,omitempty" db:"description"`
Category string `json:"category" db:"category"`
}
func main() {
products := []Product{
{
ID: 1,
Name: "iPhone 15",
Price: 5999.0,
Description: "最新款苹果手机",
Category: "电子产品",
},
{
ID: 2,
Name: "MacBook Pro",
Price: 12999.0,
// Description为空,会被omitempty忽略
Category: "电脑",
},
}
// JSON序列化
jsonData, _ := json.MarshalIndent(products, "", " ")
fmt.Printf("JSON格式数据:\n%s\n", jsonData)
}
运行结果:
bash
JSON格式数据:
[
{
"id": 1,
"name": "iPhone 15",
"price": 5999,
"description": "最新款苹果手机",
"category": "电子产品"
},
{
"id": 2,
"name": "MacBook Pro",
"price": 12999,
"category": "电脑"
}
]
7. 结构体比较
7.1 结构体相等性比较
go
package main
import "fmt"
type Point struct {
X, Y int
}
type Person struct {
Name string
Age int
Data []int // 切片字段
}
func main() {
// 可比较的结构体
p1 := Point{1, 2}
p2 := Point{1, 2}
p3 := Point{3, 4}
fmt.Printf("p1 == p2: %t\n", p1 == p2)
fmt.Printf("p1 == p3: %t\n", p1 == p3)
// 包含不可比较字段的结构体
person1 := Person{Name: "张三", Age: 20, Data: []int{1, 2, 3}}
person2 := Person{Name: "张三", Age: 20, Data: []int{1, 2, 3}}
// 以下代码会编译错误,因为包含切片字段
// fmt.Printf("person1 == person2: %t\n", person1 == person2)
// 手动比较
equal := person1.Name == person2.Name &&
person1.Age == person2.Age &&
len(person1.Data) == len(person2.Data)
if equal {
for i := range person1.Data {
if person1.Data[i] != person2.Data[i] {
equal = false
break
}
}
}
fmt.Printf("手动比较结果:%t\n", equal)
}
运行结果:
bash
p1 == p2: true
p1 == p3: false
手动比较结果:true
8. 结构体工厂函数
8.1 创建结构体的构造函数
go
package main
import (
"fmt"
"time"
)
type User struct {
ID int
Username string
Email string
CreatedAt time.Time
IsActive bool
}
// 构造函数1:创建普通用户
func NewUser(username, email string) *User {
return &User{
Username: username,
Email: email,
CreatedAt: time.Now(),
IsActive: true,
}
}
// 构造函数2:创建管理员用户
func NewAdminUser(username, email string) *User {
user := NewUser(username, email)
user.ID = -1 // 管理员ID设为特殊值
return user
}
// 构造函数3:带验证的构造函数
func NewUserWithValidation(username, email string) (*User, error) {
if len(username) < 3 {
return nil, fmt.Errorf("用户名长度不能少于3个字符")
}
if len(email) < 5 || !contains(email, "@") {
return nil, fmt.Errorf("邮箱格式不正确")
}
return &User{
Username: username,
Email: email,
CreatedAt: time.Now(),
IsActive: true,
}, nil
}
// 辅助函数
func contains(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
func main() {
// 使用构造函数创建用户
user1 := NewUser("zhangsan", "zhangsan@example.com")
fmt.Printf("普通用户:%+v\n", *user1)
admin := NewAdminUser("admin", "admin@example.com")
fmt.Printf("管理员用户:%+v\n", *admin)
// 带验证的构造函数
user2, err := NewUserWithValidation("li", "invalid-email")
if err != nil {
fmt.Printf("创建用户失败:%v\n", err)
}
user3, err := NewUserWithValidation("lisi", "lisi@example.com")
if err != nil {
fmt.Printf("创建用户失败:%v\n", err)
} else {
fmt.Printf("验证通过的用户:%+v\n", *user3)
}
}
运行结果:
bash
普通用户:{ID:0 Username:zhangsan Email:zhangsan@example.com CreatedAt:2024-01-15 10:30:45.123456789 +0800 CST IsActive:true}
管理员用户:{ID:-1 Username:admin Email:admin@example.com CreatedAt:2024-01-15 10:30:45.123456789 +0800 CST IsActive:true}
创建用户失败:用户名长度不能少于3个字符
验证通过的用户:{ID:0 Username:lisi Email:lisi@example.com CreatedAt:2024-01-15 10:30:45.123456789 +0800 CST IsActive:true}
9. 结构体与函数
9.1 结构体作为函数参数
go
package main
import "fmt"
type Rectangle struct {
Width float64
Height float64
}
// 值传递
func calculateArea(rect Rectangle) float64 {
return rect.Width * rect.Height
}
// 指针传递(可以修改原结构体)
func scaleRectangle(rect *Rectangle, factor float64) {
rect.Width *= factor
rect.Height *= factor
}
// 返回结构体
func createSquare(side float64) Rectangle {
return Rectangle{Width: side, Height: side}
}
func main() {
// 创建矩形
rect := Rectangle{Width: 10, Height: 5}
fmt.Printf("原始矩形:%+v\n", rect)
// 值传递计算面积
area := calculateArea(rect)
fmt.Printf("面积:%.2f\n", area)
fmt.Printf("计算后矩形:%+v\n", rect) // 原矩形未改变
// 指针传递缩放
scaleRectangle(&rect, 2.0)
fmt.Printf("缩放后矩形:%+v\n", rect)
fmt.Printf("缩放后面积:%.2f\n", calculateArea(rect))
// 创建正方形
square := createSquare(8)
fmt.Printf("创建的正方形:%+v\n", square)
fmt.Printf("正方形面积:%.2f\n", calculateArea(square))
}
运行结果:
bash
原始矩形:{Width:10 Height:5}
面积:50.00
计算后矩形:{Width:10 Height:5}
缩放后矩形:{Width:20 Height:10}
缩放后面积:200.00
创建的正方形:{Width:8 Height:8}
正方形面积:64.00
10. 总结
结构体是Go语言中组织和管理相关数据的重要工具:
核心特点:
- 将多个相关的数据字段组合在一起
- 可以嵌套其他结构体
- 支持标签用于序列化等操作
- 可以定义构造函数来规范化创建过程
使用场景:
- 表示现实世界的实体(用户、商品、订单等)
- 组织配置信息
- 作为函数参数传递复杂数据
- 实现面向对象编程的基础
最佳实践:
- 使用构造函数来创建结构体实例
- 为重要的结构体添加标签
- 合理使用指针传递避免不必要的拷贝
- 注意包含不可比较字段的结构体比较问题
结构体是Go语言面向对象编程的基础,掌握好结构体的使用对后续学习方法和接口非常重要。