您的位置: 首页 - 站长

html5经典网站wordpress网站结构

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

html5经典网站,wordpress网站结构,嘉定网站建设网页制作,wordpress5.2.2文章目录 1.什么是泛型#xff1f;2.有 interface{} 为什么还要有泛型#xff1f;3.泛型有哪些特性?3.1 类型参数泛型函数泛型类型 3.2 类型约束3.3 类型集3.4 约束元素任意类型约束元素近似约束元素联合约束元素约束中的可比类型 3.5 类型推断 4.实现原理4.1 类型擦除虚方法… 文章目录 1.什么是泛型2.有 interface{} 为什么还要有泛型3.泛型有哪些特性?3.1 类型参数泛型函数泛型类型 3.2 类型约束3.3 类型集3.4 约束元素任意类型约束元素近似约束元素联合约束元素约束中的可比类型 3.5 类型推断 4.实现原理4.1 类型擦除虚方法表 4.2 字典4.3 单态化模板Template蜡印Stenciling 5.小结参考wenxian 泛型Generics是 Go 语言在较早版本缺失的一个特性直到 Go 1.18 版本中才引入了泛型。泛型提供了一种更灵活、更通用的方式来编写函数和数据结构以处理不同类型的数据而不必针对每种类型编写重复的代码。 1.什么是泛型 假设我们有一个实现两个数相加的功能函数 func Add(a int, b int) int {return a b }通过传入 int 类型的 a 和 b就可以返回 a 和 b 相加后的结果。如果 a 和 b 是 float 类型呢 如果要解决上述问题通常有两种解决方法 1增加一个函数。 func AddFloat(a, b float32) float32 {return a b }2利用反射运行时进行类型判断。 func Add(a interface{}, b interface{}) interface{} {switch a.(type) {case int:return a.(int) b.(int)case float32:return a.(float32) b.(float32)default:return nil} }这两个方法有什么缺点吗 方法1会引入新的函数如果还有其他类型的a,b需要相加的话就需要再增加更多的函数。 方法2使用了反射性能会有影响。 如果不想增加一个新的功能逻辑一模一样的函数同时也不想使用有性能问题的反射的话。就可以使用泛型。 func AddT int | float32 | float64 T {return a b }泛型是一种编程范式允许程序员在强类型程序设计语言中编写模板代码来适应任意类型。 通俗一点的描述泛型是一种相同代码逻辑使用不同类型用的方法而不需要一遍又一遍地复制和粘贴相同的函数只是类型发生了变化。 2.有 interface{} 为什么还要有泛型 虽然 Go 中的空接口 interface{} 允许存储任何类型的值但它是一种动态类型的机制并且在使用时需要进行类型断言。相比之下泛型Generics提供了一种静态类型的通用解决方案使得代码可以在不失去类型安全性的前提下处理多种数据类型。 使用泛型可以带来如下好处 类型安全 泛型允许开发者在编译时指定代码的通用类型为类型参数定义一个类型约束而不需要使用空接口进行运行时类型断言。这提供了更强的类型安全性因为在编译时就能够发现类型错误。 性能优化 在某些情况下使用泛型可以带来性能优势。由于泛型代码是在编译时生成的而不是在运行时进行类型断言因此它可以更好地进行优化。 代码重用和抽象 泛型允许编写通用的、与具体数据类型无关的代码从而提高代码的重用性和抽象性。不再需要为每种数据类型都编写相似的代码避免违反 DRY 原则Don’t Repeat Yourself。 3.泛型有哪些特性? Go 语言泛型实现采用了一种基于类型参数的方式实现更加通用和类型安全的代码而不是通过接口像空接口 interface{}和类型断言来实现动态类型的处理。 以下是 Go 泛型实现的基本特性 3.1 类型参数 Go 的泛型使用类型参数来实现通用性。在定义函数、类型或方法时可以声明一个或多个类型参数。这些类型参数允许你在代码中引用并操作不同数据类型的对象。 泛型函数 泛型函数允许你编写能够处理不同类型的数据的通用函数而不必为每种类型编写重复的代码。例如可以创建一个泛型的排序函数适用于不同类型的切片。 func SwapT any (T, T) {return b, a }在上面的例子中T 是一个类型参数它表示一个占位符可以代表任意类型。在函数体内可以使用 T 来表示参数和返回值的类型。 泛型类型 泛型也可以用于创建通用的类型如泛型切片、泛型映射等。这样可以更灵活地处理不同类型的数据。 type Stack[T any] struct {items []T }func (s *Stack[T]) Push(item T) {s.items append(s.items, item) }func (s *Stack[T]) Pop() T {if len(s.items) 0 {panic(Stack is empty)}item : s.items[len(s.items)-1]s.items s.items[:len(s.items)-1]return item }上述例子中Stack 是一个泛型的堆栈数据结构可以处理任意类型的元素。 3.2 类型约束 为了确保类型安全性Go 引入了类型约束type constraints。 类型约束规定了类型参数必须满足的条件以便进行合法的操作。例如可以使用 interface{} 类型进行类型约束也可以使用特定的接口类型或基本类型。 func PrintT fmt.Stringer {fmt.Println(value.String()) }在上述例子中T 被约束为实现了 fmt.Stringer 接口的类型。 3.3 类型集 类型集Type Set表示一堆类型的集合用来在泛型中约束类型参数的范围。 在 Go1.18 之前Go 官方对接口的定义是接口是一个方法集Method Set。而 Go1.18 开始将接口的定义正式更改为了类型集。 Go 1.18 引入了一种新的接口语法可以嵌入其他数据类型构成类型集合。 type Numeric interface {int | float32 | float64 }这意味着一个接口不仅可以定义一组方法还可以定义一组类型。使用 Numeric 接口作为类型约束意味着值可以是整数或浮点数。 type Node[T Numeric] struct {value T }3.4 约束元素 任意类型约束元素 在类型集合中任意类型均可作为类型参数的约束元素。 // 其中 int 为基础类型 type Integer interface { int } 近似约束元素 实际编码时可能会有很多的类型别名例如 type (orderStatus int32sendStatus int32receiveStatus int32… )Go1.18 中扩展了近似约束元素Approximation Constraint Element这个概念以上述例子来说即基础类型为 int32 的类型。语法表现为 type AnyStatus interface{ ~int32 }如果我们需要对上述自定义的 status 做一个翻译就可以使用以下方式 // 使用定义的类型集 func translateStatusT AnyStatus string {switch status {case 1:return 成功case -1:return 失败default:return 未知} }// 或者不使用类型集 func translateStatusT ~int32 string {switch status {case 1:return 成功case -1:return 失败default:return 未知} }联合约束元素 联合元素写成一系列由竖线 (|) 分隔的约束元素。 这里给所有有符号的整数类型添加一个通用的求和方法。 type Integer interface {~int | ~int8 | ~int16 | ~int32 | ~int64 }func addIntegerT Integer T {return a b }约束中的可比类型 Go1.18 中内置了一个类型约束 comparable约束comparable约束的类型集是所有可比较类型的集合。这允许你对该类型的值进行比较操作。 func indexSliceT comparable int {for i, v : range s {if v x {return i}}return -1 }3.5 类型推断 在许多情况下可以使用类型推断来避免必须显式写出部分或全部类型参数。可以利用函数调用使用的实参类型推断出类型参数。 func PrintT any {fmt.Println(s) }s : []int{1, 2, 3}// 显示指定参数类型 Print[]int // 推断参数类型 Print(s)4.实现原理 要了解 Go 是如何实现泛型的首先需要了解一下实现泛型的最常用方法。 4.1 类型擦除 在编译器中实现泛型的另一种方法是类型擦除Type erasure。 对 Java 来说统一的数据类型就是 Object在编译阶段做完类型检查后就将类型信息通过转换成 Object 进行擦除这样只需要生成一份泛型函数的副本即可。类型擦除保证了泛型函数生成的字节码和非泛型函数的是相同的也符合 Java 对兼容性的要求。不过类型擦除也给 Java 的泛型带来了很多的限制比如 基本类型不支持泛型化 Java 的泛型只能应用于类和对象无法直接用于基本数据类型如 int、char。这导致在使用泛型时需要使用包装类Wrapper Classes如 Integer 代替 int。 不能创建参数化类型的数组 例如 ListString[] array new ArrayListString[10]; 是非法的。这是因为 Java 泛型系统中的数组是具体化的而泛型是擦除的二者不兼容。 无法创建参数化类型的实例 不能直接实例化泛型类型。例如不能使用 new T() 创建泛型类的实例因为在类型擦除后无法确定 T 的具体类型。 虚方法表 对 Java 来说通过类型擦除结合「虚方法表」Virtual Method Table来实现泛型的效果运行时同样的数据类型 Object却能调用原始类型的方法。 泛型函数被修改成只接受统一数据类型 Object 作为参数。在调用实际类型对象的方法时需要找到不通对象的方法。因此需要一个可以查询方法的内存地址的表格虚方法表。 虚方法表不仅可以用来实现泛型还可以用来实现其他类型的多态性比如 C 中的多态。然而推导这些指针和调用虚拟函数要比直接调用函数慢而且使用虚方法表会阻止编译器进行优化。 4.2 字典 编译器在编译泛型函数时只生成了一份函数副本通过新增一个字典参数来供调用方传递类型参数(Type Parameters)这样就可以用一个函数实例支持多种不同的类型参数。这种实现方式称为字典传递Dictionary passing。 Go 实现泛型的方式就是在编译阶段通过将类型信息以字典的方式传递给泛型函数。当然这个字典不仅包含了类型信息还包含了此类型的内存操作函数如 make/len/new 等。 同样的 Swift 也通过字典传递了一种名为 Witness table 的数据结构这种数据结构包含着类型的大小及类型的内存操作函数移动、复制与释放。 Swift 在编译阶段通过在函数入参中以字典的形式把 Witness table 注入达到了以统一的方式处理任何类型而不用对类型进行装箱操作。这样一来Swift就可以实现泛型而且不需要单态化虽然在运行时有动态查找的开销但这种表结构节省了分配内存和缓存不一致性的成本。 4.3 单态化 单态化Monomorphization是一个实现泛型的常用方法编译器为每个被调用的数据类型单独生成一个泛型函数副本以确保类型安全和最佳性能。 func maxT Numeric T {// … }larger : max(3, 5)由于上面显示的 max 函数是用两个整数调用的编译器在对代码进行单态化时将为 int 生成一个 max 的副本。 func maxInt(a, b int) int {// … }larger : maxInt(3, 5)单态化针对不同类型创建单独的函数副本显而易见会增加编译时长但是可以在编译期针对不同类型进行代码优化且运行时没有类型相关的判断逻辑性能较好。 模板Template C 通过模板实现泛型类、方法和函数这导致编译器为每个唯一的类型参数集编译了代码的单独副本。这种方法的一个关键优势是没有运行时性能开销尽管它以增加编译时间和二进制文件大小为代价。 蜡印Stenciling 蜡印其实就是模版也是一种代码生成技术。但 Go 除了使用字典传递实现装箱外还采用了 GC Shape Stenciling 的技术。这种看起来很高级的名词简单来说是为了解决蜡印或模版的问题因为在蜡印的过程中编译器会为每一个实例化的类型参数生成一套独立的代码这种会有一个问题请看下面的例子 type a int type b int虽然 a 和 b 是两个自定义的类型但它们底层都是 int 类型但编译器会为这两个类型而生成两套函数的版本如对 max 泛型函数来说会生成 max_a 与 max_b 两个函数版本。这是一种浪费还会生成庞大的二进制文件。 GC Shape 这种技术就是通过对类型的底层内存布局从内存分配器或垃圾回收器的视角分组对拥有相同的内存布局的类型参数进行蜡印比如指针和接口在内存中总是有相同的布局编译器将为指针和接口的调用生成同一个泛型函数的副本这样就可以避免生成大量重复的代码。 Go 实现泛型的方式混合了字典与蜡印技术对于实例类型的 Shape 相同的情况只生成一份代码对于 Shape 相同的类型使用字典区分类型的不同行为。最终的流程如下 1.开始编译 2.寻找泛型函数调用 3.使用 GCShape 对类型分组 4.为类型生成实例化的函数 5.创建包含类型信息的字典 6.调用实例化的函数并传递入参与类型字典总的来说 Go 在 1.18 实现泛型的方式和 Rust 类似如果感兴趣可以看这篇Rust泛型的文章透过Rust探索系统的本原泛型。 5.小结 对静态类型检查的编程语言实现泛型的方式有很多如 C通过模版实现相比 C 的宏模版显然更强大灵活。Java通过类型擦除的装箱技术结合虚方法表实现虽然类型擦除导致 Java 的泛型实现不如人意但这种代价确保了兼容性。Swift通过字典传入的方式配合 Witness Table 的实现这种巧妙的方式在编译速度与运行时速度之间取得了不错的平衡。Go通过字典传入的方式配合 GC Shape 的蜡印技术实现这种方式在编译速度与运行时速度之间取得了不错的平衡。 虽然看起来方式多样但实际上只有两种思路 编译时只生成一份代码调用方法统一运行时根据传入的实参类型信息执行相应的操作。编译时根据不同类型生成不同的代码。 泛型是 Go 语言中一个重要的新增特性它使得代码更加灵活、清晰减少了重复代码的编写提高了代码的可维护性和性能。 在性能讨论中经常被忽略的是所有这些好处和成本只涉及到函数的调用。通常情况下大部分的执行时间在函数内部。调用方法的开销可能不会成为性能瓶颈所以要考虑先优化函数实现再考虑调用开销。 Go 也在不断的改进自己实现泛型的机制所以此文会存在一些时效性的问题。 参考wenxian An Introduction To Generics Type Parameters Proposal 泛型设计 - | Go 语言设计哲学- 煎鱼 golang拾遗为什么我们需要泛型- apocelipes 简单易懂的Go 泛型使用和实现原理介绍- 万俊峰Kevin 编程语言是如何实现泛型的 - BMPI Go泛型是怎么实现的? - 鸟窝