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)
}
总结¶
通过本章学习,我们掌握了:
- 结构体定义与初始化:学会了如何定义结构体类型并使用多种方式初始化结构体实例
- 方法的定义与调用:理解了方法与函数的区别,学会了如何为结构体定义方法
- 接收者类型的选择:掌握了值接收者和指针接收者的区别及适用场景
- 结构体嵌套与组合:学会了通过嵌套实现代码复用,理解了Go语言的组合特性
结构体和方法是Go语言面向对象编程的核心概念,通过组合而不是继承来实现代码复用,这是Go语言设计哲学的重要体现。在实际开发中,合理使用结构体和方法可以写出更加清晰、模块化的代码。
上一节:2.5 函数定义、参数传递、返回值处理
下一节:2.7 实战练习