当前位置: 首页 > news >正文

Go 学习笔记:结构体嵌入、sort.Interface 与 io.Reader/Writer

Go 学习笔记:结构体嵌入、sort.Interface 与 io.Reader/Writer


今日学习总结

今天继续学习 2.5「方法与接口」的剩余三个核心知识点:

  1. 结构体嵌入(Embedding):用组合的方式复用字段和方法,理解 Go 中 “组合优于继承” 的设计哲学。
  2. sort.Interface 接口实践:通过实现三个方法,让任何自定义类型都能参与标准库的排序。
  3. io.Reader/Writer 接口体系:掌握 Go 最基础的数据流抽象,理解标准库如何通过极简接口实现高可组合性。

一、结构体嵌入(组合优于继承)

1.1 什么是结构体嵌入

Go 的结构体可以包含匿名字段(anonymous field):只写类型名,不写字段名。这种机制叫做结构体嵌入(struct embedding)。

被嵌入的类型会把自己的字段和方法"提升"到外层结构体,外层结构体可以直接访问这些字段和方法,仿佛它们是自己的一样。

typePositionstruct{X,Yfloat64}typeCirclestruct{Position// 匿名字段:嵌入 PositionRadiusfloat64// 命名字段}typeWheelstruct{Circle// 匿名字段:嵌入 CircleSpokesint// 命名字段}

1.2 访问提升后的字段

嵌入后,可以直接访问深层字段,不必写完整路径:

varw Wheel w.X=10// 等价于 w.Circle.Position.X = 10w.Y=20// 等价于 w.Circle.Position.Y = 20w.Radius=50// 等价于 w.Circle.Radius = 50w.Spokes=12

完整路径依然有效,只是 shorter path 让代码更简洁。

1.3 字面量的注意事项

结构体嵌入没有简短的字面量语法。必须按声明的嵌套结构书写:

// 正确写法w:=Wheel{Circle:Circle{Position:Position{X:10,Y:20},Radius:50,},Spokes:12,}// 错误写法:不能省略嵌套层级// w := Wheel{X: 10, Y: 20, Radius: 50, Spokes: 12} // 编译错误

1.4 方法也会被提升

嵌入不仅复用字段,还复用方法。这是 Go 实现"组合"的核心机制。

func(p Position)DistanceToOrigin()float64{returnmath.Sqrt(p.X*p.X+p.Y*p.Y)}// Circle 自动获得 DistanceToOrigin 方法// Wheel 也自动获得 Circle 提升上来的 DistanceToOrigin 方法c:=Circle{Position:Position{X:3,Y:4},Radius:1}fmt.Println(c.DistanceToOrigin())// 5w:=Wheel{Circle:c,Spokes:8}fmt.Println(w.DistanceToOrigin())// 5

1.5 组合 vs 继承

Go 没有类的继承,但嵌入提供了一种比继承更灵活的复用方式:

特性继承(典型 OOP)Go 结构体嵌入
关系is-ahas-a
多态子类 override通过接口实现多态
方法可被 override外层可重新声明同名方法"遮蔽"内层方法
耦合父类子类强耦合组合关系更松散

组合优于继承:Go 通过嵌入和接口实现代码复用,避免了继承层次过深带来的脆弱性。

1.6 完整练习代码

packagemainimport("fmt""math")// 基础类型:位置typePositionstruct{X,Yfloat64}func(p Position)DistanceToOrigin()float64{returnmath.Sqrt(p.X*p.X+p.Y*p.Y)}func(p Position)String()string{returnfmt.Sprintf("Position(%.1f, %.1f)",p.X,p.Y)}// 嵌入 Position 的圆形typeCirclestruct{Position Radiusfloat64}// 为 Circle 增加自己的方法func(c Circle)Area()float64{returnmath.Pi*c.Radius*c.Radius}func(c Circle)String()string{returnfmt.Sprintf("Circle(center=%s, radius=%.1f)",c.Position.String(),c.Radius)}// 嵌入 Circle 的轮子typeWheelstruct{Circle Spokesint}func(w Wheel)String()string{returnfmt.Sprintf("Wheel(%s, spokes=%d)",w.Circle.String(),w.Spokes)}funcmain(){// 1. 嵌套字面量w:=Wheel{Circle:Circle{Position:Position{X:3,Y:4},Radius:5,},Spokes:12,}// 2. 直接访问提升的字段fmt.Println("X:",w.X,"Y:",w.Y,"Radius:",w.Radius)// 3. 调用提升的方法fmt.Println("圆心到原点距离:",w.DistanceToOrigin())// 来自 Positionfmt.Println("圆面积:",w.Area())// 来自 Circle// 4. 外层 String 遮蔽了内层 Stringfmt.Println(w.String())// 5. 通过完整路径显式访问fmt.Println(w.Circle.Position.String())}

二、sort.Interface 接口实践

2.1 排序接口的本质

Go 的sort包提供了一个通用排序函数sort.Sort,它不要求被排序的元素是什么具体类型,只要求它们实现sort.Interface

packagesorttypeInterfaceinterface{Len()intLess(i,jint)bool// i, j 是序列下标Swap(i,jint)}

排序算法不关心数据本身,它只关心三件事:序列多长、两个元素谁小、怎么交换。这就是接口的威力。

2.2 为自定义类型实现排序

假设有一个Book类型,我们想按价格排序:

typeBookstruct{TitlestringAuthorstringPricefloat64}// 按价格升序排序的切片类型typeByPrice[]Bookfunc(b ByPrice)Len()int{returnlen(b)}func(b ByPrice)Less(i,jint)bool{returnb[i].Price<b[j].Price}func(b ByPrice)Swap(i,jint){b[i],b[j]=b[j],b[i]}

排序:

books:=[]Book{{"Go语言圣经","Alan Donovan",89},{"Go语言设计与实现","左书祺",99},{"Go语言高级编程","柴树杉",79},}sort.Sort(ByPrice(books))

2.3 常用便捷函数

对于常见的切片类型,sort包已经提供了现成实现:

ints:=[]int{3,1,4,1,5,9,2,6}sort.Ints(ints)// []int 排序strs:=[]string{"banana","apple","cherry"}sort.Strings(strs)// []string 排序floats:=[]float64{3.14,1.41,2.71}sort.Float64s(floats)// []float64 排序

2.4 反向排序

sort.Reverse可以对任何sort.Interface产生反向排序器:

sort.Sort(sort.Reverse(ByPrice(books)))// 按价格降序

2.5 多级排序与自定义比较函数

实际场景中经常需要按多个字段排序。可以写一个结构体,把切片和比较函数组合起来:

typeByFuncstruct{items[]Book lessfunc(a,b Book)bool}func(b ByFunc)Len()int{returnlen(b.items)}func(b ByFunc)Less(i,jint)bool{returnb.less(b.items[i],b.items[j])}func(b ByFunc)Swap(i,jint){b.items[i],b.items[j]=b.items[j],b.items[i]}// 使用:先按作者排序,作者相同再按价格排序sort.Sort(ByFunc{items:books,less:func(a,b Book)bool{ifa.Author!=b.Author{returna.Author<b.Author}returna.Price<b.Price},})

2.6 完整练习代码

packagemainimport("fmt""sort")typeBookstruct{TitlestringAuthorstringPricefloat64}// 按价格排序typeByPrice[]Bookfunc(b ByPrice)Len()int{returnlen(b)}func(b ByPrice)Less(i,jint)bool{returnb[i].Price<b[j].Price}func(b ByPrice)Swap(i,jint){b[i],b[j]=b[j],b[i]}// 按作者排序,作者相同按书名typeByAuthor[]Bookfunc(b ByAuthor)Len()int{returnlen(b)}func(b ByAuthor)Less(i,jint)bool{ifb[i].Author!=b[j].Author{returnb[i].Author<b[j].Author}returnb[i].Title<b[j].Title}func(b ByAuthor)Swap(i,jint){b[i],b[j]=b[j],b[i]}// 通用自定义比较排序器typecustomSortstruct{books[]Book lessfunc(a,b Book)bool}func(c customSort)Len()int{returnlen(c.books)}func(c customSort)Less(i,jint)bool{returnc.less(c.books[i],c.books[j])}func(c customSort)Swap(i,jint){c.books[i],c.books[j]=c.books[j],c.books[i]}funcprintBooks(books[]Book){for_,b:=rangebooks{fmt.Printf("%-20s %-12s ¥%.2f\n",b.Title,b.Author,b.Price)}fmt.Println()}funcmain(){books:=[]Book{{"Go语言圣经","Alan Donovan",89.0},{"Go语言设计与实现","左书祺",99.0},{"Go语言高级编程","柴树杉",79.0},{"Go并发编程实战","Katherine Cox-Buday",89.0},}// 1. 按价格升序sort.Sort(ByPrice(books))fmt.Println("按价格升序:")printBooks(books)// 2. 按价格降序sort.Sort(sort.Reverse(ByPrice(books)))fmt.Println("按价格降序:")printBooks(books)// 3. 按作者排序sort.Sort(ByAuthor(books))fmt.Println("按作者+书名排序:")printBooks(books)// 4. 自定义多级排序:先按价格,再按作者sort.Sort(customSort{books:books,less:func(a,b Book)bool{ifa.Price!=b.Price{returna.Price<b.Price}returna.Author<b.Author},})fmt.Println("自定义:先价格、后作者:")printBooks(books)// 5. 基础类型便捷排序nums:=[]int{3,1,4,1,5,9,2,6}sort.Ints(nums)fmt.Println("sort.Ints:",nums)// 6. 判断是否已排序fmt.Println("是否已排序:",sort.IntsAreSorted(nums))}

三、io.Reader/Writer 接口体系

3.1 数据流的最小抽象

Go 标准库的io包定义了两个最基础、最重要的接口:

packageio// 读取:把数据读到缓冲区 p 中typeReaderinterface{Read(p[]byte)(nint,errerror)}// 写入:把缓冲区 p 中的数据写出去typeWriterinterface{Write(p[]byte)(nint,errerror)}

这两个接口都只有一个方法,但它们是 Go 所有 I/O 操作的基石。文件、网络连接、内存缓冲区、压缩流、加密流等,都实现了io.Readerio.Writer

3.2 Reader 的使用要点

Read方法:不保证一次读满整个缓冲区。调用者应该处理n返回的实际字节数,并正确处理io.EOF(读到末尾)和其他错误。

标准做法:

buf:=make([]byte,1024)n,err:=r.Read(buf)ifn>0{// 处理读取到的 n 个字节}iferr!=nil{iferr==io.EOF{// 读到末尾}else{// 其他错误}}

3.3 Writer 的使用要点

Write方法:不保证一次写完所有数据。如果返回的n < len(p),必须返回非 nil 错误。

n,err:=w.Write(data)iferr!=nil{// 处理错误}ifn!=len(data){// 正常情况下不会到这里,但应做检查}

3.4 接口组合:io.ReadWriter

typeReadWriterinterface{Reader Writer}

一个类型只要同时实现了ReadWrite,就满足io.ReadWriter*os.File*bytes.Buffer都实现了这个接口。

3.5 为什么 io.Reader/Writer 如此强大

因为这两个接口足够小,所以可以任意组合:

  • io.Copy(dst Writer, src Reader):把任意 Reader 的内容复制到任意 Writer。
  • io.TeeReader(r Reader, w Writer):读取的同时写入另一个 Writer。
  • io.MultiWriter(writers ...Writer):把一份数据同时写入多个 Writer。
  • io.LimitReader(r Reader, n int64):限制最多读取 n 个字节。

3.6 完整练习代码

packagemainimport("bytes""fmt""io""strings")// 自定义 Writer:统计写入的字节数typeCountingWriterstruct{Writer io.Writer TotalBytesint64}func(cw*CountingWriter)Write(p[]byte)(nint,errerror){n,err=cw.Writer.Write(p)cw.TotalBytes+=int64(n)returnn,err}// 自定义 Reader:把字符串作为数据源typeStringReaderstruct{datastringoffsetint}func(sr*StringReader)Read(p[]byte)(nint,errerror){ifsr.offset>=len(sr.data){return0,io.EOF}n=copy(p,sr.data[sr.offset:])sr.offset+=nreturnn,nil}funcmain(){// 1. 使用标准库的 strings.NewReaderr:=strings.NewReader("Hello, Go! 这是一段测试数据。")buf:=new(bytes.Buffer)io.Copy(buf,r)fmt.Println("复制结果:",buf.String())// 2. 自定义 Writer 统计字节数cw:=&CountingWriter{Writer:new(bytes.Buffer)}fmt.Fprint(cw,"abc123中文")fmt.Printf("CountingWriter 写入字节数: %d\n",cw.TotalBytes)// 3. 自定义 Reader 读取字符串sr:=&StringReader{data:"ABCDEFG"}tmp:=make([]byte,3)for{n,err:=sr.Read(tmp)ifn>0{fmt.Printf("读取 %d 字节: %s\n",n,string(tmp[:n]))}iferr==io.EOF{break}iferr!=nil{fmt.Println("读取出错:",err)break}}// 4. io.TeeReader:读取的同时写入另一个 Writersrc:=strings.NewReader("TeeReader 测试数据")varcopyTo bytes.Buffer tee:=io.TeeReader(src,&copyTo)varresult bytes.Buffer io.Copy(&result,tee)fmt.Println("TeeReader 原始数据:",result.String())fmt.Println("TeeReader 副本数据:",copyTo.String())// 5. io.MultiWriter:同时写入多个 Writervarbuf1,buf2 bytes.Buffer mw:=io.MultiWriter(&buf1,&buf2)fmt.Fprint(mw,"MultiWriter 数据")fmt.Println("buf1:",buf1.String())fmt.Println("buf2:",buf2.String())// 6. io.LimitReader:限制读取长度limited:=io.LimitReader(strings.NewReader("1234567890"),5)limitedBuf:=new(bytes.Buffer)io.Copy(limitedBuf,limited)fmt.Println("LimitReader 结果:",limitedBuf.String())}

四、知识要点速查表

知识点核心要点易错点
结构体嵌入匿名字段会提升字段和方法字面量不能省略嵌套层级
方法提升外层可直接调用内层方法外层同名方法会遮蔽内层方法
组合 vs 继承Go 用组合 + 接口实现复用没有is-a的继承关系
sort.Interface实现 Len/Less/Swap 即可排序下标 i, j 对应的是排序序列中的位置
sort.Reverse包装已有 Interface 做降序不需要重新定义新类型
io.ReaderRead(p)不保证读满先处理 n > 0,再处理 err == io.EOF
io.WriterWrite(p)不保证写满n < len§ 时必须返回非 nil 错误
io.ReadWriterReader + Writer 的组合os.File、bytes.Buffer 都满足
io.Copy任意 Reader → 任意 Writer会自动处理缓冲区循环读取

五、学习心得

  1. 结构体嵌入是 Go 的"轻量级继承":它让代码复用变得简单,但本质上仍是组合。外层类型和被嵌入类型之间没有子类关系,理解这一点就不会把 Java/C++ 的继承思维带进来。

  2. sort.Interface 是接口威力的经典体现:只要实现三个方法,任何自定义类型都能复用标准库的排序算法。这种"最小接口 + 最大复用"的设计思想值得在写自己的代码时模仿。

  3. io.Reader/Writer 是 Go 的 I/O 通用语言:接口越小,组合越强。写函数时尽量接收io.Reader/io.Writer而不是*os.File,代码会更灵活、更可测试。


明日计划:继续 2.5 方法与接口收尾 —— 常见接口设计陷阱(如 nil interface、值接收器与指针接收器对接口的影响),然后进入 2.6 Goroutine 与 Channel 并发编程。

http://www.jsqmd.com/news/1197824/

相关文章:

  • 如何用最小二乘法从零推导线性回归方程?(高中数学视角)
  • LXI总线实战:从规范到模块化设计的开发指南
  • (2026最新)遂宁防水补漏本地人必选的正规靠谱公司推荐-房屋漏水检测维修师傅上门-卫生间/厨房/阳台/房顶/外墙漏水检测精准测漏 - 即刻修防水
  • 基于 Python LangChain/LlamaIndex + FastAPI + Streamlit + Ollama 构建企业离线内网 RAG 知识问答系统
  • 高精度0-5V DAC输出电路:从电压基准到驱动代码的完整设计
  • 真力时中国官方售后服务中心|服务热线及门店详细地址权威信息公示(2026年7月最新) - 亨得利官方服务中心
  • 大模型的涌现 ~ Emergent Abilities
  • C++实战:Windows API打造高效鼠标连点器,从原理到防检测技巧
  • 2026 年徐汇专业的启闭式通风气楼品牌综合实力解析,夏天空调房闷热?它如何悄悄解决空气循环难题 - 行业鉴选官
  • Cloud Studio如何解决开发环境不确定性问题
  • CSGO茄子5EPL直播高光操作解析与战术学习指南
  • C++图形界面开发:从零实现EasyX文本框控件,掌握事件驱动与状态管理
  • Cursor小项目极简开发流程:三层操作+物理化约束
  • 在现有 Ceph 集群搭建 S3 对象存储
  • 2026 年新消息:鹿邑诚信的无机纤维棉喷涂施工优质厂家选哪家,揭秘!专业喷涂如何让你的材料寿命翻倍 - 行业鉴选官
  • BurpSuite实战插件精选:从安装到漏洞挖掘的进阶指南
  • 信奥P1289磁盘碎片整理:贪心算法与序列操作详解
  • Activiti5.22.0国产化适配实践:源码级改造支持达梦数据库
  • AWS安全组SG原理与最佳实践:应用层连接跟踪器详解
  • 学ai 第二日python 复习夯实基础
  • 大模型应用开发06:PDF 文件对话技术方案对比与多模态解析实践
  • 后面需要进行修改的地方
  • OpenAI提示词指南:结果导向方法提升AI交互效率
  • 京东开发岗面试题目
  • Win11安装Claude Code必须用WSL2+NVM+Node 20
  • 从方向盘转角到车轮轨迹:揭秘汽车转向几何与转弯半径的底层逻辑
  • Trilium自托管安装实战:MySQL+反向代理+生产级加固
  • Docker安装失败根因解析:从内核模块到权限配置的全链路排查
  • 阿里云OpenClaw+Hermes Agent企业微信部署实战指南
  • Windows窗口置顶工具:提升多任务效率的AlwaysOnTop完全指南