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)与接收者】

相关推荐
川西胖墩墩2 小时前
新手在线画泳道图PC端简单操作快速做出标准化流程图表
学习·流程图·敏捷流程
saoys2 小时前
Opencv 学习笔记:提取轮廓中心点坐标(矩计算法)
笔记·opencv·学习
云霄IT2 小时前
go语言post请求遭遇403反爬解决tls/ja3指纹或Cloudflare防护
开发语言·后端·golang
楼田莉子2 小时前
Linux学习:进程信号
linux·运维·服务器·c++·学习
●VON2 小时前
React Native for OpenHarmony:井字棋游戏的开发与跨平台适配实践
学习·react native·react.js·游戏·性能优化·交互
盐焗西兰花2 小时前
鸿蒙学习实战之路-Reader Kit获取目录列表最佳实践
学习·华为·harmonyos
AI视觉网奇2 小时前
ue 安装报错MD-DL ue 安装笔记
笔记·学习·ue5
pop_xiaoli2 小时前
effective-Objective-C 第一章阅读笔记
开发语言·笔记·ios·objective-c·cocoa·xcode
崇山峻岭之间2 小时前
Matlab学习记录42
学习