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

Go学习第8天:接口 + 泛型 + 错误处理

Go :接口、泛型、错误处理

    • 目录
  • 一、接口(Interface)
    • 1.1 核心特性
    • 1.2 基础语法与使用
      • 1.2.1 接口定义
      • 1.2.2 接口实现
      • 1.2.3 多态演示
    • 1.3 空接口 interface{}
      • 示例:通用打印函数
    • 1.4 接口组合
    • 1.5 类型断言 & 类型选择
      • 1.5.1 基础类型断言(两种写法)
      • 1.5.2 类型选择(type switch)
    • 1.6 接口高频踩坑
  • 二、泛型(Generics)
    • 2.1 核心概念
    • 2.2 基础语法与内置约束
      • 2.2.1 通用语法
        • 泛型函数
        • 泛型结构体
      • 2.2.2 三大内置约束
      • 2.2.3 示例演示
        • 示例1:any 约束(任意类型通用函数)
        • 示例2:comparable 约束(可比较类型)
        • 示例3 自定义联合约束(限定数字类型)
    • 2.3 常用泛型实战案例
      • 案例1:通用工具函数(交换、去重、判断包含)
      • 案例2:泛型结构体(通用栈)
    • 2.4 泛型高频踩坑
  • 三、错误处理
    • 3.1 基础:error 接口
      • 3.1.1 创建普通错误
      • 3.1.2 函数返回错误(标准写法)
    • 3.2 自定义错误类型
    • 3.3 错误包装与解析(Go 1.13+)
      • 3.3.1 错误包装
      • 3.3.2 错误解析
    • 3.4 panic & recover(处理致命错误)
      • 标准使用模板
    • 3.5 错误处理高频踩坑
  • 四、知识点速记表

目录

  1. 接口(Interface)
  2. 泛型(Generics)
  3. 错误处理(Error、panic、recover)
  4. 知识点速记

一、接口(Interface)

接口是 Go 实现行为契约、多态、解耦的核心特性,它只定义一组方法签名,不包含字段与方法实现。Go 采用隐式实现,无需关键字声明实现关系,只要类型实现接口全部方法,就默认适配该接口。

1.1 核心特性

  1. 隐式实现:无implements关键字,实现接口所有方法即代表实现接口;
  2. 接口变量:存储动态类型(实际类型)+动态值,零值为nil
  3. 空接口interface{}:可接收任意类型,是所有类型的父集;
  4. 接口组合:支持嵌套多个接口,实现接口继承效果;
  5. 多态:同一接口变量可绑定不同实现类型,调用同名方法执行不同逻辑。

1.2 基础语法与使用

1.2.1 接口定义

语法格式:

type接口名interface{方法名1(参数列表)返回值列表 方法名2(参数列表)返回值列表// ... 多个方法}

示例(图形面积、周长接口):

packagemainimport"math"import"fmt"// 定义 Shape 接口,约定两个行为:求面积、周长typeShapeinterface{Area()float64Perimeter()float64}

1.2.2 接口实现

任意自定义类型(常用结构体)实现接口全部方法,即自动实现接口。

// 圆形结构体typeCirclestruct{Radiusfloat64}// 实现 Shape 接口的 Area 方法func(c Circle)Area()float64{returnmath.Pi*c.Radius*c.Radius}// 实现 Shape 接口的 Perimeter 方法func(c Circle)Perimeter()float64{return2*math.Pi*c.Radius}funcmain(){vars Shape// 定义接口类型变量c:=Circle{Radius:5}s=c// 结构体实例赋值给接口变量(多态)fmt.Println("面积:",s.Area())fmt.Println("周长:",s.Perimeter())}

1.2.3 多态演示

不同结构体实现同一接口,接口变量切换实例,执行不同逻辑:

packagemainimport"fmt"// 手机接口typePhoneinterface{call()}// 诺基亚结构体typeNokiaPhonestruct{}func(n NokiaPhone)call(){fmt.Println("诺基亚:拨打电话")}// 苹果手机结构体typeIPhonestruct{}func(i IPhone)call(){fmt.Println("iPhone:拨打电话")}funcmain(){varp Phone p=NokiaPhone{}p.call()p=IPhone{}p.call()}

1.3 空接口 interface{}

空接口没有定义任何方法,因此 Go 中所有类型都默认实现空接口,常用于通用参数、通用容器。

示例:通用打印函数

packagemainimport"fmt"// 接收任意类型参数funcprintValue(valinterface{}){fmt.Printf("值:%v,类型:%T\n",val,val)}funcmain(){printValue(100)printValue("Go语言")printValue(3.14)printValue([]int{1,2,3})}

1.4 接口组合

将多个接口嵌套,组合成新接口,实现接口复用:

packagemainimport"fmt"typeReaderinterface{Read()string}typeWriterinterface{Write(datastring)}// 组合接口:同时拥有读、写能力typeReadWriterinterface{Reader Writer}// 文件结构体,同时实现两个子接口typeFilestruct{}func(f File)Read()string{return"读取文件内容"}func(f File)Write(datastring){fmt.Println("写入内容:",data)}funcmain(){varrw ReadWriter=File{}fmt.Println(rw.Read())rw.Write("测试数据")}

1.5 类型断言 & 类型选择

接口变量存储的是抽象类型,如需还原底层具体类型,使用类型断言;批量判断类型使用type switch

1.5.1 基础类型断言(两种写法)

  1. 直接断言(失败触发panic,不推荐)
    variinterface{}="测试字符串"str:=i.(string)
  2. 安全断言(搭配ok判断,工程首选)
    packagemainimport"fmt"funcmain(){variinterface{}=123str,ok:=i.(string)ifok{fmt.Println("断言成功:",str)}else{fmt.Println("类型不匹配")}}

1.5.2 类型选择(type switch)

批量匹配接口底层类型,适合多类型分支判断:

packagemainimport"fmt"funccheckType(valinterface{}){switchv:=val.(type){caseint:fmt.Println("整型:",v)casestring:fmt.Println("字符串:",v)casefloat64:fmt.Println("浮点型:",v)default:fmt.Println("未知类型")}}funcmain(){checkType(666)checkType("接口测试")}

1.6 接口高频踩坑

  1. 方法未全部实现:结构体只实现接口部分方法,编译报错;
  2. 值接收 vs 指针接收:接口方法如果是指针接收者,只能赋值结构体指针,不能赋值结构体实例;
  3. 空接口误用:滥用interface{}会丢失类型安全,尽量优先具体类型;
  4. 断言不做判断:直接断言不使用ok,类型不匹配直接程序崩溃;
  5. nil 接口判断:接口变量 =nil的条件是动态类型、动态值全部为 nil,仅值为 nil 时判断结果为 false。

二、泛型(Generics)

泛型是 Go 1.18 正式引入的特性,用于编写类型无关、可复用的通用代码,避免为不同类型重复编写相同逻辑。核心组成:类型参数+类型约束

2.1 核心概念

  1. 类型参数:函数/结构体后用[]声明占位类型,如[T]
  2. 类型约束:限制类型参数的可用类型(如任意类型、可比较类型、自定义类型集合);
  3. 类型推断:多数场景编译器可自动推导类型,无需显式声明。

2.2 基础语法与内置约束

2.2.1 通用语法

泛型函数
func函数名[T 约束](参数列表)返回值{函数体}
泛型结构体
type结构体名[T 约束]struct{字段}

2.2.2 三大内置约束

约束含义适用场景
any等价interface{},允许任意类型不限制类型的通用工具
comparable允许使用==/!=比较的类型Map 键、元素查找、去重
自定义联合约束用 `` 组合多种具体类型

2.2.3 示例演示

示例1:any 约束(任意类型通用函数)
packagemainimport"fmt"// T 为任意类型funcPrintAny[T any](val T){fmt.Printf("值:%v,类型:%T\n",val)}funcmain(){PrintAny(10)PrintAny("泛型测试")PrintAny(3.14)}
示例2:comparable 约束(可比较类型)

实现通用切片元素查找:

packagemainimport"fmt"// 仅支持可比较类型funcFindIndex[T comparable](slice[]T,target T)int{foridx,v:=rangeslice{ifv==target{returnidx}}return-1}funcmain(){nums:=[]int{1,2,3}fmt.Println(FindIndex(nums,2))// 输出 1}
示例3 自定义联合约束(限定数字类型)
packagemainimport"fmt"// 自定义数字约束typeNumberinterface{int|int8|int16|int32|int64|uint|float32|float64}// 通用加法函数funcAdd[T Number](a,b T)T{returna+b}funcmain(){fmt.Println(Add(10,20))fmt.Println(Add(1.5,2.5))}

2.3 常用泛型实战案例

案例1:通用工具函数(交换、去重、判断包含)

packagemainimport"fmt"// 交换两个任意类型变量funcSwap[T any](a,b T)(T,T){returnb,a}// 切片去重funcUnique[T comparable](slice[]T)[]T{m:=make(map[T]bool)varres[]Tfor_,v:=rangeslice{if!m[v]{m[v]=trueres=append(res,v)}}returnres}funcmain(){a,b:=1,2a,b=Swap(a,b)fmt.Println(a,b)old:=[]int{1,1,2,2,3}fmt.Println(Unique(old))}

案例2:泛型结构体(通用栈)

packagemainimport"fmt"// 泛型栈typeStack[T any]struct{elements[]T}// 入栈func(s*Stack[T])Push(val T){s.elements=append(s.elements,val)}// 出栈func(s*Stack[T])Pop()(T,bool){iflen(s.elements)==0{varzero Treturnzero,false}idx:=len(s)-1val:=s.elements[idx]s.elements=s.elements[:idx]returnval,true}funcmain(){// 整型栈intStack:=Stack[int]{}intStack.Push(10)fmt.Println(intStack.Pop())}

2.4 泛型高频踩坑

  1. 版本限制:泛型仅 Go 1.18 及以上版本支持,低版本直接编译报错;
  2. 约束误用:对非可比较类型(切片、Map)使用comparable约束,编译失败;
  3. 类型推断失效:部分复杂场景需手动指定泛型类型函数名[类型](参数)
  4. 过度使用泛型:简单逻辑无需泛型,增加代码可读性负担;
  5. 方法约束:泛型类型的方法不能再额外添加类型参数。

三、错误处理

Go 不使用传统try-catch异常机制,采用显式错误返回为核心,搭配panic(致命恐慌)和recover(恢复恐慌)处理严重异常,分为三类场景:普通业务错误、运行恐慌、自定义错误。

3.1 基础:error 接口

error是 Go 内置接口,所有错误类型都必须实现该接口:

typeerrorinterface{Error()string// 返回错误描述文本}

3.1.1 创建普通错误

使用标准库errors.New创建基础错误:

packagemainimport"errors"import"fmt"funcmain(){err:=errors.New("参数非法")fmt.Println(err)}

3.1.2 函数返回错误(标准写法)

业务函数通常将error作为最后一个返回值nil代表无错误:

packagemainimport"errors"import"fmt"// 除法函数,除数为0返回错误funcdivide(a,bint)(int,error){ifb==0{return0,errors.New("除数不能为0")}returna/b,nil}funcmain(){res,err:=divide(10,0)iferr!=nil{// 优先判断错误fmt.Println("执行失败:",err)return}fmt.Println("结果:",res)}

3.2 自定义错误类型

通过结构体实现 error 接口,扩展错误信息(错误码、详情等):

packagemainimport"fmt"// 自定义除法错误typeDivideErrstruct{Dividendint// 被除数Divisorint// 除数}// 实现 error 接口func(e*DivideErr)Error()string{returnfmt.Sprintf("错误:%d 不能除以 %d",e.Dividend,e.Divisor)}funcdivide(a,bint)(int,error){ifb==0{return0,&DivideErr{Dividend:a,Divisor:b}}returna/b,nil}funcmain(){_,err:=divide(20,0)fmt.Println(err)}

3.3 错误包装与解析(Go 1.13+)

3.3.1 错误包装

使用fmt.Errorf("%w", 原始错误)包装错误,保留原始错误链:

packagemainimport"errors"import"fmt"varErrNotFound=errors.New("数据不存在")funcqueryData(idint)error{// 包装原始错误,追加上下文returnfmt.Errorf("查询id=%d 失败:%w",id,ErrNotFound)}

3.3.2 错误解析

  1. errors.Is:判断错误链中是否包含指定原始错误;
  2. errors.As:将错误转为自定义错误类型。
packagemainimport"errors"import"fmt"varErrNotFound=errors.New("数据不存在")funcqueryData(idint)error{returnfmt.Errorf("查询id=%d 失败:%w",id,ErrNotFound)}funcmain(){err:=queryData(1001)// 判断是否是指定错误iferrors.Is(err,ErrNotFound){fmt.Println("根错误:数据不存在")}}

3.4 panic & recover(处理致命错误)

  • panic:主动抛出运行恐慌,程序停止正常执行,逐层执行defer;用于不可恢复的严重错误(如配置加载失败、核心依赖缺失);
  • recover:搭配defer使用,捕获panic,恢复程序运行。

标准使用模板

packagemainimport"fmt"funcsafeFunc(){// 必须在 defer 匿名函数内调用 recoverdeferfunc(){ifr:=recover();r!=nil{fmt.Println("捕获恐慌:",r)}}()// 触发恐慌panic("数组下标越界")}funcmain(){fmt.Println("程序开始")safeFunc()fmt.Println("程序继续执行")// 未崩溃,正常运行}

3.5 错误处理高频踩坑

  1. 忽略错误返回值:函数返回error时不判断,隐藏线上隐患;
  2. 错误判断顺序错误:先使用结果、再判断错误(错误时结果为零值);
  3. recover 使用错误recover必须放在defer匿名函数内,嵌套/后置均无法捕获;
  4. 滥用 panic:普通业务错误使用errorpanic仅用于致命场景;
  5. 错误包装丢失根错误:包装错误必须使用%w,否则无法用errors.Is/As解析。

四、知识点速记表

模块核心要点高频踩坑
接口隐式实现、空接口接收任意类型、类型断言/type switch、接口组合方法实现不全、指针/值接收混淆、直接断言不判空
泛型类型参数+约束、any/comparable/自定义约束、泛型函数/结构体Go1.18以下不支持、约束类型不匹配、过度泛型
错误处理error 接口、显式返回错误、%w包装错误、panic+recover忽略error、refer使用位置错误、普通场景滥用panic
http://www.gsyq.cn/news/1526653.html

相关文章:

  • 别再纠结C#和Qt了!从零到一,用.NET MAUI搞定你的第一个跨平台桌面App
  • 青岛配眼镜哪里好,适合什么人选镜指南 - 配眼镜新资讯
  • TV Bro浏览器:智能电视上网的终极解决方案
  • 2026年6月常州GEO/SEO全链路服务商评测:十家头部公司推荐榜单 - 936品牌测评网
  • 保姆级教程:用MoveIt Setup Assistant配置你的第一个URDF机器人模型(含Gazebo文件生成避坑)
  • YOLO小目标检测救星:实测CARAFE对比双线性插值/反卷积,mAP提升多少?
  • Pandas数据清洗六大实战Hack:性能优化与工程化实践
  • 【技术干货】Kimi K2.7 Code 深度拆解:MCP工具调用超越Claude,开源编程模型新标杆
  • Claude Code 实战:AI 结对编程如何真正提效:从踩坑到可复用方案
  • 深耕广东房企资质服务赛道,广州融景企业管理集团打造房地产开发二级资质代办标杆品牌 - 广东科技观察
  • 2026年液位计厂家推荐排行榜:吉林磁翻板/玻璃管/浮球/雷达/超声波/防爆/就地/水箱/储罐/工业/污水池液位计品牌深度测评 - 品牌发掘
  • AI CAD图纸一秒检索怎么实现
  • 2026中国薪酬咨询机构专业评测:从体系搭建到改革落地的实战指南 - 互联网科技品牌测评
  • 弥赛亚叙事:学术赵高,数学鬼才,牛顿封神的认知病毒
  • 如何彻底解决Windows和Office激活问题:KMS_VL_ALL_AIO智能激活方案完全指南
  • 把二维照片变成能旋转查看的3D模型,做设计搞开发玩创意的都值得试试
  • 2026潍坊劳动律师怎么选?5个实战判断标准不踩雷 - 本地品牌推荐
  • 2026年双螺杆造粒机厂家选购实操指南:行业实情、参数落地与常见问题解答 - 小艾信息发布
  • SD-PPP:3步解锁Photoshop中的AI绘图革命,专业设计师的智能创作引擎
  • MPC8309 eLBC控制器:寄存器配置与内存管理实战指南
  • pnpm 启动前端项目
  • 【Kafka源码解读和使用指南】第67篇:Kafka请求处理机制深度解析——生产请求与获取请求的完整链路
  • 别再纠结RAID了!用一张图帮你选对RAID 0/1/10/01,NAS和服务器都适用
  • 【新版升级】前端组件开发公众号|全赛道IT开发技术 + 产品商业付费社群完整方案
  • 二进制基础:计算机核心数制全解析
  • BilibiliDown:5分钟学会B站视频批量下载,轻松建立个人资源库
  • 深度解析 LLM Agent 架构:从核心组件到生产级系统设计
  • TV Bro:用遥控器征服智能电视上网的智慧之选
  • 2026年污水泵厂家推荐榜:营口潜水/立式卧式/切割防爆不锈钢耐腐蚀污水泵品牌精选及选购指南 - 品牌发掘
  • 2026年金华律师机构推荐榜:离婚、知识产权与民商事争议解决领域深度解析 - 企业推荐官【官方】