练习
学完本章你应该掌握
- 能围绕“行为”定义接口,并让结构体、自定义类型等不同值通过方法集隐式实现接口。
- 能把接口和切片、map、函数、循环、输入输出结合起来,写出统一调用多种实现的代码。
- 能区分“接口用于约束行为”和“
any用于临时接收任意值”这两类场景,而不是混着使用。 - 能在需要时通过类型断言和
type switch安全拿回具体类型,并继续访问字段或做更细分的处理。 - 能识别并修正常见接口错误,例如方法签名不匹配、指针接收者导致接口赋值失败、断言失败未处理。
- 能把
01-08单元的结构体、方法、切片、map、判断、循环、函数、高阶函数、自定义类型等知识自然接到接口题目里。
一般
第 1 题:统一调用多个会说话的角色
编写一个 Go 程序,完成下面要求:
- 定义接口
Speaker,要求实现Speak() string - 定义结构体
Student和Robot Student.Speak()返回:学生小李:我在学接口Robot.Speak()返回:机器人R1:ready- 再编写函数
sayAll(list []Speaker),统一输出切片里每个值的说话内容 - 在
main中构造一个[]Speaker并调用sayAll
查看参考答案
| |
说明:这道题的重点不是“定义两个结构体”,而是体会 sayAll 为什么可以只接收 []Speaker,却同时处理不同具体类型。
第 2 题:用接口统一计算付款金额
编写一个 Go 程序,完成下面要求:
- 定义接口
Payable,要求实现Pay() int - 定义结构体
CourseOrder,字段有Title string、Price int - 定义结构体
BookOrder,字段有Title string、Price int、Discount int CourseOrder.Pay()返回课程价格BookOrder.Pay()返回Price - Discount- 再编写函数
calcTotal(items []Payable) int,统计总付款金额 - 在
main中放入下面两笔订单:
| |
- 最后输出
total=207
查看参考答案
| |
说明:这道题把接口和切片、循环、函数结合起来了。调用方只关心“这项内容能不能算出应付金额”,不关心它到底是课程订单还是图书订单。
第 3 题:按问候风格查询接口实现
编写一个 Go 程序,完成下面要求:
- 定义接口
Greeter,要求实现Greet(name string) string - 定义
ChineseGreeter和EnglishGreeter ChineseGreeter.Greet("小王")返回你好,小王EnglishGreeter.Greet("Tom")返回Hello, Tom- 使用
map[string]Greeter保存两种问候方式:cn对应中文问候en对应英文问候 - 读取两个输入:问候方式和姓名
- 如果存在该问候方式,就输出结果
- 如果不存在,输出
不支持这种问候方式
例如输入:
| |
查看参考答案
| |
说明:这里的 map[string]Greeter 很关键。它把“键查找”与“接口统一调用”接在了一起,是很常见的基础设计方式。
第 4 题:修正指针接收者导致的接口赋值问题
下面程序的目标是输出:
| |
但它现在不能通过编译。请改正代码,让它正常运行。
| |
查看参考答案
修正后的代码如下:
| |
原因说明:
Update是定义在*Profile上的方法,不是定义在Profile值上的方法。- 所以满足
Updater接口的是*Profile,不是Profile。 - 这里把
p改成&p后,接口里保存的就是结构体地址,修改会同步反映到外部变量。
第 5 题:实现一个 any 通用说明器
编写一个函数 describe(value any),完成下面要求:
- 如果传入的是
string,输出:string chars=字符数 - 如果传入的是
int,输出:int square=平方值 - 如果传入的是
Student,输出:student 姓名 分数 - 其他类型输出:
unknown
要求:
- 自己定义结构体
Student - 在
main中至少测试下面四个值:"接口"12Student{Name: "小李", Score: 88}true
查看参考答案
| |
说明:
any等价于interface{},适合临时接收任意类型的值。- 这里用
type switch做分流;字符串长度使用len([]rune(v)),这样中文按字符数统计更直观。
第 6 题:从混合切片中提取实现了接口的值
编写一个 Go 程序,完成下面要求:
- 定义接口
Speaker,要求实现Speak() string - 定义结构体
Dog和Cat,都实现Speak() string - 编写函数
collectSpeeches(items []any) []string - 这个函数遍历混合切片,只把实现了
Speaker的值取出来,并收集它们的说话内容 - 在
main中准备下面这组数据:
| |
- 最后逐行输出收集到的内容
查看参考答案
| |
说明:这道题体现了“先用 any 容纳混合数据,再通过类型断言挑出满足某个接口的值”这条常见处理路线。
进阶
下面 6 题会把接口和自定义类型、结构体指针、切片、map、函数、高阶函数、循环菜单一起用起来,更接近真实的程序组织方式。
第 7 题:让自定义类型也实现接口
编写一个 Go 程序,完成下面要求:
- 定义接口
Textable,要求实现Text() string - 定义自定义类型
OrderStatus,底层类型是int - 定义自定义类型
TaskStatus,底层类型是int - 给这两个自定义类型都绑定
Text() string方法 - 准备一个
[]Textable - 让它同时保存订单状态和任务状态
- 最后统一输出每一项的文字描述
提示:这道题的重点是体会“接口并不只给结构体用,自定义类型同样可以实现接口”。
查看参考答案
| |
说明:这里真正实现接口的不是结构体,而是两个自定义类型。只要方法匹配,接口并不关心底层到底是什么。
第 8 题:用接口统一处理不同学习资源
编写一个 Go 程序,完成下面要求:
- 定义接口
Resource,要求实现两个方法:Title() stringStudyMinutes() int - 定义结构体
Article,字段有:Name stringWordCount int - 定义结构体
Video,字段有:Name stringDuration int - 约定文章阅读时间按“每 200 字算 1 分钟,向上取整”计算
- 编写函数
calcPlan(resources []Resource) int,统计总学习时间 - 在
main中准备下面这组资源:
| |
- 先逐行输出每个资源的标题和学习时间
- 再输出总学习时间
查看参考答案
| |
说明:这道题把结构体、方法、接口、切片、循环和判断连起来了。文章和视频字段不同,但对外都能回答“标题是什么”“要学多久”。
第 9 题:用指针接收者和接口批量给学生加分
编写一个 Go 程序,完成下面要求:
- 定义接口
BonusAdder,要求实现:AddBonus(points int)Summary() string - 定义结构体
Student,字段有:Name stringScore int - 让
Student用指针接收者实现这两个方法 - 准备下面这组学生数据:
| |
- 只把分数小于
60的学生放进[]BonusAdder - 统一给这些学生加
5分 - 最后按原切片顺序输出所有学生的最终成绩
查看参考答案
| |
说明:
- 这里一定要把
&students[i]放进接口切片,否则改到的就只是副本。 - 这道题把“指针接收者能修改原值”和“接口可以统一批量操作”真正组合到了一起。
第 10 题:统计接口切片中每种具体类型的数量
编写一个 Go 程序,完成下面要求:
- 定义接口
Speaker - 定义结构体
Dog、Cat、Robot - 三者都实现
Speak() string - 准备下面这个接口切片:
| |
- 编写函数
countKinds(list []Speaker) map[string]int - 使用
type switch统计每种具体类型的数量 - 最后按下面顺序输出:
| |
查看参考答案
| |
说明:这道题先把不同类型统一放进接口切片,再用 type switch 做“二次细分”,很适合理解接口和具体类型的关系。
第 11 题:用高阶函数筛选接口值
编写一个 Go 程序,完成下面要求:
- 定义接口
Speaker - 定义结构体
Student和Robot,都实现Speak() string - 编写函数
filterSpeakers(list []Speaker, checker func(string) bool) []Speaker - 它的作用是:只保留
Speak()返回值满足条件的对象 - 再编写闭包工厂函数
makeKeywordChecker(keyword string) func(string) bool - 它返回的新函数用于判断一段文本里是否包含指定关键字
- 在
main中准备下面这组数据:
| |
- 使用关键字
接口进行筛选 - 最后输出筛选后的说话内容
提示:可以使用 strings.Contains
查看参考答案
| |
说明:
filterSpeakers体现了“函数作为参数”的高阶函数用法。makeKeywordChecker体现了闭包,它会记住外层传进来的keyword。strings.Contains用来判断字符串中是否包含某段子串。
第 12 题:实现一个多渠道提醒中心
请完成一个稍微完整一点的小场景:
- 定义接口
Notifier,要求实现Send(user, message string) string - 定义三种实现:
EmailNotifierSMSNotifierAppNotifier - 使用
map[int]Notifier保存这三种通知渠道:1表示邮件2表示短信3表示 App - 使用
for {}做循环菜单 - 每轮读取一个选项:
1、2、3表示发送通知0表示退出程序 - 如果输入合法,就给用户
小李发送消息今晚完成接口练习 - 如果输入其他值,输出
无效选项
查看参考答案
| |
说明:这道题把接口、结构体、map、输入、循环和条件分流全都串起来了。以后你再增加一种通知方式时,通常只需要新增一个实现,而不用重写整套菜单逻辑。