10分钟了解Golang泛型

news2024/11/12 14:32:35

泛型是Golang在1.18版本引入的强大工具,能够帮助我们在合适的场合实现简洁、可读、可维护的代码。原文: Go Generics: Everything You Need To Know

alt
导言

可能有人会觉得Go泛型很难,因此想要借鉴其他语言(比如Java、NodeJS)的泛型实践。事实上Go泛型很容易学,本文希望能帮助读者更好的理解Go泛型。

👉注:本文不会将 Go 泛型与其他语言的泛型实现进行比较,但会帮助你理解 Go 泛型元素背后的上下文、结构及其原理。

前置条件

要编写本文中的示例代码,需要:

  • 在计算机上安装 Go 1.18+
  • 对Golang结构、类型、函数和方法有最低限度的了解
概述

在 2020 年之前,Go泛型既是风险也是机遇。

当 Go 泛型在 2009 年左右被首次提出时(当时该编程语言已经公开),该特性是 Go 语言的主要弱点之一(Go 团队调查发现)。

此后,Go 团队在 Go 草案设计中接受了许多泛型实现,并在 Go 1.18 版本中首次引入了泛型。

Go 博客 2020 调查结果
Go 博客 2020 调查结果

Go 2020 调查显示,自 Go 语言诞生以来,Go 社区一直要求引入泛型功能。

Go 开发人员(以及 Go 团队成员)看到这一缺陷阻碍了 Go 语言的发展,同时,如果得到修复,Go将具有更大的灵活性和性能。

什么是程序设计中的泛型?

根据维基百科[1]的解释,泛型编程是一种计算机编程风格,在这种编程风格中,算法的具体类型可以在以后指定。

简单解释一下:泛型是一种可以与多种类型结合使用的类型,泛型函数是一种可以与多种类型结合使用的函数。

☝️ 简单提一下:尽管"泛型"在过去和现在都可以通过 interface{}、反射包或代码生成器在 Go 中实现,但还是要提一下在使用这三种方法之前需要仔细考虑。

为了帮助我们以实用的方式理解和学习 Go 泛型,我们将在本文稍后部分提供示例代码。

但要知道,既然 Go 泛型已经可用,就可以消除模板代码,不必担心向后兼容问题,同时还能编写可重用、类型安全和可维护的代码。

那么......为什么需要 Go 泛型?

简而言之,最多可提高 20% 性能。

根据 Go 博客的描述,Go 泛型为 Go 语言增加了三个主要组件:

  • 函数和类型的类型参数。
  • 将接口类型定义为类型集,包括没有方法的类型。
  • 类型推导,允许在调用函数时省略类型参数。

在 Go 1.18 之前没有这种功能吗?

从技术上讲,早在 Go 泛型发布之前,Go 就有一些处理"泛型"的方法:

  • 使用"泛型"代码生成器生成 Go 软件包,如 https://github.com/cheekybits/genny [2]
  • 使用带有 switch语句和类型转换的接口
  • 使用带有参数验证的反射软件包

然而,与正式的Go泛型相比,这些方法还远远不够,有如下缺点:

  • 使用类型 switch和转换时性能较低
  • 类型安全损耗:接口和反射不是类型安全的,这意味着代码可能会传递任何类型,而这些类型在编译过程中会被忽略,从而在运行时引起 panic
  • Go 项目构建更复杂,编译时间更长
  • 可能需要对调用代码和函数代码进行类型断言
  • 缺乏对自定义派生类型的支持
  • 代码可读性差(使用反射时更明显)

👉注:上述观点并不意味着在 Go 编程中使用接口或反射包不好;它们还有其他用途,应该在合适的场景下应用。

