跳转至

2.6 结构体与方法

学习目标

  • 掌握结构体的定义与初始化方法
  • 理解方法的定义与调用方式
  • 学会选择正确的接收者类型
  • 掌握结构体嵌套与组合的使用

结构体定义与初始化

结构体的定义

结构体是一种聚合数据类型,用于将多个不同类型的命名字段组合在一起形成一个新的类型。

package main

import "fmt"

// 定义Person结构体
type Person struct {
    Name    string
    Age     int
    Address string
}

func main() {
    // 使用字段名初始化
    p1 := Person{
        Name:    "张三",
        Age:     25,
        Address: "北京市朝阳区",
    }

    // 省略字段名初始化(必须按顺序且包含所有字段)
    p2 := Person{"李四", 30, "上海市浦东新区"}

    // 部分字段初始化,未指定的字段取零值
    p3 := Person{Name: "王五"}

    fmt.Println("p1:", p1)
    fmt.Println("p2:", p2)
    fmt.Println("p3:", p3)

    // 访问结构体字段
    fmt.Println("p1的姓名:", p1.Name)
    p1.Age = 26  // 修改字段值
    fmt.Println("修改后p1的年龄:", p1.Age)
}

匿名结构体

package main

import "fmt"

func main() {
    // 定义并初始化匿名结构体
    employee := struct {
        ID     int
        Name   string
        Salary float64
    }{
        ID:     1001,
        Name:   "赵六",
        Salary: 8000.50,
    }

    fmt.Printf("员工信息: %+v\n", employee)
    fmt.Printf("员工姓名: %s, 薪资: %.2f\n", employee.Name, employee.Salary)
}

方法的定义与调用

方法的基本概念

方法是与特定类型关联的函数,通过接收者(receiver)来指定方法所属的类型。

package main

import "fmt"

// 定义Rectangle结构体
type Rectangle struct {
    Width  float64
    Height float64
}

// Area方法:值接收者
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Perimeter方法:值接收者
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// Scale方法:指针接收者,用于修改结构体字段
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}

    fmt.Printf("原始矩形: 宽%.2f, 高%.2f\n", rect.Width, rect.Height)
    fmt.Printf("面积: %.2f\n", rect.Area())
    fmt.Printf("周长: %.2f\n", rect.Perimeter())

    // 调用指针接收者方法
    rect.Scale(2)
    fmt.Printf("缩放后矩形: 宽%.2f, 高%.2f\n", rect.Width, rect.Height)
    fmt.Printf("缩放后面积: %.2f\n", rect.Area())
}

方法与函数的区别

package main

import "fmt"

type Circle struct {
    Radius float64
}

// 方法形式
func (c Circle) Area() float64 {
    return 3.14159 * c.Radius * c.Radius
}

// 函数形式
func CircleArea(c Circle) float64 {
    return 3.14159 * c.Radius * c.Radius
}

func main() {
    circle := Circle{Radius: 5}

    // 方法调用
    fmt.Printf("方法计算面积: %.2f\n", circle.Area())

    // 函数调用
    fmt.Printf("函数计算面积: %.2f\n", CircleArea(circle))
}

接收者类型的选择

值接收者 vs 指针接收者

package main

import "fmt"

type Counter struct {
    value int
}

// 值接收者方法 - 不会修改原始结构体
func (c Counter) IncrementByValue() {
    c.value++
    fmt.Printf("值接收者方法内: %d\n", c.value)
}

// 指针接收者方法 - 会修改原始结构体
func (c *Counter) IncrementByPointer() {
    c.value++
    fmt.Printf("指针接收者方法内: %d\n", c.value)
}

// 值接收者方法 - 只读操作
func (c Counter) GetValue() int {
    return c.value
}

func main() {
    counter := Counter{value: 0}

    fmt.Println("初始值:", counter.GetValue())

    counter.IncrementByValue()
    fmt.Println("调用值接收者方法后:", counter.GetValue())

    counter.IncrementByPointer()
    fmt.Println("调用指针接收者方法后:", counter.GetValue())

    // 指针类型变量也可以调用值接收者方法
    counterPtr := &Counter{value: 10}
    fmt.Println("指针变量的值:", counterPtr.GetValue())
    counterPtr.IncrementByValue()
    fmt.Println("调用值接收者方法后:", counterPtr.GetValue())
}

