章节

7.6 练习

本篇通过若干分级练习巩固结构体定义、嵌入、结构体指针与 json tag,并能独立组织、修改和序列化基础业务数据。

练习

学完本章你应该掌握

  • 能定义结构体类型,完成结构体实例的初始化、字段读写和基础方法调用。
  • 能在切片、map、函数和条件判断中自然使用结构体组织业务数据,而不是继续用零散变量硬拼。
  • 能理解结构体嵌入的作用,知道它是组合复用而不是传统面向对象里的“类继承”。
  • 能区分值参数、指针参数、值接收者和指针接收者在“是否修改原结构体”上的差异,并写对对应代码。
  • 能为结构体字段添加 tagjson tag,控制 JSON 字段名、忽略字段和零值省略行为。
  • 能识别并修正结构体章节的常见错误,例如字段名大小写、初始化语法、修改不生效、tag 写了却不输出等问题。

简单

第 1 题:定义并输出一张学生卡

第 1 题 简单

编写一个 Go 程序,完成下面要求:

  • 定义结构体 Student
  • 字段包括:Name stringAge intScore float64
  • 创建一个学生: Name小李 Age18 Score92.5
  • 分别输出这三个字段

期望输出类似这样:

1
2
3
姓名:小李
年龄:18
成绩:92.5
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type Student struct {
	Name  string
	Age   int
	Score float64
}

func main() {
	student := Student{
		Name:  "小李",
		Age:   18,
		Score: 92.5,
	}

	fmt.Printf("姓名:%s\n", student.Name)
	fmt.Printf("年龄:%d\n", student.Age)
	fmt.Printf("成绩:%.1f\n", student.Score)
}

第 2 题:补全结构体字面量

第 2 题 简单

已知下面的结构体:

1
2
3
4
5
type Book struct {
	Title  string
	Author string
	Price  float64
}

请补全下面代码中的空白,让程序输出:

1
Go 入门 - 阿斌 - 59.9
1
2
3
4
5
6
7
book := Book{
	Title:  "Go 入门",
	Author: ________,
	Price:  ________,
}

fmt.Printf("%s - %s - %.1f\n", book.Title, book.Author, book.Price)
查看参考答案

补全后的代码如下:

1
2
3
4
5
6
7
book := Book{
	Title:  "Go 入门",
	Author: "阿斌",
	Price:  59.9,
}

fmt.Printf("%s - %s - %.1f\n", book.Title, book.Author, book.Price)

说明:结构体字面量初始化时,字段名和值之间写 :,每一行字段后面要记得写逗号。

第 3 题:给结构体绑定一个方法

第 3 题 简单

编写一个 Go 程序,完成下面要求:

  • 定义结构体 User
  • 字段包括:Name stringCity string
  • 给它绑定一个方法 PrintProfile()
  • 调用这个方法后输出:
1
用户:小王,城市:上海
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type User struct {
	Name string
	City string
}

func (u User) PrintProfile() {
	fmt.Printf("用户:%s,城市:%s\n", u.Name, u.City)
}

func main() {
	user := User{
		Name: "小王",
		City: "上海",
	}

	user.PrintProfile()
}

说明:func (u User) PrintProfile() 里的 (u User) 是接收者,表示这个方法属于 User 类型。

第 4 题:修正一段结构体代码

第 4 题 简单

下面程序有两个明显问题,请改正后让它正常运行,并输出商品价格。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import "fmt"

type Product struct {
	Name  string
	Price float64
}

func main() {
	product := Product{
		Name:  "机械键盘"
		Price: 299.0,
	}

	fmt.Println(product.price)
}
查看参考答案

修正后的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import "fmt"

type Product struct {
	Name  string
	Price float64
}

func main() {
	product := Product{
		Name:  "机械键盘",
		Price: 299.0,
	}

	fmt.Println(product.Price)
}

两个问题分别是:

  • 结构体字面量里每个字段后面要有逗号,所以 Name: "机械键盘" 这一行后面漏了逗号。
  • 字段名是 Price,访问时也要写 product.Price,不能写成不存在的 product.price

