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

网站名称注册程序淄博网站建设卓迅

网站名称注册程序,淄博网站建设卓迅,如何做网站的维护工作,哪里可以检测丙型肝炎“Don’t Repeat Yourself”是常见的软件工程建议。与其重新创建一个数据结构或函数#xff0c;不如重用它#xff0c;因为对重复的代码保持更改同步非常困难。在像 Go 这样的强类型语言中#xff0c;每个函数参数及每个结构体字段的类型必须在编译时确定。这种严格性使编译…“Don’t Repeat Yourself”是常见的软件工程建议。与其重新创建一个数据结构或函数不如重用它因为对重复的代码保持更改同步非常困难。在像 Go 这样的强类型语言中每个函数参数及每个结构体字段的类型必须在编译时确定。这种严格性使编译器能够帮助验证代码是否正确但有时会希望重用不同类型的函数的逻辑或在重用不同类型的结构体字段。Go 通过类型参数提供了这个功能俗称为泛型。本章中读者将了解为什么需要泛型Go 的泛型实现可以做什么不能做什么以及如何正确使用泛型。 泛型减少重复代码并增强类型安全 Go 是一种静态类型语言这意味着在编译代码时会检查变量和参数的类型。内置类型字典、切片、通道和函数如 ​​len​​、​​cap​​ 或 ​​make​​可接受并返回不同具体类型的值但直到 Go 1.18用户自定义的 Go 类型和函数都无法做到这一点。 如果读者熟悉动态类型语言这类语言中类型在代码运行时才会进行确定你可能不理解为什么要用泛型以及它有什么用。如果将其视为“类型参数”可能会有助于理解。截至目前我们按函数指定的参数来赋值调用。在以下代码中我们指定 ​​Min​​ 接受两个 ​​float64​​ 类型的参数并返回一个​​float64​​ func Min(v1, v2 float64) float64 {if v1 v2 {return v1}return v2 } 类似地我们按声明结构体时所指定的字段类型创建结构体。这里​​Node​​中有一个类型为​​int​​的字段和一个类型为​​*Node​​的字段。 type Node struct {val intnext *Node } 但有些情况下编写函数或结构体时在使用之前不指定参数或字段的具体类型会很有用。 泛型类型的场景很容易理解。在前面我们学习了一个int类型的二叉树。如果需要一个用于字符串或 float64 的二叉树并保证类型安全有几种选择。第一种是为每种类型编写一个自定义树但是这么多重复的代码既冗长又容易出错。 在没有泛型的情况下避免重复代码的唯一方法是修改树实现使用接口来指定如何排序。接口类似这样 type Orderable interface {// Order returns:// a value 0 when the Orderable is less than the supplied value,// a value 0 when the Orderable is greater than the supplied value,// and 0 when the two values are equal.Order(any) int } 有了​​Orderable​​我们就可以修改​​Tree​​的实现来提供支持 type Tree struct {val Orderableleft, right *Tree }func (t *Tree) Insert(val Orderable) *Tree {if t nil {return Tree{val: val}}switch comp : val.Order(t.val); {case comp 0:t.left t.left.Insert(val)case comp 0:t.right t.right.Insert(val)}return t } 对于​​OrderableInt​​类型可以插入​​int​​值 type OrderableInt intfunc (oi OrderableInt) Order(val any) int {return int(oi - val.(OrderableInt)) }func main() {var it *Treeit it.Insert(OrderableInt(5))it it.Insert(OrderableInt(3))// etc... } 这段代码虽可正常运行但无法让编译器验证插入数据结构的相同值。若有​​OrderableString​​类型 type OrderableString stringfunc (os OrderableString) Order(val any) int {return strings.Compare(string(os), val.(string)) } 以下代码可正常编译 var it *Tree it it.Insert(OrderableInt(5)) it it.Insert(OrderableString(nope)) ​​Order​​函数使用​​any​​表示传入的值。这会使Go的一个主要优势产生短路即编译时类型安全检查。在编译代码深度对已包含​​OrderableInt​​的​​Tree​​插入​​OrderableString​​时编译器接受了该代码。但在运行时程序会panic panic: interface conversion: interface {} is main.OrderableInt, not string 可以测试​​第8章的GitHub代码库​​sample_code/non_generic_tree目录中的这段代码。 现在由于Go引入了泛型可以一次性为多个类型实现数据结构并在编译时检测出不兼容的数据。很快就会讲到如何正确使用。 虽然没有泛型的数据结构不太方便但真正的局限在于函数编写。Go标准库中的多个实现是因为最初未包含泛型而做出的决策。例如Go中没有编写多个处理不同数值类型的函数而是使用带有足够大范围以精确表示几乎每种其他数值类型的​​float64​​参数来实现诸如​​math.Max​​、​​math.Min​​和​​math.Mod​​这样的函数。 不影响具有大于253 - 1 或小于-253 - 1的​​int​​、​​int64​​或​​uint​​。 还有有一功能没有泛型就无法实现。不能创建一个由接口指定变量的新实例也不能指定两个具有相同接口类型的参数也具有同样的实体类型。没有泛型就无法不借助反向就编写一个处理所有类型切片的函数而那又会牺牲一些性能并带来编译时类型安全问题​​sort.Slice​​就是如此。也就是说在Go引入泛型之前处理切片的函数(如map, reduce, and filter) 需要针对不同类型切片进行反复实现。虽然简单的算法很容易拷贝但很多也许不是大多数软件工程师会觉得因为编译器无法智能地自动实现出现重复代码很让人抓狂。 在Go语言中引入泛型 自Go首发以来一直有呼声要求将泛型添加到该语言中。Go的开发负责人Russ Cox于2009年写了一篇​​博客文章​​解释了为什么最初未包含泛型。Go着重快速编译器、可读性代码和良好的执行时间而他们所了解的泛型实现都无法同时满足这三个条件。经过十年的研究Go团队已经找到了一种可行的方法详见​​类型参数提案​​。 可以通过栈来了解Go中的泛型是如何运作的。如果读者没有计算机科学背景栈是一种数据类型其中的值以后进先出LIFO的顺序添加和删除。这就像一堆等待清洗的盘子一开始的放在底部只有先处理后添加的那些盘子才能够拿到它们。我们来看如何使用泛型创建栈 type Stack[T any] struct {vals []T }func (s *Stack[T]) Push(val T) {s.vals append(s.vals, val) }func (s *Stack[T]) Pop() (T, bool) {if len(s.vals) 0 {var zero Treturn zero, false}top : s.vals[len(s.vals)-1]s.vals s.vals[:len(s.vals)-1]return top, true } 有几个需要注意的地方。首先类型声明后使用​​[T any]​​。类型参数放在了方括号内。书写方式与变量参数相同首先是类型名称然后是类型约束。可为类型参数选择任意名称但通常习惯使用大写字母。Go使用接口来指定可以使用哪些类型。如可使用任何类型使用全局标识符​​any​​来指定。在​​Stack​​声明内部我们声明​​vals​​的类型为​​[]T​​。 接下来看一下方法声明。就像我们在​​vals​​声明中使用了​​T​​此处也是一样的。在接收器部分我们还使用​​Stack[T]​​换​​Stack​​来引用类型。 最后泛型使零值处理产生了变化。在​​Pop​​中我们不能只返回​​nil​​因为对于值类型如​​int​​这不是一个有效值。获取泛型的零值的最简单方法是使用​​var​​声明一个变量并返回因为根据定义如果未赋值​​var​​会将其变量初始化为零值。 使用泛型类型与使用非泛型类型非常相似 func main() {var intStack Stack[int]intStack.Push(10)intStack.Push(20)intStack.Push(30)v, ok : intStack.Pop()fmt.Println(v, ok) } 唯一的不同是在声明变量时对​​Stack​​指定了希望包含的类型本例中为​​int​​。如尝试将字符串压入栈编译器会捕获到。添加如下行 intStack.Push(nope) 会得到编译错误 cannot use nope (untyped string constant) as int valuein argument to intStack.Push 可在​​The Go Playground​​中测试我们的泛型栈可查看​​第8章的GitHub代码库​​sample_code/stack目录中的代码。 下面对该栈添加一个是否包含某值的方法 func (s Stack[T]) Contains(val T) bool {for _, v : range s.vals {if v val {return true}}return false } 可惜无法编译。报错如下 invalid operation: v val (type parameter T is not comparable with ) 就像​​interface{}​​没表明什么​​any​​也一样。只能存储​​any​​类型的值和提取。需要对其它类型才能使用​​​​。因几乎所有Go类型都可以使用​​​​和​​!​​进行比较在全局代码块中新定义了一个名为​​comparable​​的接口。可​​comparable​​修改的​​Stack​​定义 type Stack[T comparable] struct {vals []T } 然后就可以使用这个新方法了 func main() {var s Stack[int]s.Push(10)s.Push(20)s.Push(30)fmt.Println(s.Contains(10))fmt.Println(s.Contains(5)) } 输出的结果为 true false 可测试​​第8章的GitHub代码库​​sample_code/comparable_stack目录中我们所​​更新的栈​​。 稍后我们会学习如何创建泛型二叉树。在此之前先讲解一些概念泛型函数、接口如何使用泛型以及类型名。 泛型函数抽象算法 我们也可以编写函数。前面提到没有泛型会很难编写适用所有类型的映射、归约reduce和过滤实现。泛型使其变得简单。以下是类型参数提案中的一些实现 // Map turns a []T1 to a []T2 using a mapping function. // This function has two type parameters, T1 and T2. // This works with slices of any type. func Map[T1, T2 any](s []T1, f func(T1) T2) []T2 {r : make([]T2, len(s))for i, v : range s {r[i] f(v)}return r }// Reduce reduces a []T1 to a single value using a reduction function. func Reduce[T1, T2 any](s []T1, initializer T2, f func(T2, T1) T2) T2 {r : initializerfor _, v : range s {r f(r, v)}return r }// Filter filters values from a slice using a filter function. // It returns a new slice with only the elements of s // for which f returned true. func Filter[T any](s []T, f func(T) bool) []T {var r []Tfor _, v : range s {if f(v) {r append(r, v)}}return r } 函数将类型参数放在函数名和变量参数之间。​​Map​​和​​Reduce​​有两个类型参数都是​​any​​类型而​​Filter​​为一个参数。运行如下代码时 words : []string{One, Potato, Two, Potato} filtered : Filter(words, func(s string) bool {return s ! Potato }) fmt.Println(filtered) lengths : Map(filtered, func(s string) int {return len(s) }) fmt.Println(lengths) sum : Reduce(lengths, 0, func(acc int, val int) int {return acc val }) fmt.Println(sum) 会得到如下输出 [One Two] [3 3] 6 读者可自行使用​​Go Playground​​或​​第8章的GitHub代码库​​sample_code/map_filter_reduce目录中的代码进行测试。 泛型和接口 可以使用任意接口来进行类型约束不只是有​​any​​和​​comparable​​。例如希望创建一个存储任意实现了​​fmt.Stringer​​的同类型两个值的类型。泛型使得我们可以在编译时进行这一强制 type Pair[T fmt.Stringer] struct {Val1 TVal2 T } 也可以创建带类型参数的接口。例如下面有一个包含指定类型值比较方法并返回​​float64​​的接口。还内嵌了​​fmt.Stringer​​ type Differ[T any] interface {fmt.StringerDiff(T) float64 } 我们会使用这两个类型创建对比函数。该函数接口两个包含​​Differ​​类型字段的​​Pair​​实例返回带更接近值的​​Pair​​ func FindCloser[T Differ[T]](pair1, pair2 Pair[T]) Pair[T] {d1 : pair1.Val1.Diff(pair1.Val2)d2 : pair2.Val1.Diff(pair2.Val2)if d1 d2 {return pair1}return pair2 } ​​FindCloser​​接收包含实现了​​Differ​​接口的字段的​​Pair​​实例。​​Pair​​要求两个字段的类型相同并且该类型实现​​fmt.Stringer​​接口该函数要求更高。如果​​Pair​​实例中的字段未实现​​Differ​​编译器会不允许使用带​​FindCloser​​的​​Pair​​实例。 下面定义几个实现​​Differ​​接口的类型 type Point2D struct {X, Y int }func (p2 Point2D) String() string {return fmt.Sprintf({%d,%d}, p2.X, p2.Y) }func (p2 Point2D) Diff(from Point2D) float64 {x : p2.X - from.Xy : p2.Y - from.Yreturn math.Sqrt(float64(x*x) float64(y*y)) }type Point3D struct {X, Y, Z int }func (p3 Point3D) String() string {return fmt.Sprintf({%d,%d,%d}, p3.X, p3.Y, p3.Z) }func (p3 Point3D) Diff(from Point3D) float64 {x : p3.X - from.Xy : p3.Y - from.Yz : p3.Z - from.Zreturn math.Sqrt(float64(x*x) float64(y*y) float64(z*z)) } 该代码的使用如下 func main() {pair2Da : Pair[Point2D]{Point2D{1, 1}, Point2D{5, 5}}pair2Db : Pair[Point2D]{Point2D{10, 10}, Point2D{15, 5}}closer : FindCloser(pair2Da, pair2Db)fmt.Println(closer)pair3Da : Pair[Point3D]{Point3D{1, 1, 10}, Point3D{5, 5, 0}}pair3Db : Pair[Point3D]{Point3D{10, 10, 10}, Point3D{11, 5, 0}}closer2 : FindCloser(pair3Da, pair3Db)fmt.Println(closer2) } 可在​​The Go Playground​​​中运行或查看​​第8章的GitHub代码库​​sample_code/generic_interface目录中的代码。 使用类型名指定运算符 泛型还需要体现另外一点运算符。​​divAndRemainder​​函数可正常操作​​int​​而应用于其它类型则需要进行类型转换并且​​uint​​可存储的值远大于​​int​​。如果要为​​divAndRemainder​​编写一个泛型版本需要一种方式来指定可使用​​/​​和​​%​​。Go泛型通过类型元素来实现由接口内的一种或多种类型名指定 type Integer interface {int | int8 | int16 | int32 | int64 |uint | uint8 | uint16 | uint32 | uint64 | uintptr } 在​​使用内嵌实现组合​​一节中我们学习过嵌套接口表明所包含的接口的方法接包括内嵌接口的方法。类型元素指定类型参数可赋哪些类型以及支持哪些运算符。通过​​|​​来分隔具体类型。允许的运算符为对所有列出类型有效的那些。模运算符(​​%​​) 仅对整型有效所有我们列举了所有的整型。可以不加​​byte​​和​​rune​​因为它们分别是​​uint8​​和​​int32​​的类型别名。 注意带类型元素的接口仅对类型约束有效。将它们用作变量、字段、返回值或参数类似会报编译时错误。 现在可以编写​​divAndRemainder​​的泛型版本通过​​uint​​内置类型使用该函数或其它​​Integer​​中所列的类型 func divAndRemainder[T Integer](num, denom T) (T, T, error) {if denom 0 {return 0, 0, errors.New(cannot divide by zero)}return num / denom, num % denom, nil }func main() {var a uint 18_446_744_073_709_551_615var b uint 9_223_372_036_854_775_808fmt.Println(divAndRemainder(a, b)) } 默认类型名完全匹配。如对​​divAndRemainder​​使用底层为​​Integer​​所列类型的自定义类型会出现错误。以下代码 type MyInt int var myA MyInt 10 var myB MyInt 20 fmt.Println(divAndRemainder(myA, myB)) 会报如下错误 MyInt does not satisfy Integer (possibly missing ~ for int in Integer) 错误文本提示了如何解决这一问题。如果希望类型名对那些以这些类型为底层类型的类型也有效在类型名前加​​~​​。那么我们的​​Integer​​定义就变成了 type Integer interface {~int | ~int8 | ~int16 | ~int32 | ~int64 |~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } 可在​​The Go Playground​​ 或​​第8章的GitHub代码库​​的sample_code/type_terms目录下查看​​divAndRemainder​​的泛型版本。 类型名让我们可以定义用于编写泛型比较函数的类型 type Ordered interface {~int | ~int8 | ~int16 | ~int32 | ~int64 |~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |~float32 | ~float64 |~string } ​​Ordered​​接口列举了所有支持​​​​, ​​!​​, ​​​​, ​​​​, ​​​​和​​​​运算符的类型。因为指定一种可进行排序变量的方式非常有用所以在Go 1.21的​​cmp​​中定义了这个​​Ordered​​接口。该包还定义了两个比较函数。​​Compare​​函数根据第一个参数是否小于、等于或大于第二个参数返回-1, 0或1而​​Less​​函数在第一个参数小于第二个参数时返回​​true​​。 将同时具有类型元素和方法元素的接口用作类型参数完全合法。例如可以指定一种类型的底层类型必须为​​int​​并且具备​​String() string​​方法 type PrintableInt interface {~intString() string } 注意Go会允许我们声明其实无法实例化的类型参数接口。如果在​​PrintableInt​​中把​​~int​​换成了​​int​​就不会有满足的有效类型因为​​int​​不带方法。这样不好但编译器会进行补救。如果声明了带这种类型参数的类型或函数企图使用时会导致编译错误。假设声明了这些类型 type ImpossiblePrintableInt interface {intString() string }type ImpossibleStruct[T ImpossiblePrintableInt] struct {val T }type MyInt intfunc (mi MyInt) String() string {return fmt.Sprint(mi) } 虽然无法实例化​​ImpossibleStruct​​编译器对这些声明不会报错。不过在使用​​ImpossibleStruct​​时编译器就会报错了。以下代码 s : ImpossibleStruct[int]{10} s2 : ImpossibleStruct[MyInt]{10} 会报编译时错误 int does not implement ImpossiblePrintableInt (missing String method) MyInt does not implement ImpossiblePrintableInt (possibly missing ~ for int in constraint ImpossiblePrintableInt) 可在​​The Go Playground​​ 或​​第8章的GitHub代码库​​的sample_code/impossible目录下测试这段代码。 除了内置的原生类型外类型名也可以是切片、字典、数组、通道、结构体甚至函数。它最大的用处是用于保证类型参数具有指定底层类型或一到多个方法。 本文来自正在规划的​​Go语言云原生自我提升系列​​欢迎关注后续文章。
http://www.pierceye.com/news/739003/