接收者选择指南

package main

import "fmt"

type BankAccount struct {
    owner   string
    balance float64
}

// 使用指针接收者:需要修改结构体状态
func (account *BankAccount) Deposit(amount float64) {
    if amount > 0 {
        account.balance += amount
    }
}

// 使用指针接收者:需要修改结构体状态
func (account *BankAccount) Withdraw(amount float64) bool {
    if amount > 0 && account.balance >= amount {
        account.balance -= amount
        return true
    }
    return false
}

// 使用值接收者:不需要修改结构体,只读取数据
func (account BankAccount) Balance() float64 {
    return account.balance
}

// 使用值接收者:不需要修改结构体,只读取数据
func (account BankAccount) Owner() string {
    return account.owner
}

func main() {
    account := BankAccount{owner: "张三", balance: 1000}

    fmt.Printf("%s的账户余额: %.2f\n", account.Owner(), account.Balance())

    account.Deposit(500)
    fmt.Printf("存款后余额: %.2f\n", account.Balance())

    success := account.Withdraw(200)
    if success {
        fmt.Printf("取款后余额: %.2f\n", account.Balance())
    } else {
        fmt.Println("取款失败")
    }

    // 尝试取款超过余额
    success = account.Withdraw(2000)
    if success {
        fmt.Printf("取款后余额: %.2f\n", account.Balance())
    } else {
        fmt.Println("取款失败:余额不足")
    }
}

结构体嵌套与组合

基本嵌套

package main

import "fmt"

// 地址结构体
type Address struct {
    City     string
    Street   string
    ZipCode  string
}

// 人员结构体嵌套地址结构体
type Employee struct {
    Name      string
    Age       int
    Position  string
    Address   // 匿名嵌套字段
}

// 联系方式结构体
type Contact struct {
    Phone   string
    Email   string
}

// 完整员工信息,组合多个结构体
type FullEmployee struct {
    Employee  // 匿名嵌套
    Contact   // 匿名嵌套
    Department string
}

func main() {
    // 创建Employee实例
    emp := Employee{
        Name:     "李四",
        Age:      28,
        Position: "软件工程师",
        Address: Address{
            City:    "北京",
            Street:  "朝阳区建国路",
            ZipCode: "100000",
        },
    }

    // 直接访问嵌套结构的字段
    fmt.Printf("员工: %s, 城市: %s, 街道: %s\n", 
        emp.Name, emp.City, emp.Street)

    // 也可以完整指定嵌套结构字段
    fmt.Printf("邮编: %s\n", emp.Address.ZipCode)

    // 创建FullEmployee实例
    fullEmp := FullEmployee{
        Employee: Employee{
            Name:     "王五",
            Age:      32,
            Position: "技术经理",
            Address: Address{
                City:    "上海",
                Street:  "浦东新区张江路",
                ZipCode: "201200",
            },
        },
        Contact: Contact{
            Phone: "13800138000",
            Email: "wangwu@example.com",
        },
        Department: "研发部",
    }

    fmt.Printf("\n完整员工信息:\n")
    fmt.Printf("姓名: %s, 年龄: %d\n", fullEmp.Name, fullEmp.Age)
    fmt.Printf职位: %s, 部门: %s\n", fullEmp.Position, fullEmp.Department)
    fmt.Printf("电话: %s, 邮箱: %s\n", fullEmp.Phone, fullEmp.Email)
    fmt.Printf("地址: %s%s, 邮编: %s\n", 
        fullEmp.City, fullEmp.Street, fullEmp.ZipCode)
}

方法继承与重写

package main

import "fmt"

// 基础人员结构体
type Person struct {
    Name string
    Age  int
}

// Person的方法
func (p Person) Introduce() {
    fmt.Printf("大家好,我是%s,今年%d岁。\n", p.Name, p.Age)
}

// 学生结构体嵌套Person
type Student struct {
    Person       // 嵌套Person
    StudentID    string
    Major        string
}