第 5 题:用函数接收结构体并返回简介

第 5 题 简单

编写一个 Go 程序,完成下面要求:

  • 定义结构体 Course
  • 字段包括:Title stringHours int
  • 编写函数 buildSummary(c Course) string
  • 让这个函数返回类似下面的文字:
1
课程:Go 结构体,总课时:6
  • main 中创建一个课程并调用这个函数
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type Course struct {
	Title string
	Hours int
}

func buildSummary(c Course) string {
	return fmt.Sprintf("课程:%s,总课时:%d", c.Title, c.Hours)
}

func main() {
	course := Course{
		Title: "Go 结构体",
		Hours: 6,
	}

	fmt.Println(buildSummary(course))
}

说明:这道题把结构体和前面学过的函数放到了一起,让“多个字段的数据”可以整体传给函数处理。

一般

第 6 题:用结构体切片统计成绩

第 6 题 一般

编写一个程序,完成下面要求:

  • 定义结构体 Student
  • 字段包括:Name stringScore int
  • 准备下面这组数据:
1
2
3
4
5
students := []Student{
	{Name: "小李", Score: 82},
	{Name: "小王", Score: 59},
	{Name: "小张", Score: 91},
}
  • 使用 for range 遍历这组结构体数据
  • 统计及格人数 passCount
  • 统计平均分 avg,保留 1 位小数

期望输出类似这样:

1
2
passCount=2
avg=77.3
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

type Student struct {
	Name  string
	Score int
}

func main() {
	students := []Student{
		{Name: "小李", Score: 82},
		{Name: "小王", Score: 59},
		{Name: "小张", Score: 91},
	}

	total := 0
	passCount := 0

	for _, student := range students {
		total += student.Score
		if student.Score >= 60 {
			passCount++
		}
	}

	avg := float64(total) / float64(len(students))

	fmt.Printf("passCount=%d\n", passCount)
	fmt.Printf("avg=%.1f\n", avg)
}

说明:这道题的重点是把一组“相关字段”装进结构体,再配合切片和循环做统计,而不是继续维护多组并行变量。

第 7 题:使用结构体嵌入复用字段和方法

第 7 题 一般

编写一个 Go 程序,完成下面要求:

  • 定义结构体 People,字段为 City string
  • People 绑定方法 PrintCity(),输出 城市:北京
  • 定义结构体 Employee
  • Employee 匿名嵌入 People
  • 再增加字段:Name stringRole string
  • 创建一个员工并完成下面两件事: 输出 员工:小刘,角色:开发 直接调用 employee.PrintCity()
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

type People struct {
	City string
}

func (p People) PrintCity() {
	fmt.Printf("城市:%s\n", p.City)
}

type Employee struct {
	People
	Name string
	Role string
}

func main() {
	employee := Employee{
		People: People{
			City: "北京",
		},
		Name: "小刘",
		Role: "开发",
	}

	fmt.Printf("员工:%s,角色:%s\n", employee.Name, employee.Role)
	employee.PrintCity()
}

说明:Employee 并不是“继承了一个类”,而是通过嵌入 People 复用了字段和方法。

第 8 题:用指针参数给用户过生日

第 8 题 一般

编写一个程序,完成下面要求:

  • 定义结构体 User
  • 字段包括:Name stringAge int
  • 编写函数 birthday(u *User)
  • 这个函数的作用是让用户年龄加 1
  • main 中先输出年龄,再调用函数,再输出年龄

期望输出类似这样:

1
2
before: 20
after: 21
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

type User struct {
	Name string
	Age  int
}

func birthday(u *User) {
	u.Age++
}

func main() {
	user := User{
		Name: "小周",
		Age:  20,
	}

	fmt.Println("before:", user.Age)
	birthday(&user)
	fmt.Println("after:", user.Age)
}

说明:如果这里把参数写成 User 而不是 *User,函数里改到的只是副本,外面的 user.Age 不会变化。

第 9 题:把值接收者改成指针接收者

第 9 题 一般

下面程序想给商品补库存,但现在补完以后库存没有变化。
请改正代码,让它最终输出:

1
15
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type Goods struct {
	Name  string
	Stock int
}

func (g Goods) AddStock(n int) {
	g.Stock += n
}

func main() {
	goods := Goods{
		Name:  "耳机",
		Stock: 10,
	}

	goods.AddStock(5)
	fmt.Println(goods.Stock)
}
查看参考答案

修正后的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type Goods struct {
	Name  string
	Stock int
}

func (g *Goods) AddStock(n int) {
	g.Stock += n
}

func main() {
	goods := Goods{
		Name:  "耳机",
		Stock: 10,
	}

	goods.AddStock(5)
	fmt.Println(goods.Stock)
}

关键点:

  • 原来的 func (g Goods) AddStock 是值接收者,修改的是副本。
  • 改成 func (g *Goods) AddStock 以后,方法内部改到的就是原结构体。
  • 调用时写 goods.AddStock(5) 就可以,Go 会自动帮你取地址。

第 10 题:按姓名查询学生信息

第 10 题 一般

编写一个程序,完成下面要求:

  • 定义结构体 Student
  • 字段包括:Name stringAge intScore int
  • 准备一张 map[string]Student
  • 数据如下:
1
2
3
4
students := map[string]Student{
	"小李": {Name: "小李", Age: 18, Score: 88},
	"小王": {Name: "小王", Age: 19, Score: 95},
}
  • 读取一个姓名
  • 如果存在,输出:
1
姓名:小王,年龄:19,成绩:95
  • 如果不存在,输出:
1
没有找到该学生
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

type Student struct {
	Name  string
	Age   int
	Score int
}

func main() {
	students := map[string]Student{
		"小李": {Name: "小李", Age: 18, Score: 88},
		"小王": {Name: "小王", Age: 19, Score: 95},
	}

	var name string

	fmt.Print("请输入姓名:")
	fmt.Scan(&name)

	if student, ok := students[name]; ok {
		fmt.Printf("姓名:%s,年龄:%d,成绩:%d\n", student.Name, student.Age, student.Score)
	} else {
		fmt.Println("没有找到该学生")
	}
}

说明:这道题把结构体和前面学过的 map、输入、if 初始化语句; 条件 放到了一起,很接近真实的数据查询场景。

进阶

下面四题会把结构体和指针、切片、函数、encoding/json 放到更接近真实数据组织的场景里,重点考察多个知识点的配合使用。

第 11 题:为文章结构体添加 json tag

第 11 题 进阶

编写一个程序,完成下面要求:

  • 定义结构体 Article
  • 字段包括:Title stringAuthor stringViewCount int
  • 给它们添加合适的 json tag
  • 要求序列化后的 JSON 字段名分别是: title author view_count
  • 创建一篇文章并转成 JSON 输出

期望输出类似这样:

1
{"title":"Go结构体","author":"小王","view_count":120}
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
	"encoding/json"
	"fmt"
)

type Article struct {
	Title     string `json:"title"`
	Author    string `json:"author"`
	ViewCount int    `json:"view_count"`
}

func main() {
	article := Article{
		Title:     "Go结构体",
		Author:    "小王",
		ViewCount: 120,
	}

	data, _ := json.Marshal(article)
	fmt.Println(string(data))
}

说明:json tag 不会改变结构体字段本身的名字和类型,它只是告诉 encoding/json 在序列化时应该用什么 JSON 字段名。

第 12 题:使用 json:"-"omitempty 导出用户资料

第 12 题 进阶

编写一个程序,完成下面要求:

  • 定义结构体 Profile
  • 字段包括: Name string Age int Password string Phone string
  • JSON 规则如下: Name 输出成 name Age 输出成 age,但如果是零值则省略 Password 永远不要出现在 JSON 中 Phone 输出成 phone,但如果是空字符串则省略
  • 创建下面这个值:
1
2
3
4
5
6
profile := Profile{
	Name:     "小周",
	Age:      0,
	Password: "123456",
	Phone:    "",
}
  • 把它转成 JSON 并输出

期望输出:

1
{"name":"小周"}
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
	"encoding/json"
	"fmt"
)