相关文章:

  • 下载一个网站学院网站建设的作用
  • 济南专业网站优化花西子的网络营销策略
  • 武城网站建设费用网页设计试题及答案
  • 郑州外贸网站建设公司搜索引擎排名的三大指标
  • 温州专业微网站制作电台 主题 wordpress
  • wordpress做网站过程阳江网上车管所
  • 网站抓取qq上海自贸区注册公司流程
  • 深圳网站设计推荐刻烟台制作网站有哪些
  • 网站注册系统源码卢松松博客源码 wordpress博客模板
  • 网站开发进阶实训报告廊坊安次区网站建设公司
  • jquery插件网站推荐打开网站自动跳转代码
  • 佛山顺德容桂网站制作写作平台
  • 网站源码下载pdf文件品质好房
  • 山网站建设长沙网站开发湖南微联讯点不错
  • 网站建设的方案模板邢台123今天的招聘信息
  • 一个网站做app网站如何做收款二维码
  • 济南seo网站优化网站开发源代码 百度文库
  • 东西湖区建设局网站制作网站需要钱吗
  • 自己买服务器能在wordpress建网站欧美色影网站
  • 网站支付页面设计金华企业网站建设公司
  • wordpress评论模块临沂seo网站管理
  • 四川法制建设网站产品推广步骤
  • 服务器 网站建设比较容易做流量的网站
  • 网站建设基础实训报告天津滨海新区地图全图
  • 兰西网站建设深圳58同城招聘网
  • 兰州网站建设程序烟台赶集网网站建设
  • 自己建立网站后怎么做淘客wordpress需要npv
  • 简单网站建设推荐wordpress主题ashley
  • 单页网站开发实例下载电商营销渠道有哪些
  • 沈阳科技网站首页东营市做网站