// 重写Introduce方法
func (s Student) Introduce() {
    fmt.Printf("大家好,我是学生%s,学号%s,专业是%s。\n", 
        s.Name, s.StudentID, s.Major)
}

// 老师结构体嵌套Person
type Teacher struct {
    Person       // 嵌套Person
    TeacherID    string
    Subject      string
}

// Teacher的方法
func (t Teacher) Introduce() {
    fmt.Printf("大家好,我是老师%s,工号%s,教授%s课程。\n", 
        t.Name, t.TeacherID, t.Subject)
}

func main() {
    // 创建Person实例
    person := Person{Name: "普通人", Age: 30}
    person.Introduce()

    fmt.Println()

    // 创建Student实例
    student := Student{
        Person:    Person{Name: "张三", Age: 20},
        StudentID: "2023001",
        Major:     "计算机科学",
    }
    student.Introduce()           // 调用重写的方法
    student.Person.Introduce()    // 调用父类的方法

    fmt.Println()

    // 创建Teacher实例
    teacher := Teacher{
        Person:    Person{Name: "李老师", Age: 40},
        TeacherID: "T1001",
        Subject:   "Go语言编程",
    }
    teacher.Introduce()
}

结构体组合实战示例

package main

import (
    "fmt"
    "math"
)

// 定义点结构体
type Point struct {
    X, Y float64
}

// 点的方法:计算到另一个点的距离
func (p Point) DistanceTo(q Point) float64 {
    return math.Sqrt(math.Pow(q.X-p.X, 2) + math.Pow(q.Y-p.Y, 2))
}

// 定义线段结构体
type Line struct {
    Start, End Point
}

// 线段的方法:计算长度
func (l Line) Length() float64 {
    return l.Start.DistanceTo(l.End)
}

// 定义三角形结构体
type Triangle struct {
    A, B, C Point
}

// 三角形的方法:计算周长
func (t Triangle) Perimeter() float64 {
    return t.A.DistanceTo(t.B) + t.B.DistanceTo(t.C) + t.C.DistanceTo(t.A)
}

// 三角形的方法:计算面积(海伦公式)
func (t Triangle) Area() float64 {
    a := t.A.DistanceTo(t.B)
    b := t.B.DistanceTo(t.C)
    c := t.C.DistanceTo(t.A)
    s := (a + b + c) / 2
    return math.Sqrt(s * (s - a) * (s - b) * (s - c))
}

func main() {
    // 创建点
    p1 := Point{X: 0, Y: 0}
    p2 := Point{X: 3, Y: 0}
    p3 := Point{X: 0, Y: 4}

    // 计算点之间的距离
    fmt.Printf("p1到p2的距离: %.2f\n", p1.DistanceTo(p2))

    // 创建线段
    line := Line{Start: p1, End: p2}
    fmt.Printf("线段长度: %.2f\n", line.Length())

    // 创建三角形
    triangle := Triangle{A: p1, B: p2, C: p3}
    fmt.Printf("三角形周长: %.2f\n", triangle.Perimeter())
    fmt.Printf("三角形面积: %.2f\n", triangle.Area())

    // 验证是否为直角三角形(3-4-5)
    a := p1.DistanceTo(p2)  // 3
    b := p2.DistanceTo(p3)  // 5
    c := p3.DistanceTo(p1)  // 4
    fmt.Printf("边长: %.2f, %.2f, %.2f\n", a, b, c)
}

总结

通过本章学习,我们掌握了:

  1. 结构体定义与初始化:学会了如何定义结构体类型并使用多种方式初始化结构体实例
  2. 方法的定义与调用:理解了方法与函数的区别,学会了如何为结构体定义方法
  3. 接收者类型的选择:掌握了值接收者和指针接收者的区别及适用场景
  4. 结构体嵌套与组合:学会了通过嵌套实现代码复用,理解了Go语言的组合特性

结构体和方法是Go语言面向对象编程的核心概念,通过组合而不是继承来实现代码复用,这是Go语言设计哲学的重要体现。在实际开发中,合理使用结构体和方法可以写出更加清晰、模块化的代码。


上一节2.5 函数定义、参数传递、返回值处理
下一节2.7 实战练习