巧合的是,上述几点 ☝️ 使 Go 泛型适合处理目前在 Go 中的泛型实现,因为:

  • 类型安全 ( 运行时不会丢失类型,也不需要类型验证、切换或转换
  • 高性能
  • Go IDE 的支持
  • 向后兼容 ( 使用 Go 1.18+ 重构后,旧版代码仍可运行
  • 对自定义数据类型的高度支持
入门:使用 Go 泛型

在开始重构之前,我们借助一个迷你 Go 程序来了解 Go 泛型使用的一些术语和逻辑。

作为实操案例,我们将首先在不使用 Go 泛型的情况下解决 Leetcode 问题。然后,随着我们对这一主题的了解加深,我们将使用 Go 泛型对其进行重构。

alt

Leetcode 问题

有几家公司在技术面试时都问过这个问题,我们对措辞稍作改动,但逻辑不变。Leetcode 链接为:https://leetcode.com/problems/contains-duplicate[3]

📌问题:给定一个整型(int 或 in32 或 int64)数组 nums,如果任何值在数组中至少出现两次,则返回 true;如果每个元素都不同,则返回 false

现在,我们在不使用 Go 泛型的情况下解决这个问题。

进入开发目录,创建一个新的 Go 项目目录,名称不限。我将其命名为 leetcode1。然后将目录更改为新创建的项目目录。

按照惯例,我们在终端的项目根目录下运行 go mod init github.com/username/leetcode1,为项目创建一个 Go 模块。

❗️ 记住:不要忘记将username替换为你自己的 Github 用户名

接下来,创建 leetcode.go 文件并将下面的代码复制进去:

package main

import "fmt"

type FilterInt map[int]bool
type FilterInt32 map[int32]bool
type FilterInt64 map[int64]bool

func main() {
 data := []int{134458732}     // sample array
 data32 := []int32{134458732// sample array
 data64 := []int64{134458732// sample array
 fmt.Printf("Duplicate found %t\n", FindDuplicateInt(data))
 fmt.Printf("Duplicate found %t\n", FindDuplicateInt32(data32))
 fmt.Printf("Duplicate found %t\n", FindDuplicateInt64(data64))
}

func FindDuplicateInt(data []int) bool {
 inArray := FilterInt{}
 for _, datum := range data {
  if inArray.has(datum) {
   return true
  }
  inArray.add(datum)
 }
 return false
}

func FindDuplicateInt32(data []int32) bool {
 inArray := FilterInt32{}
 for _, datum := range data {
  if inArray.has(datum) {
   return true
  }
  inArray.add(datum)
 }
 return false
}

func FindDuplicateInt64(data []int64) bool {
 inArray := FilterInt64{}
 for _, datum := range data {
  if inArray.has(datum) {
   return true
  }
  inArray.add(datum)
 }
 return false
}

func (r FilterInt) add(datum int) {
 r[datum] = true
}

func (r FilterInt32) add(datum int32) {
 r[datum] = true
}

func (r FilterInt64) add(datum int64) {
 r[datum] = true
}

func (r FilterInt) has(datum int) bool {
 _, ok := r[datum]
 return ok
}

func (r FilterInt32) has(datum int32) bool {
 _, ok := r[datum]
 return ok
}

func (r FilterInt64) has(datum int64) bool {
 _, ok := r[datum]
 return ok
}

再看一下 Leetcode 的问题,程序应该检查输入的数组(可以是 INT、INT32 或 INT64),并找出是否有重复数据,如果有则返回 true,否则返回 false,上面这段代码就是完成这个任务的。

在第 10、11 和 12 行,分别提供了 intint32int64 类型数据的示例数组。

在第 5、6 和 7 行,分别创建了关键字类型为 intint32int64map 类型 FilterIntFilterInt32FilterInt64

所有类型 map 的值都是布尔值,所有类型都有相同的 hasadd 方法。从本质上讲,add 方法将接受 datum 参数,并在 map 中创建值为 true 的键。根据 map 是否包含作为 datum 传入的键,has 方法将返回 truefalse

现在,第 18 行的函数 FindDuplicateInt、第 29 行的函数 FindDuplicateInt32 和第 40 行的函数 FindDuplicateInt64 实现了相同的逻辑,即验证所提供的数据中是否存在重复数据,如果发现重复数据,则返回 true,否则返回 false

看看这些重复代码。

有没有让你感到恶心🤕?

总之,如果我们在终端运行项目根目录下的 go run leetcode.go,就会编译成功并运行。输出结果应该与此类似:

Duplicate found true
Duplicate found true
Duplicate found true

如果我们要查找 float32float64 或字符串的重复内容,该怎么办?

我们可以为每种类型编写一个实现,为不同类型明确编写多个函数,或者使用接口,或者通过包生成"泛型"代码。这就是"泛型"诞生的过程。

通过泛型,我们可以编写泛型函数来替代多个函数,或使用带有类型转换的接口。

接下来我们用泛型来重构代码,但首先需要熟悉一些术语和概念。

泛型基础知识
1.类型参数
类型参数的可视化表示
类型参数的可视化表示

上图描述的是泛型函数 FindDuplicateT 是类型参数,any 是类型参数的约束条件(接下来将讨论约束条件)。

类型参数就像一个抽象的数据层,通常用紧跟函数或类型名称的方括号中的大写字母(多为字母 T)来表示。下面是一些例子:

...
// map type with type parameter T and constraint comparable
type Filter[T comparable] map[T]bool
...

...
// Function FindDuplicate with type parameter T and constraint any
func FindDuplicate[T any](data T) bool {
// find duplicate code
}
...
2.类型推导

泛型函数必须了解其支持的数据类型,才能正常运行。

🎯要点:泛型类型参数的约束条件是在编译时由调用代码确定的代表单一类型的一组类型。

进一步来说,类型参数的约束代表了一系列可允许的类型,但在编译时,类型参数只代表一种类型,因为 Go 是一种强类型的静态检查语言。

❗️提醒:由于 Go 是一种强类型的静态语言,因此会在应用程序编译期间而非运行时检查类型。Go 泛型解决了这个问题。

类型由调用代码类型推导提供,如果泛型类型参数的约束条件不允许使用该类型,代码将无法编译。

符合参数约束的类型
符合参数约束的类型

由于类型是通过约束知道的,因此在大多数情况下,编译器可以在编译时推断出参数类型。

通过类型推导,可以避免从调用代码中为泛型函数或泛型类型实例化进行人工类型推导。

👉注意:如果编译器无法推断类型(即类型推导失败),可以在实例化时或在调用代码中手动指定类型。

下面是 FindDuplicate 泛型函数的一个很好的示例:

FindDuplicate 泛型函数示例
FindDuplicate 泛型函数示例

我们可以忽略调用代码中的 [int],因为编译器会推断出[int],但我更倾向于加入[int]以提高代码的可读性。

3.约束

在引入泛型之前,Go 接口用于定义方法集。然而,随着泛型约束的引入,接口现在既可以定义类型集,也可以定义方法集。

约束是用于指定允许使用的泛型的接口,在上述 FindDuplicate 函数中使用了 any 约束。

❗️Pro 提示:除非必要,否则避免使用 any 接口约束。

在底层实现上,any关键字只是一个空接口,这意味着可以用 interface{} 替换,编译时不会出现任何错误。

Go 泛型中约束的可视化表示
Go 泛型中约束的可视化表示

上述接口约束允许使用 intint16int32int64 类型。这些类型是约束联合体,用管道符 | 分隔类型。

约束在以下几个方面有好处:

  • 通过类型参数定义了一组允许的类型
  • 明确发现泛型函数的误用
  • 提高代码可读性
  • 有助于编写更具可维护性、可重用性和可测试性的代码

☝️ 简单提一下:使用约束时有一个小问题

请看下面的代码:

package main

import "fmt"

type CustomType int16

func main() {
 var value CustomType
 value = 2
 printValue(value)
}

func printValue[T int16](value T) {
 fmt.Printf("Value %d", value)
}

在上面的代码中,第 5 行定义了一个名为 CustomType 的自定义类型,其基础类型为 int16

在第 8 行,声明了一个以 CustomType 为类型的变量,并在第 9 行为其赋值。

然后,在第 10 行调用带有值的 printValue 泛型函数。

...🤔

...🤔

你认为代码可以编译运行吗?

如果我们在终端执行 go run custom-generics.go,就会出现这样的错误。

./custom-type-generics.go:10:12: CustomType does not implement int16 (possibly missing ~ for int16 in constraint int16)

尽管自定义类型 CustomTypeint16 类型,但 printValue 泛型函数的类型参数约束无法识别。

鉴于函数约束不允许使用该类型,这也是合理的。不过,可以修改 printValue 函数,使其接受我们的自定义类型。

现在,更新 printValue 函数如下:

func printValue[T int16 | CustomType](value T) { 
    fmt.Println(value)
}

使用管道操作符,我们将自定义类型 CustomType 添加到 printValue 泛型函数类型参数的约束中,现在有了一个联合约束。

如果我们再次运行该程序,编译和运行都不会出现任何错误。

但是,等等!为什么需要 int16 类型和"int16"类型的约束联合?

alt

我们将在下一节介绍波浪线 ~ 运算符。

4.波浪线(Tilde)运算符和基础类型

幸运的是,Go 1.18 通过波浪线运算符引入了底层类型,波浪线运算符允许约束支持底层类型。

在上一步代码示例中,CustomType 类型的底层类型是 int16。现在,我们使用 ~ 波浪线更新 printValue 泛型函数类型参数的约束,如下所示:

func printValue[T ~int16](value T) { 
    fmt.Println(value)
}

新代码应该是这样的:

package main

import "fmt"

type CustomType int16

func main() {
 var value CustomType
 value = 2
 printValue(value)
}

func printValue[T ~int16](value T) {
 fmt.Printf("Value %d", value)
}

再次运行程序,应该可以成功编译和运行。我们删除了约束联合,并在约束中的 int16 类型前用 ~ 波浪线运算符替换了 CustomType

编译器现在可以理解,CustomType 类型之所以可以使用,仅仅是因为它的底层类型是 int16

💡 简单来说,~ 告诉约束接受任何 int16 类型以及任何以 int16 作为底层类型的类型。

下面是一个泛型约束接口示例,它也允许函数声明:

type Number interface {
  int | float32 | float64
  IsEven() bool 
}

不过,下一步还有更多东西要学。

5.预定义约束

Go 团队非常慷慨的为我们提供了一个常用约束的预定义包,可在 golang.org/x/exp/constraints[4] 找到。

以下是预定义约束包中包含的约束示例:

type Signed interface {
 ~int | ~int8 | ~int16 | ~int32 | ~int64
}

type Unsigned interface {
 ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

type Integer interface {
 Signed | Unsigned
}

type Float interface {
 ~float32 | ~float64
}

type Ordered interface {
 Integer | Float | ~string
}

因此,我们可以更新之前示例中的 printValue 泛型函数,使其接受所有整数,具体方法如下。

func printValue[T Integer](value T) { 
    fmt.Println(value)
}

❗️ 记住:不要忘记导入预定义约束包 golang.org/x/exp/constraints。

重构 Leetcode 示例

现在我们对泛型有了一些了解,接下来重构 FindDuplicate 程序,通过泛型在整数、浮点数和字符串类型的切片及其底层类型中查找是否有重复数据。

具体修改为:

  • 创建允许使用整数、浮点和字符串及其底层类型的接口约束
  • 使用 go get 将约束包下载到项目中,在终端的 Leetcode 根目录中执行如下指令:
go get -u golang.org/x/exp/constraints
  • 添加到项目中后,在主函数上方创建名为 AllowedData 的约束,如下所示:
type AllowedData interface {
   constraints.Ordered
}

constraints.Ordered 是一种约束,允许任何使用支持比较运算符(如 ≤=≥===)的有序类型。

👉注:可以在泛型函数中使用 constraint.Ordered,而无需创建新的接口约束。不过,为了便于学习,我们还是创建了自己的约束 AllowData

  • 接下来,删除类型 map 中的所有 FilterIntX 类型,创建一个名为 Filter 的新类型,如下所示,该类型以 T 为类型参数,以 AllowedData 为约束条件:
type Filter[T AllowedData] map[T]bool

在泛型类型 Filter 前面,声明了 T 类型参数,并指定 map 键只接受类型参数的约束 AllowedData 作为键类型。

  • 现在,删除所有 FindDuplicateIntX 函数。然后使用 Go 泛型创建一个新的 FindDuplicate 函数,代码如下:
func FindDuplicate[T AllowedData](data []T) bool {
   inArray := Filter[T]{}
   for _, datum := range data {
      if inArray.has(datum) {
         return true
      }
      inArray.add(datum)
   }
   return false
}

FindDuplicate 函数是一个泛型函数,添加了类型参数 T,并在函数名后面的方括号中指定了 AllowedData 约束,然后用类型参数 T 定义了切片类型的函数参数,并用类型参数 T 初始化了 inArray

👉注:在函数中声明泛型参数时使用方括号。

  • 接下来,更新 has 以及 add 方法,如下所示。
func (r Filter[T]) add(datum T) {
   r[datum] = true
}

func (r Filter[T]) has(datum T) bool {
   _, ok := r[datum]
   return ok
}

因为我们在定义类型 Filter 时已经声明了约束,因此方法中只包含类型参数。

最后,更新调用 FindDuplicateIntX 的调用代码,使用新的泛型函数 FindDuplicate,最终代码如下:

package main

import (
 "errors"
 "fmt"
 "golang.org/x/exp/constraints"
)

type Filter[T AllowedData] map[T]bool

type AllowedData interface {
 constraints.Ordered
}

func main() {
 data := []int{134458732}     // sample array
 data32 := []int32{134458732// sample array
 data64 := []int64{134458732// sample array
 fmt.Printf("Duplicate found %t\n", FindDuplicate(data))
 fmt.Printf("Duplicate found %t\n", FindDuplicate(data32))
 fmt.Printf("Duplicate found %t\n", FindDuplicate(data64))

}

func (r Filter[T]) add(datum T) {
 r[datum] = true
}

func (r Filter[T]) has(datum T) bool {
 _, ok := r[datum]
 return ok
}

func FindDuplicate[T AllowedData](data []T) bool {
 inArray := Filter[T]{}
 for _, datum := range data {
  if inArray.has(datum) {
   return true
  }
  inArray.add(datum)
 }
 return false
}

现在执行 go run main.go,程序成功编译并运行,预期输出为:

Duplicate found true
Duplicate found true
Duplicate found true

我们成功重构了代码,却没有犯复制粘贴的错误。

6.可比较(comparable)约束

可比较约束与相等运算符(即 == 和≠)相关联。

这是在 Go 1.18 中引入的一个接口,由结构体、指针、接口、管道等类似类型实现。

👉注:Comparable 不用作任何变量的类型。

func Sort[K comparableT Data](values map[K]T) error {
 for k, t := range values {
  // code
 }
 return nil
}
7.约束类型链和类型推导
  • 类型链

允许一个已定义的类型参数与另一个类型参数复合的做法被称为类型链。当在泛型结构或函数中定义辅助类型时,这种方法就派上用场了。

示例:

类型链示例
类型链示例
  • 约束类型推导

前面我们详细介绍了类型推导,但与类型链无关,可以如下调用上图中的函数:

c := Example(2)

由于 ~T 是类型参数 T 与任意约束条件的复合体,因此在调用 Example 函数时可以推断出类型参数 U

👉注:2 是整数,是 T 的底层类型。

8.多类型参数和约束

Go 泛型支持多类型参数,但有一个问题,我们看下面的另一个例子:

package main

import "fmt"

func main() {
 printValues(123"c")
}

func printValues[AB anyC comparable](a, a1 A, b B, c C) {
 fmt.Println(a, a1, b, c)
}

如果编译并成功运行,预期输出结果将是:

1 2 3 c

在函数方括号[]中,我们添加了多个类型参数。类型参数 AB 共享同一个约束条件。在函数括号中,参数 aa1 共享同一个类型参数 any 约束条件。

现在更新主函数,如下所示。

...
func main() {
 printValues(12.13"c")
}
...

发生了什么?

我们将 2 的值从 2 改为 2.1,如你所知,这会将 2 的数据类型从 int 改为 float。当我们再次运行程序时,编译失败:

/main.go:6:14: default type float64 of 2.1 does not match inferred type int for A

等等!我们到底有没有声明 int 类型?

原因就在这里--在编译过程中,编译器会根据函数括号中的类型参数约束进行推断。可以看到,aa1 共享同一个类型参数 A,约束条件是 any(允许所有类型)。

编译器会根据调用代码的变量类型进行推断,并在编译过程中使用函数括号中的类型参数约束来检查类型。

可以看到,aa1 具有相同的类型参数 A,并带有 any 约束。因此,aa1 必须具有相同的类型,因为它们在用于类型推导的函数括号中共享相同的类型参数。

尽管类型参数 AB 共享同一个约束条件,但 b 在函数括号中是独立的。

何时使用(或不使用)泛型

总之,请记住一点--大多数用例并不需要 Go 泛型。不过,知道什么时候需要也很有帮助,因为这样可以大大提高工作效率。

这里有一些指导原则:

何时使用 Go 泛型
  • 替换多个类型执行相同逻辑的重复代码,或者替换处理切片、映射和管道等多个类型的重复代码
  • 在处理容器型数据结构(如链表、树和堆)时
  • 当代码逻辑需要对多种类型进行排序、比较和/或打印时
何时不使用 Go 泛型
  • 当 Go 泛型会让代码变得更复杂时
  • 当指定函数参数类型时
  • 当有可能滥用 Go 泛型时。避免使用 Go 泛型/类型参数,除非确定有使用多种类型的重复逻辑
  • 当不同类型的实现不同时
  • 使用 io.Reader 等读取器时
局限性

目前,匿名函数和闭包不支持类型参数。

Go 泛型的测试

由于 Go 泛型支持编写多种类型的泛型代码,测试用例将与函数支持的类型数量成正比增长。

结论

本文介绍了 Go 中的泛型、与之相关的新术语,以及如何在类型、函数、方法和结构体中使用泛型。

希望能对大家的学习 Go 有所帮助,但请不要滥用 Go 泛型。

收获
  • 如果使用得当,Go 泛型的功能会非常强大;但要谨慎,因为能力越大,责任越大。
  • Go 泛型将提高代码的灵活性和可重用性,同时保持向后兼容,从而为 Go 语言增添价值。
  • 它简单易用,直接明了,学习周期短,练习有助于更好的理解 Go 泛型及其局限性。
  • 过度使用、借用其他语言的泛型实现以及误解会导致 Go 社区出现反模式和复杂性,风险自担。

你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

参考资料
[1]

泛型 - 维基百科: https://en.wikipedia.org/wiki/Generic_programming#:~:text=Generic%20programming%20is%20a%20style,specific%20types%20provided%20as%20parameters.

[2]

genny: https://github.com/cheekybits/genny,

[3]

Leetcode: contains duplicate: https://leetcode.com/problems/contains-duplicate/

[4]

golang.org/x/exp/constraints: https://golang.org/x/exp/constraints

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1666685.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

timerfd加epoll封装定时器

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、用timerfd加epoll封装定时器的优点2、代码实现 1、用timerfd加epoll封装定时器的优点 定时器为什么需要timerfd 在设计定时器时,我们首先想到的就是…

临近空间相关概念

临近空间概念 距地 20KM-100KM 的临近空间位于内外层空间之中,也称为 超高空、近空间、亚轨道等。 特点就是:纵跨 非电离层和电离层、空气稀薄,存在 臭氧、紫外、辐射等特殊环境 存在 重力波、行星波、大气放电等特殊现象。 临近空间高速飞…

YOLOv8+CLIP实现图文特征匹配

本文通过结合YOLOv8s的高效物体检测能力与CLIP的先进图像-文本匹配技术,展示了深度学习在处理和分析复杂多模态数据中的潜力。这种技术的应用不仅限于学术研究,还能广泛应用于工业、商业和日常技术产品中,以实现更智能的人机交互和信息处理。…

[BJDCTF2020]ZJCTF,不过如此 1

涉及&#xff1a;php的伪协议、preg_replace函数的漏洞和正则表达式的运用。 解题步骤 <?phperror_reporting(0); $text $_GET["text"]; $file $_GET["file"]; if(isset($text)&&(file_get_contents($text,r)"I have a dream"))…

璩静霸道言论引发百度风波随笔

从5月9日晚开始有关“百度副总裁璩静已从公司离职”的消息&#xff0c;仅两天时间就几乎布满互联网所有知名自媒体平台&#xff0c;可谓兹事体大&#xff0c;无异于互联网发生了一场八级地震&#xff0c;波及面之广&#xff0c;匪夷所思&#xff01; 百度截图 尽管笔者一直密切…

|Python新手小白中级教程|第二十八章:面向对象编程(类定义语法私有属性类的继承与多态)(4)

文章目录 前言一、类定义语法二、私有方法和私有属性1.私有属性2.私有方法 三、类“继承”1.初识继承2.使用super函数调用父类中构造的东西 四、类“多态”1.多态基础2.子类不同形态3.使用isinstance函数与多态结合判断类型 总结 前言 大家好&#xff0c;我是BoBo仔吖&#xf…

Linux/Ubuntu下使用VS Code配置C/C++项目环境调用OpenCV

OpenCV是大型的Third party 计算机视觉库&#xff0c;在开发中会经常用到&#xff0c;本篇记录一下 在Ubuntu系统上安装和配置OpenCV&#xff0c;并使用C/C调用OpenCV 关于VS Code配置C/C开发环境的部分&#xff0c;见之前的博文 Linux/Ubuntu系统下使用VS Code配置C/C开发环境…

动态规划算法练习——计数问题

题目描述 给定两个整数 a 和 b&#xff0c;求 a 和 b 之间的所有数字中 0∼9 的出现次数。 例如&#xff0c;a1024&#xff0c;b1032&#xff0c;则 a 和 b 之间共有 9 个数如下&#xff1a; 1024 1025 1026 1027 1028 1029 1030 1031 1032 其中 0 出现 10 次&#xff0c;1 出现…

360度全景航拍生成原创,玩命增粉10W ,月入万余元【视频教学 配套设施专用工具】

抖音近期推出了一种全新的玩法&#xff0c;那就是360度全景航拍&#xff0c;这为原创者们带来了新的增粉机会&#xff0c;有望在一个月内收入过万。这个新玩法配有视频教学和专用工具。 项目 地 址 &#xff1a; laoa1.cn/1993.html 抖音的这个新功能&#xff0c;就是360度全…

南京观海微电子----开关电流与输入输出电流的关系

BOOST 结构的工作原理及波形 BOOST 结构简单原理图见图 1&#xff0c;工作时各点的电压电流波形见图 2。 不考虑上电时的情形&#xff0c;仅考虑稳定工作时&#xff0c;情况如下&#xff1a; 当开关管 Q 导通时&#xff08;开关管电压为 0&#xff09;&#xff0c;电感 L 相当…

【密评】 | 商用密码应用安全性评估从业人员考核题库(9/58)

Hill密码是重要古典密码之一&#xff0c;其加密的核心思想的是&#xff08;&#xff09;。 A.线性变换 B.非线性变换 C.循环移位 D.移位 著名的Kerckhoff原则是指&#xff08;&#xff09;。 A.系统的保密性不但依赖于对加密体制或算法的保密&#xff0c;而且依赖于密钥 B.系统…

【计算机网络】数据链路层的功能

数据链路层的基本功能&#xff1a; 封装成帧透明传输差错检测 数据链路层使用的信道主要有两种 点对点信道——PPP协议广播信道——CSMA/CD协议(有线局域网)、CSMA/CA协议(无线局域网) 数据链路层所处的地位 从图中可以看出&#xff0c;数据从主机H1送到主机H2需要在路径中…

C#【进阶】泛型

1、泛型 文章目录 1、泛型1、泛型是什么2、泛型分类3、泛型类和接口4、泛型方法5、泛型的作用思考 泛型方法判断类型 2、泛型约束1、什么是泛型2、各泛型约束3、约束的组合使用4、多个泛型有约束思考1 泛型实现单例模式思考2 ArrayList泛型实现增删查改 1、泛型是什么 泛型实现…

Autoxjs 实践-Spring Boot 集成 WebSocket

概述 最近弄了福袋工具&#xff0c;由于工具运行中&#xff0c;不好查看福袋结果&#xff0c;所以我想将福袋工具运行数据返回到后台&#xff0c;做数据统计、之后工具会越来越多&#xff0c;就弄了个后台&#xff0c;方便管理。 实现效果 WebSocket&#xff1f; websocket是…

【JavaEE初阶系列】——Cookie和Session应用之实现登录页面

目录 &#x1f6a9;本章目标 1.登录页面 2.servlet处理上述的登录请求 3.网站主页(成功登录之后的页面&#xff09; &#x1f6a9;实现过程 &#x1f393;登录页面 &#x1f393;Servlet处理登录请求 &#x1f388;获取请求传来的参数(用户名和密码) &#x1f388;验证…

Electron学习笔记(五)

文章目录 相关笔记笔记说明 七、系统1、系统对话框2、自定义窗口菜单3、系统右键菜单4、快捷键(1)、监听网页按键事件 &#xff08;窗口需处于激活状态&#xff09;(2)、监听全局按键事件 &#xff08;窗口无需处于激活状态&#xff09;(3)、补充&#xff1a;自定义窗口菜单快捷…

Three.js基础练习——渲染一个立方体

1.学习内容参考了 three.js入门教程--零基础也能学会_threejs菜鸟教程-CSDN博客 本章内容包含渲染立方体&#xff0c;并配合ui工具食用~ 2.效果图 import * as THREE from three import * as dat from dat.gui import { OrbitControls } from three/addons/controls/OrbitC…

【网站项目】SpringBoot803房屋租赁管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

【Golang】VSCode进行GO的调试

原来的launch.json {"version": "0.2.0","configurations": [{"name": "Golang","type": "go","request": "launch","program": "${workspaceFolder}","…

Linux技术---部署PXE服务器实现批量安装操作系统

部署PXE服务器实现批量安装操作系统 部署PXE服务器实现批量安装操作系统 部署PXE服务器实现批量安装操作系统1.安装相关服务组件1.1 安装tftp和xinetd1.2 安装DHCP服务1.3 准备 Linux 内核、初始化镜像文件、 PXE 引导程序、安装FTP服务并准备安装源1.4 配置启动菜单文件1.5 验…