结构体
结构体是Go中自定义类型的方式,类似Java的Class,但更简单——没有继承!作为从面向对象语言转过来的人,我一开始非常不适应Go没有类、没有继承的设计。但用久了才发现,组合优于继承,代码更加灵活清晰!
定义结构体
// 定义一个Person结构体
type Person struct {
Name string
Age int
City string
}
// 定义一个Book结构体
type Book struct {
Title string
Author string
Price float64
Pages int
}
Java程序员看过来
// Java的类
public class Person {
private String name;
private int age;
private String city;
// 还要写getter/setter...
}
// Go的结构体 - 简洁很多!
type Person struct {
Name string
Age int
City string
}
Go没有getter/setter,字段首字母大写就是public,小写就是private(包内可见)。
创建结构体实例
// 方式1:声明后赋值
var p1 Person
p1.Name = "张三"
p1.Age = 25
p1.City = "北京"
// 方式2:字面量创建(推荐!)
p2 := Person{
Name: "李四",
Age: 30,
City: "上海",
}
// 方式3:省略字段名(不推荐,顺序敏感)
p3 := Person{"王五", 28, "深圳"}
// 方式4:部分字段初始化(其他为零值)
p4 := Person{Name: "赵六"} // Age=0, City=""
// 方式5:new关键字(返回指针)
p5 := new(Person) // *Person类型
p5.Name = "钱七"
// 方式6:取地址(返回指针,最常用!)
p6 := &Person{
Name: "孙八",
Age: 35,
City: "广州",
}
访问和修改字段
p := Person{Name: "张三", Age: 25, City: "北京"}
// 读取字段
fmt.Println(p.Name) // 张三
fmt.Println(p.Age) // 25
// 修改字段
p.Age = 26
p.City = "上海"
// 指针也是用.访问(Go自动解引用!)
pp := &p
fmt.Println(pp.Name) // 张三(等价于 (*pp).Name)
pp.Age = 27 // 同样可以修改
和C/C++的区别
C/C++中指针访问成员用 ->,Go统一用 .,Go编译器自动处理解引用。
结构体是值类型
p1 := Person{Name: "张三", Age: 25}
p2 := p1 // 复制整个结构体!
p2.Name = "李四"
fmt.Println(p1.Name) // 张三(原结构体不变)
fmt.Println(p2.Name) // 李四
传递给函数也是复制:
func birthday(p Person) {
p.Age++ // 只是修改副本
}
p := Person{Name: "张三", Age: 25}
birthday(p)
fmt.Println(p.Age) // 25(没变!)
// 要修改原数据,传指针
func birthdayPtr(p *Person) {
p.Age++ // 修改原数据
}
birthdayPtr(&p)
fmt.Println(p.Age) // 26(变了!)
结构体标签(Tag)
标签用于给字段添加元信息,常用于JSON、数据库映射等。
type User struct {
ID int `json:"id" db:"user_id"`
Username string `json:"username" db:"user_name"`
Password string `json:"-"` // JSON序列化时忽略
Email string `json:"email,omitempty"` // 空值时忽略
CreatedAt string `json:"created_at"`
}
// JSON序列化
u := User{
ID: 1,
Username: "zhangsan",
Password: "secret123",
Email: "",
}
data, _ := json.Marshal(u)
fmt.Println(string(data))
// {"id":1,"username":"zhangsan"}
// Password被忽略了,Email是空值也被忽略了
匿名字段(嵌入)
Go没有继承,但有组合!通过匿名字段实现类似继承的效果。
type Address struct {
Province string
City string
Street string
}
type Employee struct {
Name string
Age int
Address // 匿名字段,嵌入Address
}
func main() {
e := Employee{
Name: "张三",
Age: 30,
Address: Address{
Province: "广东省",
City: "深圳市",
Street: "科技园路",
},
}
// 直接访问嵌入的字段
fmt.Println(e.City) // 深圳市(语法糖)
fmt.Println(e.Address.City) // 深圳市(完整路径)
// 修改也一样
e.City = "广州市"
}
多层嵌入
type Contact struct {
Phone string
Email string
}
type Person struct {
Name string
Contact // 嵌入Contact
}
type Employee struct {
Person // 嵌入Person
Company string
}
func main() {
e := Employee{
Person: Person{
Name: "张三",
Contact: Contact{
Phone: "13800138000",
Email: "zhangsan@example.com",
},
},
Company: "阿里巴巴",
}
// 直接访问深层字段
fmt.Println(e.Name) // 张三
fmt.Println(e.Phone) // 13800138000
fmt.Println(e.Email) // zhangsan@example.com
}
字段名冲突
type A struct {
Name string
}
type B struct {
Name string
}
type C struct {
A
B
}
func main() {
c := C{}
// c.Name = "test" // ❌ 编译错误:歧义
c.A.Name = "A的Name" // ✅ 明确指定
c.B.Name = "B的Name" // ✅ 明确指定
}
结构体比较
type Point struct {
X, Y int
}
p1 := Point{1, 2}
p2 := Point{1, 2}
p3 := Point{1, 3}
fmt.Println(p1 == p2) // true
fmt.Println(p1 == p3) // false
注意
只有当结构体的所有字段都是可比较的类型时,结构体才能比较。 包含切片、map、函数的结构体不能用 == 比较!
type Data struct {
Values []int // 切片不可比较
}
d1 := Data{Values: []int{1, 2, 3}}
d2 := Data{Values: []int{1, 2, 3}}
// d1 == d2 // ❌ 编译错误
空结构体
空结构体 struct{} 不占用内存,常用于:
// 1. 实现Set(用map[T]struct{})
set := make(map[string]struct{})
set["apple"] = struct{}{}
set["banana"] = struct{}{}
if _, exists := set["apple"]; exists {
fmt.Println("apple存在")
}
// 2. 信号通道(只需要通知,不需要传数据)
done := make(chan struct{})
go func() {
// 干活...
close(done) // 发送完成信号
}()
<-done // 等待完成
构造函数
Go没有构造函数,约定用 New 开头的函数代替:
type User struct {
id int
name string
age int
}
// "构造函数" - 返回指针是惯例
func NewUser(name string, age int) *User {
return &User{
id: generateID(), // 假设这是生成ID的函数
name: name,
age: age,
}
}
// 带验证的构造函数
func NewUserWithValidation(name string, age int) (*User, error) {
if name == "" {
return nil, errors.New("name不能为空")
}
if age < 0 || age > 150 {
return nil, errors.New("age不合法")
}
return &User{
id: generateID(),
name: name,
age: age,
}, nil
}
实战案例:图书管理系统
package main
import (
"fmt"
"time"
)
// 图书结构体
type Book struct {
ISBN string
Title string
Author string
Price float64
Publisher string
PubDate time.Time
}
// 图书馆结构体
type Library struct {
Name string
Location string
Books []Book
}
// 创建新图书
func NewBook(isbn, title, author string, price float64) *Book {
return &Book{
ISBN: isbn,
Title: title,
Author: author,
Price: price,
}
}
// 创建图书馆
func NewLibrary(name, location string) *Library {
return &Library{
Name: name,
Location: location,
Books: make([]Book, 0),
}
}
// 添加图书
func (l *Library) AddBook(book Book) {
l.Books = append(l.Books, book)
}
// 按作者查找图书
func (l *Library) FindByAuthor(author string) []Book {
result := []Book{}
for _, book := range l.Books {
if book.Author == author {
result = append(result, book)
}
}
return result
}
// 计算图书总价值
func (l *Library) TotalValue() float64 {
total := 0.0
for _, book := range l.Books {
total += book.Price
}
return total
}
// 打印图书馆信息
func (l *Library) Print() {
fmt.Printf("📚 %s(%s)\n", l.Name, l.Location)
fmt.Printf(" 共有 %d 本图书,总价值 ¥%.2f\n", len(l.Books), l.TotalValue())
fmt.Println(" ────────────────────────────────")
for i, book := range l.Books {
fmt.Printf(" %d. 《%s》 - %s ¥%.2f\n", i+1, book.Title, book.Author, book.Price)
}
}
func main() {
// 创建图书馆
lib := NewLibrary("城市图书馆", "北京市海淀区")
// 添加图书
lib.AddBook(Book{
ISBN: "978-7-111-44277-5",
Title: "Go程序设计语言",
Author: "艾伦·多诺万",
Price: 79.00,
})
lib.AddBook(Book{
ISBN: "978-7-111-52580-9",
Title: "Go语言实战",
Author: "威廉·肯尼迪",
Price: 59.00,
})
lib.AddBook(Book{
ISBN: "978-7-111-55287-4",
Title: "Go Web编程",
Author: "郑兆雄",
Price: 69.00,
})
lib.AddBook(Book{
ISBN: "978-7-121-32829-0",
Title: "Go语言高级编程",
Author: "柴树杉",
Price: 89.00,
})
// 打印图书馆信息
lib.Print()
// 查找特定作者的书
fmt.Println("\n🔍 查找作者\"柴树杉\"的图书:")
books := lib.FindByAuthor("柴树杉")
for _, b := range books {
fmt.Printf(" 《%s》 ¥%.2f\n", b.Title, b.Price)
}
}
输出:
📚 城市图书馆(北京市海淀区)
共有 4 本图书,总价值 ¥296.00
────────────────────────────────
1. 《Go程序设计语言》 - 艾伦·多诺万 ¥79.00
2. 《Go语言实战》 - 威廉·肯尼迪 ¥59.00
3. 《Go Web编程》 - 郑兆雄 ¥69.00
4. 《Go语言高级编程》 - 柴树杉 ¥89.00
🔍 查找作者"柴树杉"的图书:
《Go语言高级编程》 ¥89.00
实战案例:学生信息管理
package main
import (
"fmt"
"sort"
)
// 成绩结构体
type Score struct {
Subject string
Grade float64
}
// 学生结构体
type Student struct {
ID string
Name string
Age int
Class string
Scores []Score
}
// 计算平均分
func (s *Student) Average() float64 {
if len(s.Scores) == 0 {
return 0
}
total := 0.0
for _, score := range s.Scores {
total += score.Grade
}
return total / float64(len(s.Scores))
}
// 获取最高分科目
func (s *Student) BestSubject() (string, float64) {
if len(s.Scores) == 0 {
return "", 0
}
best := s.Scores[0]
for _, score := range s.Scores[1:] {
if score.Grade > best.Grade {
best = score
}
}
return best.Subject, best.Grade
}
// 判断是否及格(所有科目>=60)
func (s *Student) IsPassed() bool {
for _, score := range s.Scores {
if score.Grade < 60 {
return false
}
}
return true
}
// 班级结构体
type Class struct {
Name string
Students []Student
}
// 获取班级平均分
func (c *Class) Average() float64 {
if len(c.Students) == 0 {
return 0
}
total := 0.0
for _, s := range c.Students {
total += s.Average()
}
return total / float64(len(c.Students))
}
// 获取排名
func (c *Class) Ranking() []Student {
// 复制一份避免修改原切片
ranked := make([]Student, len(c.Students))
copy(ranked, c.Students)
sort.Slice(ranked, func(i, j int) bool {
return ranked[i].Average() > ranked[j].Average()
})
return ranked
}
func main() {
// 创建班级
class := Class{
Name: "计算机1班",
Students: []Student{
{
ID: "2024001", Name: "张三", Age: 20, Class: "计算机1班",
Scores: []Score{
{"语文", 85},
{"数学", 92},
{"英语", 78},
},
},
{
ID: "2024002", Name: "李四", Age: 21, Class: "计算机1班",
Scores: []Score{
{"语文", 90},
{"数学", 88},
{"英语", 95},
},
},
{
ID: "2024003", Name: "王五", Age: 20, Class: "计算机1班",
Scores: []Score{
{"语文", 75},
{"数学", 55},
{"英语", 70},
},
},
},
}
// 打印班级信息
fmt.Printf("📚 %s 成绩单\n", class.Name)
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
for _, s := range class.Students {
status := "✅ 及格"
if !s.IsPassed() {
status = "❌ 不及格"
}
bestSubject, bestGrade := s.BestSubject()
fmt.Printf("%s(%s)\n", s.Name, s.ID)
fmt.Printf(" 平均分: %.2f %s\n", s.Average(), status)
fmt.Printf(" 最强科目: %s(%.1f分)\n", bestSubject, bestGrade)
fmt.Println()
}
// 打印排名
fmt.Println("📊 成绩排名")
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
for i, s := range class.Ranking() {
fmt.Printf(" 第%d名: %s 平均分: %.2f\n", i+1, s.Name, s.Average())
}
fmt.Printf("\n班级平均分: %.2f\n", class.Average())
}
输出:
📚 计算机1班 成绩单
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
张三(2024001)
平均分: 85.00 ✅ 及格
最强科目: 数学(92.0分)
李四(2024002)
平均分: 91.00 ✅ 及格
最强科目: 英语(95.0分)
王五(2024003)
平均分: 66.67 ❌ 不及格
最强科目: 语文(75.0分)
📊 成绩排名
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第1名: 李四 平均分: 91.00
第2名: 张三 平均分: 85.00
第3名: 王五 平均分: 66.67
班级平均分: 80.89
练习
- 定义一个
Rectangle结构体,包含长和宽,实现计算面积和周长的函数 - 定义一个
BankAccount结构体,实现存款、取款、查询余额功能 - 使用嵌入实现一个
Manager结构体,嵌入Employee结构体
参考答案
package main
import (
"errors"
"fmt"
)
// 1. Rectangle
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// 2. BankAccount
type BankAccount struct {
owner string
balance float64
}
func NewBankAccount(owner string) *BankAccount {
return &BankAccount{owner: owner, balance: 0}
}
func (a *BankAccount) Deposit(amount float64) error {
if amount <= 0 {
return errors.New("存款金额必须大于0")
}
a.balance += amount
return nil
}
func (a *BankAccount) Withdraw(amount float64) error {
if amount <= 0 {
return errors.New("取款金额必须大于0")
}
if amount > a.balance {
return errors.New("余额不足")
}
a.balance -= amount
return nil
}
func (a *BankAccount) Balance() float64 {
return a.balance
}
// 3. Manager嵌入Employee
type Employee struct {
Name string
Salary float64
}
type Manager struct {
Employee // 嵌入Employee
Department string
TeamSize int
}
func main() {
// 1. 测试Rectangle
rect := Rectangle{Width: 10, Height: 5}
fmt.Printf("矩形: 面积=%.2f, 周长=%.2f\n", rect.Area(), rect.Perimeter())
// 2. 测试BankAccount
account := NewBankAccount("张三")
account.Deposit(1000)
account.Withdraw(300)
fmt.Printf("账户余额: ¥%.2f\n", account.Balance())
// 3. 测试Manager
mgr := Manager{
Employee: Employee{Name: "李四", Salary: 20000},
Department: "技术部",
TeamSize: 10,
}
fmt.Printf("经理: %s, 部门: %s, 团队: %d人\n", mgr.Name, mgr.Department, mgr.TeamSize)
}
结构体是Go面向对象的基础,下一节学习指针——理解Go的内存!
