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

发布时间:2026/6/14 23:33:10

Go学习第8天:接口 + 泛型 + 错误处理 Go 接口、泛型、错误处理目录一、接口Interface1.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 switch1.6 接口高频踩坑二、泛型Generics2.1 核心概念2.2 基础语法与内置约束2.2.1 通用语法泛型函数泛型结构体2.2.2 三大内置约束2.2.3 示例演示示例1any 约束任意类型通用函数示例2comparable 约束可比较类型示例3 自定义联合约束限定数字类型2.3 常用泛型实战案例案例1通用工具函数交换、去重、判断包含案例2泛型结构体通用栈2.4 泛型高频踩坑三、错误处理3.1 基础error 接口3.1.1 创建普通错误3.1.2 函数返回错误标准写法3.2 自定义错误类型3.3 错误包装与解析Go 1.133.3.1 错误包装3.3.2 错误解析3.4 panic recover处理致命错误标准使用模板3.5 错误处理高频踩坑四、知识点速记表目录接口Interface泛型Generics错误处理Error、panic、recover知识点速记一、接口Interface接口是 Go 实现行为契约、多态、解耦的核心特性它只定义一组方法签名不包含字段与方法实现。Go 采用隐式实现无需关键字声明实现关系只要类型实现接口全部方法就默认适配该接口。1.1 核心特性隐式实现无implements关键字实现接口所有方法即代表实现接口接口变量存储动态类型实际类型动态值零值为nil空接口interface{}可接收任意类型是所有类型的父集接口组合支持嵌套多个接口实现接口继承效果多态同一接口变量可绑定不同实现类型调用同名方法执行不同逻辑。1.2 基础语法与使用1.2.1 接口定义语法格式type接口名interface{方法名1(参数列表)返回值列表 方法名2(参数列表)返回值列表// ... 多个方法}示例图形面积、周长接口packagemainimportmathimportfmt// 定义 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}sc// 结构体实例赋值给接口变量多态fmt.Println(面积,s.Area())fmt.Println(周长,s.Perimeter())}1.2.3 多态演示不同结构体实现同一接口接口变量切换实例执行不同逻辑packagemainimportfmt// 手机接口typePhoneinterface{call()}// 诺基亚结构体typeNokiaPhonestruct{}func(n NokiaPhone)call(){fmt.Println(诺基亚拨打电话)}// 苹果手机结构体typeIPhonestruct{}func(i IPhone)call(){fmt.Println(iPhone拨打电话)}funcmain(){varp Phone pNokiaPhone{}p.call()pIPhone{}p.call()}1.3 空接口 interface{}空接口没有定义任何方法因此 Go 中所有类型都默认实现空接口常用于通用参数、通用容器。示例通用打印函数packagemainimportfmt// 接收任意类型参数funcprintValue(valinterface{}){fmt.Printf(值%v类型%T\n,val,val)}funcmain(){printValue(100)printValue(Go语言)printValue(3.14)printValue([]int{1,2,3})}1.4 接口组合将多个接口嵌套组合成新接口实现接口复用packagemainimportfmttypeReaderinterface{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 ReadWriterFile{}fmt.Println(rw.Read())rw.Write(测试数据)}1.5 类型断言 类型选择接口变量存储的是抽象类型如需还原底层具体类型使用类型断言批量判断类型使用type switch。1.5.1 基础类型断言两种写法直接断言失败触发panic不推荐variinterface{}测试字符串str:i.(string)安全断言搭配ok判断工程首选packagemainimportfmtfuncmain(){variinterface{}123str,ok:i.(string)ifok{fmt.Println(断言成功,str)}else{fmt.Println(类型不匹配)}}1.5.2 类型选择type switch批量匹配接口底层类型适合多类型分支判断packagemainimportfmtfunccheckType(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 接口高频踩坑方法未全部实现结构体只实现接口部分方法编译报错值接收 vs 指针接收接口方法如果是指针接收者只能赋值结构体指针不能赋值结构体实例空接口误用滥用interface{}会丢失类型安全尽量优先具体类型断言不做判断直接断言不使用ok类型不匹配直接程序崩溃nil 接口判断接口变量 nil的条件是动态类型、动态值全部为 nil仅值为 nil 时判断结果为 false。二、泛型Generics泛型是 Go 1.18 正式引入的特性用于编写类型无关、可复用的通用代码避免为不同类型重复编写相同逻辑。核心组成类型参数类型约束。2.1 核心概念类型参数函数/结构体后用[]声明占位类型如[T]类型约束限制类型参数的可用类型如任意类型、可比较类型、自定义类型集合类型推断多数场景编译器可自动推导类型无需显式声明。2.2 基础语法与内置约束2.2.1 通用语法泛型函数func函数名[T 约束](参数列表)返回值{函数体}泛型结构体type结构体名[T 约束]struct{字段}2.2.2 三大内置约束约束含义适用场景any等价interface{}允许任意类型不限制类型的通用工具comparable允许使用/!比较的类型Map 键、元素查找、去重自定义联合约束用 组合多种具体类型2.2.3 示例演示示例1any 约束任意类型通用函数packagemainimportfmt// T 为任意类型funcPrintAny[T any](val T){fmt.Printf(值%v类型%T\n,val)}funcmain(){PrintAny(10)PrintAny(泛型测试)PrintAny(3.14)}示例2comparable 约束可比较类型实现通用切片元素查找packagemainimportfmt// 仅支持可比较类型funcFindIndex[T comparable](slice[]T,target T)int{foridx,v:rangeslice{ifvtarget{returnidx}}return-1}funcmain(){nums:[]int{1,2,3}fmt.Println(FindIndex(nums,2))// 输出 1}示例3 自定义联合约束限定数字类型packagemainimportfmt// 自定义数字约束typeNumberinterface{int|int8|int16|int32|int64|uint|float32|float64}// 通用加法函数funcAdd[T Number](a,b T)T{returnab}funcmain(){fmt.Println(Add(10,20))fmt.Println(Add(1.5,2.5))}2.3 常用泛型实战案例案例1通用工具函数交换、去重、判断包含packagemainimportfmt// 交换两个任意类型变量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]trueresappend(res,v)}}returnres}funcmain(){a,b:1,2a,bSwap(a,b)fmt.Println(a,b)old:[]int{1,1,2,2,3}fmt.Println(Unique(old))}案例2泛型结构体通用栈packagemainimportfmt// 泛型栈typeStack[T any]struct{elements[]T}// 入栈func(s*Stack[T])Push(val T){s.elementsappend(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.elementss.elements[:idx]returnval,true}funcmain(){// 整型栈intStack:Stack[int]{}intStack.Push(10)fmt.Println(intStack.Pop())}2.4 泛型高频踩坑版本限制泛型仅 Go 1.18 及以上版本支持低版本直接编译报错约束误用对非可比较类型切片、Map使用comparable约束编译失败类型推断失效部分复杂场景需手动指定泛型类型函数名[类型](参数)过度使用泛型简单逻辑无需泛型增加代码可读性负担方法约束泛型类型的方法不能再额外添加类型参数。三、错误处理Go 不使用传统try-catch异常机制采用显式错误返回为核心搭配panic致命恐慌和recover恢复恐慌处理严重异常分为三类场景普通业务错误、运行恐慌、自定义错误。3.1 基础error 接口error是 Go 内置接口所有错误类型都必须实现该接口typeerrorinterface{Error()string// 返回错误描述文本}3.1.1 创建普通错误使用标准库errors.New创建基础错误packagemainimporterrorsimportfmtfuncmain(){err:errors.New(参数非法)fmt.Println(err)}3.1.2 函数返回错误标准写法业务函数通常将error作为最后一个返回值nil代表无错误packagemainimporterrorsimportfmt// 除法函数除数为0返回错误funcdivide(a,bint)(int,error){ifb0{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 接口扩展错误信息错误码、详情等packagemainimportfmt// 自定义除法错误typeDivideErrstruct{Dividendint// 被除数Divisorint// 除数}// 实现 error 接口func(e*DivideErr)Error()string{returnfmt.Sprintf(错误%d 不能除以 %d,e.Dividend,e.Divisor)}funcdivide(a,bint)(int,error){ifb0{return0,DivideErr{Dividend:a,Divisor:b}}returna/b,nil}funcmain(){_,err:divide(20,0)fmt.Println(err)}3.3 错误包装与解析Go 1.133.3.1 错误包装使用fmt.Errorf(%w, 原始错误)包装错误保留原始错误链packagemainimporterrorsimportfmtvarErrNotFounderrors.New(数据不存在)funcqueryData(idint)error{// 包装原始错误追加上下文returnfmt.Errorf(查询id%d 失败%w,id,ErrNotFound)}3.3.2 错误解析errors.Is判断错误链中是否包含指定原始错误errors.As将错误转为自定义错误类型。packagemainimporterrorsimportfmtvarErrNotFounderrors.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恢复程序运行。标准使用模板packagemainimportfmtfuncsafeFunc(){// 必须在 defer 匿名函数内调用 recoverdeferfunc(){ifr:recover();r!nil{fmt.Println(捕获恐慌,r)}}()// 触发恐慌panic(数组下标越界)}funcmain(){fmt.Println(程序开始)safeFunc()fmt.Println(程序继续执行)// 未崩溃正常运行}3.5 错误处理高频踩坑忽略错误返回值函数返回error时不判断隐藏线上隐患错误判断顺序错误先使用结果、再判断错误错误时结果为零值recover 使用错误recover必须放在defer匿名函数内嵌套/后置均无法捕获滥用 panic普通业务错误使用errorpanic仅用于致命场景错误包装丢失根错误包装错误必须使用%w否则无法用errors.Is/As解析。四、知识点速记表模块核心要点高频踩坑接口隐式实现、空接口接收任意类型、类型断言/type switch、接口组合方法实现不全、指针/值接收混淆、直接断言不判空泛型类型参数约束、any/comparable/自定义约束、泛型函数/结构体Go1.18以下不支持、约束类型不匹配、过度泛型错误处理error 接口、显式返回错误、%w包装错误、panicrecover忽略error、refer使用位置错误、普通场景滥用panic

相关新闻