Golang学习历程【第九篇 结构体(struct)】

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 为什么需要结构体

结构体的主要优势:

  1. 数据聚合:将相关的数据组织在一起
  2. 代码清晰:提高代码的可读性和可维护性
  3. 类型安全:创建专门的数据类型
  4. 面向对象:为后续的方法和接口打基础

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语言中组织和管理相关数据的重要工具:

核心特点

  • 将多个相关的数据字段组合在一起
  • 可以嵌套其他结构体
  • 支持标签用于序列化等操作
  • 可以定义构造函数来规范化创建过程

使用场景

  1. 表示现实世界的实体(用户、商品、订单等)
  2. 组织配置信息
  3. 作为函数参数传递复杂数据
  4. 实现面向对象编程的基础

最佳实践

  • 使用构造函数来创建结构体实例
  • 为重要的结构体添加标签
  • 合理使用指针传递避免不必要的拷贝
  • 注意包含不可比较字段的结构体比较问题

结构体是Go语言面向对象编程的基础,掌握好结构体的使用对后续学习方法和接口非常重要。


上一篇:Golang学习历程【Golang学习历程【第八篇 指针(pointer)】

下一篇:Golang学习历程【第十篇 方法(method)与接收者】

相关推荐
花酒锄作田15 小时前
Gin 框架中的规范响应格式设计与实现
golang·gin
西岸行者19 小时前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意20 小时前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码21 小时前
嵌入式学习路线
学习
毛小茛1 天前
计算机系统概论——校验码
学习
babe小鑫1 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms1 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下1 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。1 天前
2026.2.25监控学习
学习
im_AMBER1 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode