章节

8.3 练习

本篇通过若干分级练习巩固自定义类型、类型别名、方法绑定与显式转换,并能在基础数据建模中做出正确选择。

练习

学完本章你应该掌握

  • 能区分自定义类型和类型别名,理解“是否产生新类型”会影响赋值、比较、传参和方法绑定。
  • 能使用 type 新类型 底层类型 为普通值补充业务语义,并通过常量集中表达状态、等级等枚举值。
  • 能在需要时完成自定义类型与底层类型之间的显式转换,避免直接混用导致的编译错误。
  • 能判断什么时候更适合使用类型别名,什么时候更适合使用自定义类型,而不是为了“换个名字”随意定义。
  • 能把本章知识自然放进函数、结构体、切片、map 和控制流程里,写出语义更清晰的基础程序。
  • 能识别并修正常见错误,例如漏写 =、把自定义类型直接和底层类型比较、把别名误当成新类型使用。

简单

第 1 题:定义一个订单状态自定义类型

第 1 题 简单

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

  • 定义自定义类型 OrderStatus,底层类型是 int
  • 定义两个常量: StatusPending = 1 StatusPaid = 2
  • main 中定义 status := StatusPaid
  • 先输出它对应的整数值
  • 再定义 raw := 2
  • 判断 statusraw 是否表示同一个值,并输出结果

要求:比较时请使用显式类型转换。

查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

type OrderStatus int

const (
	StatusPending OrderStatus = 1
	StatusPaid    OrderStatus = 2
)

func main() {
	status := StatusPaid
	raw := 2

	fmt.Println(int(status))
	fmt.Println(int(status) == raw)
}

输出结果:

1
2
2
true

说明:OrderStatus 是一个新类型,虽然底层是 int,但和 int 变量比较时仍应先显式转换。

第 2 题:判断哪些写法可以通过编译

第 2 题 简单

下面这些写法里,哪些可以通过编译,哪些不能?请分别写出结果,并简要说明原因。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type StatusCode int
type AliasCode = int

var raw int = 200

A. var a StatusCode = 200
B. var b int = a
C. var c int = int(a)
D. var d AliasCode = raw
E. var e int = d
F. fmt.Println(a == raw)
查看参考答案

可以通过编译的是:

1
A、C、D、E

不能通过编译的是:

1
B、F

原因说明:

  • A 合法,200 可以赋值给 StatusCode
  • B 不合法,StatusCode 是新类型,不能直接赋值给 int
  • C 合法,先转成 int(a) 就可以。
  • D 合法,AliasCodeint 的别名,本质上还是 int
  • E 合法,别名和原类型可以直接赋值。
  • F 不合法,aStatusCoderawint 变量,二者不能直接比较。

第 3 题:修正自定义类型和整数混用错误

第 3 题 简单

下面程序想输出:

1
17

但它现在不能通过编译。请改正后让它正常运行。

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

import "fmt"

type Price int

func main() {
	var shipping Price = 12
	discount := 5

	total := shipping + discount
	if shipping == discount {
		fmt.Println("运费和优惠金额相同")
	}

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

修正后的代码如下:

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

import "fmt"

type Price int

func main() {
	var shipping Price = 12
	discount := 5

	total := shipping + Price(discount)
	if shipping == Price(discount) {
		fmt.Println("运费和优惠金额相同")
	}

	fmt.Println(total)
}

关键点:

  • Price 是自定义类型,不能直接和 int 做加法。
  • 比较时也要保证两边类型一致,所以这里同样要先转换。

第 4 题:使用类型别名判断编辑权限

第 4 题 简单

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

  • 定义类型别名 UserID = int
  • 定义 ownerID,值为 1001
  • 定义 currentID,值为 1001
  • 如果两者相等,输出 当前用户可以编辑
  • 否则输出 无权限

要求:直接比较,不要额外做类型转换。

查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import "fmt"

type UserID = int

func main() {
	var ownerID UserID = 1001
	currentID := 1001

	if ownerID == currentID {
		fmt.Println("当前用户可以编辑")
	} else {
		fmt.Println("无权限")
	}
}

说明:UserIDint 的别名,所以这里可以直接和 int 比较。

第 5 题:给会员等级自定义类型绑定方法

第 5 题 简单

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

  • 定义自定义类型 MemberLevel,底层类型是 int
  • 定义三个常量: LevelNormal = 1 LevelVIP = 2 LevelSVIP = 3
  • MemberLevel 绑定方法 Name() string
  • 返回规则如下: LevelNormal 返回 普通会员 LevelVIP 返回 VIP会员 LevelSVIP 返回 SVIP会员 其他值返回 未知等级
  • main 中输出 LevelVIP.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
26
27
28
package main

import "fmt"

type MemberLevel int

const (
	LevelNormal MemberLevel = 1
	LevelVIP    MemberLevel = 2
	LevelSVIP   MemberLevel = 3
)

func (m MemberLevel) Name() string {
	switch m {
	case LevelNormal:
		return "普通会员"
	case LevelVIP:
		return "VIP会员"
	case LevelSVIP:
		return "SVIP会员"
	default:
		return "未知等级"
	}
}

func main() {
	fmt.Println(LevelVIP.Name())
}

输出结果:

1
VIP会员

说明:自定义类型可以绑定方法,这也是它和“只是换个名字”的类型别名之间很重要的区别。

一般

第 6 题:用自定义分数类型统计总分和平均分

第 6 题 一般

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

  • 定义自定义类型 Score,底层类型是 int
  • 编写函数 calcStats(scores []Score) (total int, avg float64)
  • 在函数中使用 for range 统计总分和平均分
  • 准备下面这个切片:
1
scores := []Score{78, 92, 85, 60}
  • main 中调用这个函数
  • 输出:
1
2
total=315
avg=78.8
查看参考答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

type Score int

func calcStats(scores []Score) (total int, avg float64) {
	for _, score := range scores {
		total += int(score)
	}

	if len(scores) > 0 {
		avg = float64(total) / float64(len(scores))
	}
	return
}

func main() {
	scores := []Score{78, 92, 85, 60}
	total, avg := calcStats(scores)

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

说明:Score 虽然底层是 int,但在累加到 int total 时仍要显式转换,这正是自定义类型“语义更清晰,但不会自动混用”的体现。

第 7 题:用别名和自定义类型组织订单查询

第 7 题 一般

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

  • 定义类型别名 OrderID = int
  • 定义自定义类型 OrderStatus,底层类型是 int
  • 定义三个状态常量: StatusPending = 1 StatusPaid = 2 StatusShipped = 3
  • OrderStatus 绑定方法 Text() string
  • 定义结构体 Order,字段包括: ID OrderID Buyer string Status OrderStatus
  • 准备一张订单表:
1
2
3
4
orders := map[OrderID]Order{
	1001: {ID: 1001, Buyer: "阿斌", Status: StatusPaid},
	1002: {ID: 1002, Buyer: "小李", Status: StatusPending},
}
  • 读取一个订单编号
  • 如果存在,输出:
1
订单1001,买家:阿斌,状态:已支付
  • 如果不存在,输出:
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import "fmt"

type OrderID = int
type OrderStatus int

const (
	StatusPending OrderStatus = 1
	StatusPaid    OrderStatus = 2
	StatusShipped OrderStatus = 3
)

func (s OrderStatus) Text() string {
	switch s {
	case StatusPending:
		return "待支付"
	case StatusPaid:
		return "已支付"
	case StatusShipped:
		return "已发货"
	default:
		return "未知状态"
	}
}

type Order struct {
	ID     OrderID
	Buyer  string
	Status OrderStatus
}

func main() {
	orders := map[OrderID]Order{
		1001: {ID: 1001, Buyer: "阿斌", Status: StatusPaid},
		1002: {ID: 1002, Buyer: "小李", Status: StatusPending},
	}

	var id int

	fmt.Print("请输入订单编号:")
	fmt.Scan(&id)

	if order, ok := orders[id]; ok {
		fmt.Printf("订单%d,买家:%s,状态:%s\n", order.ID, order.Buyer, order.Status.Text())
	} else {
		fmt.Println("没有找到该订单")
	}
}

说明:这道题里 OrderID 用别名是为了保留和 int 的直接兼容;OrderStatus 用自定义类型是为了让状态值拥有自己的语义和方法。

第 8 题:修正把别名写成自定义类型的代码

第 8 题 一般

下面程序的设计目标是:

  • 用户输入的是普通 int
  • 这个 int 可以直接拿来查询 users 这张表
  • 不希望每次查表时都手动做类型转换

但它现在不能通过编译。请改正代码,并保持上面的设计目标不变。

 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 UserID int

type User struct {
	ID   UserID
	Name string
}

func main() {
	users := map[UserID]User{
		1001: {ID: 1001, Name: "阿斌"},
		1002: {ID: 1002, Name: "小李"},
	}

	var inputID int
	fmt.Print("请输入用户编号:")
	fmt.Scan(&inputID)

	if user, ok := users[inputID]; ok {
		fmt.Println(user.Name)
	} else {
		fmt.Println("没有找到该用户")
	}
}
查看参考答案

修正后的代码如下:

 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 UserID = int

type User struct {
	ID   UserID
	Name string
}

func main() {
	users := map[UserID]User{
		1001: {ID: 1001, Name: "阿斌"},
		1002: {ID: 1002, Name: "小李"},
	}

	var inputID int
	fmt.Print("请输入用户编号:")
	fmt.Scan(&inputID)

	if user, ok := users[inputID]; ok {
		fmt.Println(user.Name)
	} else {
		fmt.Println("没有找到该用户")
	}
}

原因说明:

  • 原来的 type UserID int 定义的是自定义类型,不是别名。
  • 如果目标是“让 int 可以直接使用”,就应该写成 type UserID = int
  • 这道题的重点不是语法细节本身,而是先想清楚这个场景到底要“新类型”还是要“兼容旧类型”。

进阶

下面两题会把前面学过的函数、结构体、切片、map、判断和循环一起用起来,重点考察你是否真的能把“自定义类型”和“类型别名”放进更完整的数据建模场景里。

第 9 题:统计任务状态数量

第 9 题 进阶

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

  • 定义自定义类型 TaskStatus,底层类型是 int
  • 定义三个状态常量: StatusTodo = 1 StatusDoing = 2 StatusDone = 3
  • TaskStatus 绑定方法 Text() string
  • 定义结构体 Task,字段包括: Name string Status TaskStatus
  • 准备下面这组任务数据:
1
2
3
4
5
6
7
tasks := []Task{
	{Name: "看课", Status: StatusTodo},
	{Name: "写笔记", Status: StatusDoing},
	{Name: "做练习", Status: StatusDone},
	{Name: "复盘", Status: StatusTodo},
	{Name: "提交作业", Status: StatusDone},
}
  • 遍历任务切片,用 map[TaskStatus]int 统计每种状态的数量
  • 最后按下面顺序输出:
1
2
3
待开始: 2
进行中: 1
已完成: 2
查看参考答案
 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
47
48
49
package main

import "fmt"

type TaskStatus int

const (
	StatusTodo  TaskStatus = 1
	StatusDoing TaskStatus = 2
	StatusDone  TaskStatus = 3
)

func (s TaskStatus) Text() string {
	switch s {
	case StatusTodo:
		return "待开始"
	case StatusDoing:
		return "进行中"
	case StatusDone:
		return "已完成"
	default:
		return "未知状态"
	}
}

type Task struct {
	Name   string
	Status TaskStatus
}

func main() {
	tasks := []Task{
		{Name: "看课", Status: StatusTodo},
		{Name: "写笔记", Status: StatusDoing},
		{Name: "做练习", Status: StatusDone},
		{Name: "复盘", Status: StatusTodo},
		{Name: "提交作业", Status: StatusDone},
	}

	counts := make(map[TaskStatus]int)
	for _, task := range tasks {
		counts[task.Status]++
	}

	order := []TaskStatus{StatusTodo, StatusDoing, StatusDone}
	for _, status := range order {
		fmt.Printf("%s: %d\n", status.Text(), counts[status])
	}
}

说明:这道题里,自定义类型不只是“看起来更清楚”,它还直接参与了方法调用、切片字段建模和 map 统计键的设计。

第 10 题:实现一个学员等级报告

第 10 题 进阶

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

  • 定义类型别名 StudentID = int
  • 定义自定义类型 ResultLevel,底层类型是 int
  • 定义四个等级常量: LevelA LevelB LevelC LevelD
  • ResultLevel 绑定方法 Text() string
  • 编写函数 judge(score int) ResultLevel score >= 90 返回 LevelA score >= 80 返回 LevelB score >= 60 返回 LevelC 其他返回 LevelD
  • 定义结构体 Student,字段包括: ID StudentID Name string Score int
  • 准备下面这组学生数据:
1
2
3
4
5
6
students := []Student{
	{ID: 1001, Name: "阿斌", Score: 92},
	{ID: 1002, Name: "小李", Score: 85},
	{ID: 1003, Name: "小王", Score: 76},
	{ID: 1004, Name: "小周", Score: 59},
}
  • 遍历学生切片,逐行输出每个学生的编号、姓名和等级
  • 再使用 map[ResultLevel]int 统计各等级人数
  • 最后按 A / B / C / D 的顺序输出统计结果

示例输出可以类似这样:

1
2
3
4
5
6
7
8
1001 阿斌 -> A
1002 小李 -> B
1003 小王 -> C
1004 小周 -> D
A: 1
B: 1
C: 1
D: 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package main

import "fmt"

type StudentID = int
type ResultLevel int

const (
	LevelA ResultLevel = 1
	LevelB ResultLevel = 2
	LevelC ResultLevel = 3
	LevelD ResultLevel = 4
)

func (r ResultLevel) Text() string {
	switch r {
	case LevelA:
		return "A"
	case LevelB:
		return "B"
	case LevelC:
		return "C"
	case LevelD:
		return "D"
	default:
		return "未知"
	}
}

func judge(score int) ResultLevel {
	switch {
	case score >= 90:
		return LevelA
	case score >= 80:
		return LevelB
	case score >= 60:
		return LevelC
	default:
		return LevelD
	}
}

type Student struct {
	ID    StudentID
	Name  string
	Score int
}

func main() {
	students := []Student{
		{ID: 1001, Name: "阿斌", Score: 92},
		{ID: 1002, Name: "小李", Score: 85},
		{ID: 1003, Name: "小王", Score: 76},
		{ID: 1004, Name: "小周", Score: 59},
	}

	counts := make(map[ResultLevel]int)

	for _, student := range students {
		level := judge(student.Score)
		fmt.Printf("%d %s -> %s\n", student.ID, student.Name, level.Text())
		counts[level]++
	}

	order := []ResultLevel{LevelA, LevelB, LevelC, LevelD}
	for _, level := range order {
		fmt.Printf("%s: %d\n", level.Text(), counts[level])
	}
}

说明:这道题把“别名适合兼容旧类型”“自定义类型适合承载业务语义”这两个选择放进同一个小项目里了。StudentID 保持了和 int 的直接兼容,ResultLevel 则承担了等级表达、方法调用和统计分组这些更明确的业务职责。

本文禁止转载
使用 Hugo 构建
主题 StackJimmy 设计 由 Hobin 魔改
最近构建时间:2026-04-17 19:07:48 CST
载入天数...载入时分秒...
发表了 1 篇文章 · 发表了 152 篇笔记 · 总计 18 万 0 千字