当前位置:首页 > 文章列表 > Golang > Go教程 > Go 泛型怎样限制数值运算参数

Go 泛型怎样限制数值运算参数

来源:17golang原创 2026-10-21 21:30:43 0浏览 收藏

Go 泛型要限制数值运算参数,应在类型参数上使用由类型项组成的接口约束,例如把整数和浮点数列入类型集,并用 ~ 接纳对应底层类型的自定义类型。不要用 any 代替数值约束:允许传入任意类型,不代表函数体就能对它们做加减乘除。

泛型在 Go 1.18 引入,这不是一次新的数值规则发布,而是可以用一份函数表达多种具体类型的能力。旧的普通函数不必因此重写;当几份逻辑确实相同、只差数值类型时,再考虑抽出约束。版本背景参见 https://go.dev/doc/go1.18。

Go 官方地址:https://go.dev/

泛型带来的是编译期约束,不是任意类型运算

假设需要一个函数,把初始读数与若干修正量相加。用 T any 声明参数时,类型集中包括无法相加的对象,因此不能直接写 total += adjustment。泛型函数不会等到调用时才任选一条算术规则,函数体必须满足约束所承诺的共同能力。Go 官方介绍对这一设计有说明:https://go.dev/blog/intro-generics。

这里先做一个工程选择:这个函数服务于整数计数和实数读数,不服务于字符串拼接,也不服务于复数。约束名称和具体成员都由这项用途决定,不把名字叫作“数值”就视为覆盖所有数学对象。

先按运算需要拆出整数与实数约束

下面的完整示例声明两个约束:Integer 列出本例接受的底层整数类型,Real 把它与两种浮点类型合并。Integer | ~float32 | ~float64 表达并集;~int64 表达底层类型为 int64 的类型,而单独写 int64 只表示该类型本身。规则见官方规范:https://go.dev/ref/spec#General_interfaces。

本例不纳入 uintptr,这是业务范围的主动选择,不是否认它属于 Go 整数类型。byte 与 rune 是已有基础类型的别名,不需要重复列入。若下一项功能需要取余或位运算,应使用整数约束,而不是把同样的操作塞进包含浮点数的 Real 函数。

Integer与Real约束及其底层类型族的静态分组关系
图1:静态说明图。整数类型族归入Integer,浮点类型族与Integer共同组成Real;连线表示成员或约束关系,不是执行顺序。

用完整例子看参数和返回值怎样保留类型

package main

import "fmt"

// Integer 接纳这些底层整数类型;本例刻意不把 uintptr 当业务数值。
type Integer interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

// Real 在整数之外接纳浮点数,不包含字符串和复数。
type Real interface {
	Integer | ~float32 | ~float64
}

// Accumulate 保留调用者的数值类型,不转成 float64 或 any。
func Accumulate[T Real](seed T, adjustments ...T) T {
	total := seed
	for _, adjustment := range adjustments {
		total += adjustment
	}
	return total
}

type SampleCount int64
type Temperature float64

func main() {
	count := Accumulate(SampleCount(40), SampleCount(7), SampleCount(-2))
	temperature := Accumulate(Temperature(18.5), Temperature(0.25))
	fmt.Printf("%T %v\n", count, count)
	fmt.Printf("%T %v\n", temperature, temperature)

	// 以下写法故意留作注释:字符串不属于 Real。
	// _ = Accumulate("base", "extra")

	// 两个已有类型的变量不能因都属于 Real 就混成同一次 T。
	var a int32 = 1
	var b int64 = 2
	// _ = Accumulate(a, b)
	_ = Accumulate[int64](int64(a), b)
}

这份示例可保存为 main.go。按代码语义,计数结果为 SampleCount 类型的45,温度结果为 Temperature 类型的18.75;这是示例预期,不是本文在本机执行所得的记录。循环没有外部资源需要关闭,也没有自动的溢出错误返回。

seed、各修正量及返回值使用同一个 T。已有类型的 int32 与 int64 变量不能仅因同属约束就自动统一;示例最后显式选择 int64 并转换较窄的值。无类型常量的推断规则另当别论,不应概括成“所有参数写法都必须标注类型”。官方入门教程解释了类型参数和调用推断:https://go.dev/doc/tutorial/generics。