type Profile struct {
	Name     string `json:"name"`
	Age      int    `json:"age,omitempty"`
	Password string `json:"-"`
	Phone    string `json:"phone,omitempty"`
}

func main() {
	profile := Profile{
		Name:     "小周",
		Age:      0,
		Password: "123456",
		Phone:    "",
	}

	data, _ := json.Marshal(profile)
	fmt.Println(string(data))
}

说明:

  • json:"-" 表示这个字段永远不参与 JSON 编码。
  • omitempty 表示字段是零值时省略输出,0""false 都可能被省略。

第 13 题:修正 tag 写了却没有输出的问题

第 13 题 进阶

下面程序希望输出:

1
{"name":"阿斌","age":21}

但它现在输出的是:

1
{}

请改正代码,让 JSON 正常包含姓名和年龄。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
	"encoding/json"
	"fmt"
)

type student struct {
	name string `json:"name"`
	age  int    `json:"age"`
}

func main() {
	s := student{
		name: "阿斌",
		age:  21,
	}

	data, _ := json.Marshal(s)
	fmt.Println(string(data))
}
查看参考答案

修正后的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	s := Student{
		Name: "阿斌",
		Age:  21,
	}

	data, _ := json.Marshal(s)
	fmt.Println(string(data))
}

原因说明:

  • encoding/json 只能处理可导出的字段,也就是首字母大写的字段。
  • 原来的 nameage 虽然写了 tag,但字段本身是小写,仍然不会被 JSON 编码。
  • tag 只是附加说明,不能替代“字段需要导出”这条规则。

第 14 题:实现一个班级成绩报告

第 14 题 进阶

请完成一个稍微完整一点的小场景:

  • 定义结构体 Student
  • 字段包括: Name string Score int Comment string
  • 给这三个字段添加合适的 json tag
  • 其中 Comment 为空时不输出到 JSON
  • Student 绑定一个指针接收者方法 AddBonus(points int),用于给分数加分
  • 准备下面这组学生数据:
1
2
3
4
5
students := []Student{
	{Name: "小李", Score: 58, Comment: "需要补作业"},
	{Name: "小王", Score: 76},
	{Name: "小张", Score: 59},
}
  • 遍历这组数据: 如果学生分数小于 60,就给他加 5 分 统计最终的及格人数 passCount 统计最终平均分 avg
  • 最后把更新后的学生切片转成 JSON 输出

期望输出类似这样:

1
2
3
passCount=3
avg=67.7
[{"name":"小李","score":63,"comment":"需要补作业"},{"name":"小王","score":76},{"name":"小张","score":64}]
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Name    string `json:"name"`
	Score   int    `json:"score"`
	Comment string `json:"comment,omitempty"`
}

func (s *Student) AddBonus(points int) {
	s.Score += points
}

func main() {
	students := []Student{
		{Name: "小李", Score: 58, Comment: "需要补作业"},
		{Name: "小王", Score: 76},
		{Name: "小张", Score: 59},
	}

	total := 0
	passCount := 0

	for i := range students {
		if students[i].Score < 60 {
			students[i].AddBonus(5)
		}

		total += students[i].Score
		if students[i].Score >= 60 {
			passCount++
		}
	}

	avg := float64(total) / float64(len(students))

	fmt.Printf("passCount=%d\n", passCount)
	fmt.Printf("avg=%.1f\n", avg)

	data, _ := json.Marshal(students)
	fmt.Println(string(data))
}

说明:

  • 这里使用 for i := range students,是为了直接修改切片里的原始结构体元素。
  • 如果写成 for _, student := range students,拿到的是每个元素的副本,修改时很容易和预期不一致。
  • 这道题把结构体、指针接收者、切片遍历、判断统计和 JSON 输出都串起来了,比较接近真实业务中的数据处理流程。
本文禁止转载
使用 Hugo 构建
主题 StackJimmy 设计 由 Hobin 魔改
最近构建时间:2026-04-17 19:07:48 CST
载入天数...载入时分秒...
发表了 1 篇文章 · 发表了 152 篇笔记 · 总计 18 万 0 千字