业务接口可以比数学分类更窄

如果只允许指定的计数单位,开放全部 ~int64 可能太宽。可以直接枚举两个具体定义类型,把裸 int64 与其他底层同为 int64 的类型排除在这个接口之外。下面代码是另一段独立声明,不要求与上面的业务类型名称相同。

// 业务只接收两个具体定义类型,不接收裸 int64 或其他定义类型。
type RowCount int64
type ByteCount int64
type Counter interface {
	RowCount | ByteCount
}

func CombineCounter[T Counter](left, right T) T {
	// 两种具体类型都支持加法;返回值仍为所选类型。
	return left + right
}

CombineCounter 的两端仍必须是同一次实例化选定的同一类型,因此不会把 RowCount 和 ByteCount 相加。白名单约束只限制类型,不限制正负、最大计数或单位换算规则;这些仍要在业务函数中明确处理。

RowCount和ByteCount白名单及CombineCounter输入返回类型的静态关系
图2:静态说明图。Counter只列出两个具体计数类型,CombineCounter用同一个T约束输入和返回类型;图示不表示实际运行结果。

从旧函数迁移时不要改变数值语义

抽泛型之前,先问旧实现是否真的只有类型不同。如果其中一份会四舍五入、另一份会拒绝负数,它们并不是直接替换类型名就能合并的函数。保留业务包装层,再复用共同计算部分,通常比开放一个过宽的公共约束更清楚。

需求约束选择仍须单独决定
整数取余、位运算只含整数的类型集零除数、取值范围
整数或浮点加减乘类似本例Real溢出与精度策略
比较大小按用途考虑cmp.Ordered是否也允许字符串
限定计数单位具体业务类型白名单单位与正负规则

cmp.Ordered 包含字符串,因此不是“只允许数值”的同义词;它也不包含复数。需要该约束时,先确认业务是排序比较还是数值计算。官方定义:https://pkg.go.dev/cmp#Ordered。

类型约束不会扩大整数的位宽,也不会自动消除浮点误差。整数除法仍是整数除法,运算仍按实际类型的规则进行;不要把把结果统一转成 float64 当作透明重构。涉及溢出、精度或舍入时,应明确设计而不是依赖约束名。算术规则参见 https://go.dev/ref/spec#Arithmetic_operators。

读者可以怎样试验这些边界

# 将完整示例保存为 main.go 后,在该文件所在目录运行。
go run main.go
# 使用独立包加入正向、拒绝类型及数值极限用例,再运行自己的测试。
go test ./...

可以分别观察:自定义计数类型是否被接纳;字符串示例取消注释后是否被拒绝;把两种已声明的数值变量混用时是否需要显式转换。测试用例还应围绕你采用的具体类型覆盖空修正列表、负修正量、极限值和业务拒绝条件,不把本例的小数字当作完整的数值安全方案。

any加运行时类型判断是否等价?

不等价。本例在函数签名限定允许的类型;运行时分支则把接纳范围、错误处理和结果转换推迟到另一套设计中。需要动态输入时可以另做解析层,但不必因此放弃计算层的静态约束。

可以把Real当普通变量类型吗?

这种包含类型项的非基本接口用于类型约束,不用于声明一个存放各种数值的普通接口变量。需要动态容器时应另外选择表示方式,不能直接把约束当成容器元素类型。

约束越宽,复用性是否越好?

不一定。约束每加入一种类型,就必须考虑它对函数体和业务含义的影响。先为当前算法保留必要成员,等确实需要另一类运算时,再独立定义约束,比先造一个包罗万象的Number更容易维护。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go 泛型怎样写保持顺序的切片去重Go 泛型怎样写保持顺序的切片去重
上一篇
Go 泛型怎样写保持顺序的切片去重
Go debug.FreeOSMemory 怎样判断释放是否真的有帮助
下一篇
Go debug.FreeOSMemory 怎样判断释放是否真的有帮助
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    582次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    620次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    651次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    616次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    433次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码