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

Go 泛型之泛型约束

Go 泛型之泛型约束

文章目录

  • Go 泛型之泛型约束
    • 一、引入
    • 二、最宽松的约束:any
    • 三、支持比较操作的内置约束:comparable
    • 四、自定义约束
    • 五、类型集合(type set)
    • 六、简化版的约束形式
    • 七、约束的类型推断
    • 八、小结

一、引入

虽然泛型是开发人员表达“通用代码”的一种重要方式,但这并不意味着所有泛型代码对所有类型都适用。更多的时候,我们需要对泛型函数的类型参数以及泛型函数中的实现代码设置限制。泛型函数调用者只能传递满足限制条件的类型实参,泛型函数内部也只能以类型参数允许的方式使用这些类型实参值。在 Go 泛型语法中,我们使用类型参数约束(type parameter constraint)(以下简称约束)来表达这种限制条件。

约束之于类型参数就好比函数参数列表中的类型之于参数:

函数普通参数在函数实现代码中可以表现出来的性质与可以参与的运算由参数类型限制,而泛型函数的类型参数就由约束(constraint)来限制。

2018 年 8 月由伊恩·泰勒和罗伯特·格瑞史莫主写的 Go 泛型第一版设计方案中,Go 引入了 contract 关键字来定义泛型类型参数的约束。但经过约两年的 Go 社区公示和讨论,在 2020 年 6 月末发布的泛型新设计方案中,Go 团队又放弃了新引入的 contract 关键字,转而采用已有的 interface 类型来替代 contract 定义约束。这一改变得到了 Go 社区的大力支持。使用 interface 类型作为约束的定义方法能够最大程度地复用已有语法,并抑制语言引入泛型后的复杂度。

但原有的 interface 语法尚不能满足定义约束的要求。所以,在 Go 泛型版本中,interface 语法也得到了一些扩展,也正是这些扩展给那些刚刚入门 Go 泛型的 Go 开发者带来了一丝困惑,这也是约束被认为是 Go 泛型的一个难点的原因。

下面我们来看一下 Go 类型参数的约束, Go 原生内置的约束、如何定义自己的约束、新引入的类型集合概念等。我们先来看一下 Go 语言的内置约束,从 Go 泛型中最宽松的约束:any 开始。

二、最宽松的约束:any

无论是泛型函数还是泛型类型,其所有类型参数声明中都必须显式包含约束,即便你允许类型形参接受所有类型作为类型实参传入也是一样。那么我们如何表达“所有类型”这种约束呢?我们可以使用空接口类型(interface{})来作为类型参数的约束:

func Print[T interface{}](sl []T) {// ... ...
}func doSomething[T1 interface{}, T2 interface{}, T3 interface{}](t1 T1, t2 T2, t3 T3) {// ... ...
}

不过使用 interface{} 作为约束至少有以下几点“不足”:

  • 如果存在多个这类约束时,泛型函数声明部分会显得很冗长,比如上面示例中的 doSomething 的声明部分;
  • interface{} 包含 {} 这样的符号,会让本已经很复杂的类型参数声明部分显得更加复杂;
  • comparableSortableordered 这样的约束命名相比,interface{} 作为约束的表意不那么直接。

为此,Go 团队在 Go 1.18 泛型落地的同时又引入了一个预定义标识符:anyany 本质上是 interface{} 的一个类型别名:

// $GOROOT/src/builtin/buildin.go
// any is an alias for interface{} and is equivalent to interface{} in all ways.
type any = interface{}

这样,我们在泛型类型参数声明中就可以使用 any 替代 interface{},而上述 interface{} 作为类型参数约束的几点“不足”也随之被消除掉了。

any 约束的类型参数意味着可以接受所有类型作为类型实参。在函数体内,使用 any 约束的形参 T 可以用来做如下操作:

  • 声明变量
  • 同类型赋值
  • 将变量传给其他函数或从函数返回
  • 取变量地址
  • 转换或赋值给 interface{} 类型变量
  • 用在类型断言或 type switch 中
  • 作为复合类型中的元素类型
  • 传递给预定义的函数,比如 new

下面是 any 约束的类型参数执行这些操作的一个示例:

// any.go
func doSomething[T1, T2 any](t1 T1, t2 T2) T1 {var a T1        // 声明变量var b T2a, b = t1, t2   // 同类型赋值_ = bf := func(t T1) {}f(a)            // 传给其他函数p := &a         // 取变量地址_ = pvar i interface{} = a  // 转换或赋值给interface{}类型变量_ = ic := new(T1)    // 传递给预定义函数_ = cf(a)            // 将变量传给其他函数sl := make([]T1, 0, 10) // 作为复合类型中的元素类型_ = slj, ok := i.(T1) // 用在类型断言中_ = ok_ = jswitch i.(type) { // 作为type switch中的case类型case T1:case T2:}return a        // 从函数返回
}

但如果对 any 约束的类型参数进行了非上述允许的操作,比如相等性或不等性比较,那么 Go 编译器就会报错:

// any.gofunc doSomething[T1, T2 any](t1 T1, t2 T2) T1 {var a T1 if a == t1 { // 编译器报错:invalid operation: a == t1 (incomparable types in type set)}if a != t1 { // 编译器报错:invalid operation: a != t1 (incomparable types in type set)}... ...
}

所以说,如果我们想在泛型函数体内部对类型参数声明的变量实施相等性(==)或不等性比较(!=)操作,我们就需要更换约束,这就引出了 Go 内置的另外一个预定义约束:comparable

三、支持比较操作的内置约束:comparable

Go 泛型提供了预定义的约束:comparable,其定义如下:

// $GOROOT/src/builtin/buildin.go// comparable is an interface that is implemented by all comparable types
// (booleans, numbers, strings, pointers, channels, arrays of comparable types,
// structs whose fields are all comparable types).
// The comparable interface may only be used as a type parameter constraint,
// not as the type of a variable.
type comparable interface{ comparable }

不过从上述这行源码我们仍然无法直观看到 comparable 的实现细节,Go 编译器会在编译期间判断某个类型是否实现了 comparable 接口。

根据其注释说明,所有可比较的类型都实现了 comparable 这个接口,包括:布尔类型、数值类型、字符串类型、指针类型、channel 类型、元素类型实现了 comparable 的数组和成员类型均实现了 comparable 接口的结构体类型。下面的例子可以让我们直观地看到这一点:

// comparable.gotype foo struct {a ints string
}type bar struct {a  intsl []string
}func doSomething[T comparable](t T) T {var a Tif a == t {}if a != t {}return a
}   func main() {doSomething(true)doSomething(3)doSomething(3.14)doSomething(3 + 4i)doSomething("hello")var p *intdoSomething(p)doSomething(make(chan int))doSomething([3]int{1, 2, 3})doSomething(foo{})doSomething(bar{}) //  bar does not implement comparable
}

我们看到,最后一行 bar 结构体类型因为内含不支持比较的切片类型,被 Go 编译器认为未实现 comparable 接口,但除此之外的其他类型作为类型实参都满足 comparable 约束的要求。

此外还要注意,comparable 虽然也是一个 interface,但它不能像普通 interface 类型那样来用,比如下面代码会导致编译器报错:

var i comparable = 5 // 编译器错误:cannot use type comparable outside a type constraint: interface is (or embeds) comparable

从编译器的错误提示,我们看到:comparable 只能用作修饰类型参数的约束。

四、自定义约束

我们知道,Go 泛型最终决定使用 interface 语法来定义约束。这样一来,凡是接口类型均可作为类型参数的约束。下面是一个使用普通接口类型作为类型参数约束的示例:

// stringify.gofunc Stringify[T fmt.Stringer](s []T) (ret []string) {for _, v := range s {ret = append(ret, v.String())}return ret
}type MyString stringfunc (s MyString) String() string {return string(s)
}func main() {sl := Stringify([]MyString{"I", "love", "golang"})fmt.Println(sl) // 输出:[I love golang]
}

这个例子中,我们使用的是 fmt.Stringer 接口作为约束。一方面,这要求类型参数 T 的实参必须实现 fmt.Stringer 接口的所有方法;另一方面,泛型函数 Stringify 的实现代码中,声明的 T 类型实例(比如 v)也仅被允许调用 fmt.StringerString 方法。

这类基于行为(方法集合)定义的约束对于习惯了 Go 接口类型的开发者来说,是相对好理解的。定义和使用起来,与下面这样的以接口类型作为形参的普通 Go 函数相比,区别似乎不大:

func Stringify(s []fmt.Stringer) (ret []string) {for _, v := range s {ret = append(ret, v.String())}return ret
}

但现在我想扩展一下上面 stringify.go 这个示例,将 Stringify 的语义改为只处理非零值的元素:

// stringify_without_zero.gofunc StringifyWithoutZero[T fmt.Stringer](s []T) (ret []string) {var zero Tfor _, v := range s {if v == zero { // 编译器报错:invalid operation: v == zero (incomparable types in type set)continue}ret = append(ret, v.String())}return ret
}

我们看到,针对 v 的相等性判断导致了编译器报错,我们需要为类型参数赋予更多的能力,比如支持相等性和不等性比较。这让我们想起了我们刚刚学过的 Go 内置约束 comparable,实现 comparable 的类型,便可以支持相等性和不等性判断操作了。

我们知道,comparable 虽然不能像普通接口类型那样声明变量,但它却可以作为类型嵌入到其他接口类型中,下面我们就扩展一下上面示例:

// stringify_new_without_zero.go
type Stringer interface {comparableString() string
}func StringifyWithoutZero[T Stringer](s []T) (ret []string) {var zero Tfor _, v := range s {if v == zero {continue}ret = append(ret, v.String())}return ret
}type MyString stringfunc (s MyString) String() string {return string(s)
}func main() {sl := StringifyWithoutZero([]MyString{"I", "", "love", "", "golang"}) // 输出:[I love golang]fmt.Println(sl)
}

在这个示例里,我们自定义了一个 Stringer 接口类型作为约束。在该类型中,我们不仅定义了 String 方法,还嵌入了 comparable,这样在泛型函数中,我们用 Stringer 约束的类型参数就具备了进行相等性和不等性比较的能力了!

但我们的示例演进还没有完,现在相等性和不等性比较已经不能满足我们需求了,我们还要为之加上对排序行为的支持,并基于排序能力实现下面的 StringifyLessThan 泛型函数:

func StringifyLessThan[T Stringer](s []T, max T) (ret []string) {var zero Tfor _, v := range s {if v == zero || v >= max {continue}ret = append(ret, v.String())}return ret
}

但现在当我们编译上面 StringifyLessThan 函数时,我们会得到编译器的报错信息 invalid operation: v >= max (type parameter T is not comparable with >=)。Go 编译器认为 Stringer 约束的类型参数 T 不具备排序比较能力。

如果连排序比较性都无法支持,这将大大限制我们泛型函数的表达能力。但是 Go 又不支持运算符重载(operator overloading),不允许我们定义出下面这样的接口类型作为类型参数的约束:

type Stringer[T any] interface {String() stringcomparable>(t T) bool>=(t T) bool<(t T) bool<=(t T) bool
}

那我们又该如何做呢?别担心,Go 核心团队显然也想到了这一点,于是对 Go 接口类型声明语法做了扩展,支持在接口类型中放入类型元素(type element)信息,比如下面的 ordered 接口类型:

type ordered interface {~int | ~int8 | ~int16 | ~int32 | ~int64 |~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |~float32 | ~float64 | ~string
}

在这个接口类型的声明中,我们没有看到任何方法,取而代之的是一组由竖线 “|” 分隔的、带着小尾巴 “~” 的类型列表。这个列表表示的是,以它们为底层类型(underlying type)的类型都满足 ordered 约束,都可以作为以 ordered 为约束的类型参数的类型实参,传入泛型函数。

我们将其组合到我们声明的 Stringer 接口中,然后应用一下我们的 StringifyLessThan 函数:

type Stringer interface {orderedcomparableString() string
}func main() {sl := StringifyLessThan([]MyString{"I", "", "love", "", "golang"}, MyString("cpp")) // 输出:[I]fmt.Println(sl)
}

这回编译器没有报错,并且程序输出了预期的结果。

好了,看了那么多例子,是时候正式对 Go 接口类型语法的扩展做一个说明了。下面是扩展后的接口类型定义的组成示意图:

我们看到,新的接口类型依然可以嵌入其他接口类型,满足组合的设计哲学;除了嵌入的其他接口类型外,其余的组成元素被称为接口元素(interface element)。

接口元素也有两类,一类就是常规的方法元素(method element),每个方法元素对应一个方法原型;另一类则是此次扩展新增的类型元素(type element),即在接口类型中,我们可以放入一些类型信息,就像前面的 ordered 接口那样。

类型元素可以是单个类型,也可以是一组由竖线 “|” 连接的类型,竖线 “|” 的含义是“并”,这样的一组类型被称为 union element。无论是单个类型,还是 union element 中由 “|” 分隔的类型,如果类型中不带有 “~” 符号的类型就代表其自身;而带有 “~” 符号的类型则代表以该类型为底层类型(underlying type)的所有类型,这类带有 “~” 的类型也被称为 approximation element,如下面示例:

type Ia interface {int | string  // 仅代表int和string
}type Ib interface {~int | ~string  // 代表以int和string为底层类型的所有类型
}

下图是类型元素的分解说明,供你参考:

不过要注意的是:union element 中不能包含带有方法元素的接口类型,也不能包含预定义的约束类型,如 comparable

扩展后,Go 将接口类型分成了两类,一类是基本接口类型(basic interface type),即其自身和其嵌入的接口类型都只包含方法元素,而不包含类型元素。基本接口类型不仅可以当做常规接口类型来用,即声明接口类型变量、接口类型变量赋值等,还可以作为泛型类型参数的约束。

除此之外的非空接口类型都属于非基本接口类型,即直接或间接(通过嵌入其他接口类型)包含了类型元素的接口类型。这类接口类型仅可以用作泛型类型参数的约束,或被嵌入到其他仅作为约束的接口类型中,下面的代码就很直观地展示了这两种接口类型的特征:

type BasicInterface interface { // 基本接口类型M1()
}type NonBasicInterface interface { // 非基本接口类型BasicInterface~int | ~string // 包含类型元素
}type MyString stringfunc (MyString) M1() {
}  func foo[T NonBasicInterface](a T) { // 非基本接口类型作为约束
}  func bar[T BasicInterface](a T) { // 基本接口类型作为约束
}  func main() {var s = MyString("hello")var bi BasicInterface = s // 基本接口类型支持常规用法var nbi NonBasicInterface = s // 非基本接口不支持常规用法,导致编译器错误:cannot use type NonBasicInterface outside a type constraint: interface contains type constraintsbi.M1()nbi.M1()foo(s)bar(s)           
}

看到这里,你可能会觉得有问题了:基本接口类型,由于其仅包含方法元素,我们依旧可以基于之前讲过的方法集合,来确定一个类型是否实现了接口,以及是否可以作为类型实参传递给约束下的类型形参。但对于只能作为约束的非基本接口类型,既有方法元素,也有类型元素,我们如何判断一个类型是否满足约束,并作为类型实参传给类型形参呢?

这时候我们就需要 Go 泛型落地时引入的新概念:类型集合(type set),类型集合将作为后续判断类型是否满足约束的基本手段。

五、类型集合(type set)

类型集合(type set)的概念是 Go 核心团队在 2021 年 4 月更新 Go 泛型设计方案时引入的。在那一次方案变更中,原方案中用于接口类型中定义类型元素的 type 关键字被去除了,泛型相关语法得到了进一步的简化。

一旦确定了一个接口类型的类型集合,类型集合中的元素就可以满足以该接口类型作为的类型约束,也就是可以将该集合中的元素作为类型实参传递给该接口类型约束的类型参数。

那么类型集合究竟是怎么定义的呢?下面我们来看一下。

结合 Go 泛型设计方案以及Go 语法规范,我们可以这么来理解类型集合:

  • 每个类型都有一个类型集合;
  • 非接口类型的类型的类型集合中仅包含其自身,比如非接口类型 T,它的类型集合为 {T},即集合中仅有一个元素且这唯一的元素就是它自身。

但我们最终要搞懂的是用于定义约束的接口类型的类型集合,所以以上这两点都是在为下面接口类型的类型集合定义做铺垫,定义如下:

  • 空接口类型(anyinterface{})的类型集合是一个无限集合,该集合中的元素为所有非接口类型。这个与我们之前的认知也是一致的,所有非接口类型都实现了空接口类型;
  • 非空接口类型的类型集合则是其定义中接口元素的类型集合的交集(如下图)。

由此可见,要想确定一个接口类型的类型集合,我们需要知道其中每个接口元素的类型集合。

上面我们说过,接口元素可以是其他嵌入接口类型,可以是常规方法元素,也可以是类型元素。当接口元素为其他嵌入接口类型时,该接口元素的类型集合就为该嵌入接口类型的类型集合;而当接口元素为常规方法元素时,接口元素的类型集合就为该方法的类型集合。

到这里你可能会很疑惑:一个方法也有自己的类型集合?

是的。Go 规定一个方法的类型集合为所有实现了该方法的非接口类型的集合,这显然也是一个无限集合,如下图所示:

通过方法元素的类型集合,我们也可以合理解释仅包含多个方法的常规接口类型的类型集合,那就是这些方法元素的类型集合的交集,即所有实现了这三个方法的类型所组成的集合。

最后我们再来看看类型元素。类型元素的类型集合相对来说是最好理解的,每个类型元素的类型集合就是其表示的所有类型组成的集合。如果是 ~T 形式,则集合中不仅包含 T 本身,还包含所有以 T 为底层类型的类型。如果使用 Union element,则类型集合是所有竖线 “|” 连接的类型的类型集合的并集。

接下来,我们来做个稍复杂些的实例分析,我们来分析一下下面接口类型I 的类型集合:

type Intf1 interface {~int | stringF1()F2()
}type Intf2 interface {~int | ~float64
}type I interface {Intf1 M1()M2()int | ~string | Intf2
}

我们看到,接口类型 I 由四个接口元素组成,分别是 Intf1M1M2Union element “int | ~string | Intf2”,我们只要分别求出这四个元素的类型集合,再取一个交集即可。

  • Intf1 的类型集合

Intf1 是接口类型 I 的一个嵌入接口,它自身也是由三个接口元素组成,它的类型集合为这三个接口元素的交集,即 {以 int 为底层类型的所有类型、string、实现了 F1 和 F2 方法的所有类型}

  • M1 和 M2 的类型集合

就像前面所说的,方法的类型集合是由所有实现该方法的类型组成的,因此 M1 的方法集合为 {实现了 M1 的所有类型}M2 的方法集合为 {实现了 M2 的所有类型}

  • int | ~string | Intf2 的类型集合

这是一个类型元素,它的类型集合为 int~stringIntf2 类型集合的并集。int 类型集合就是 {int}~string 的类型集合为 {以 string 为底层类型的所有类型},而 Intf2 的类型集合为 {以 int 为底层类型的所有类型,以 float64 为底层类型的所有类型}

为了更好地说明最终类型集合是如何取得的,我们在下面再列一下各个接口元素的类型集合:

  • Intf1 的类型集合:{以 int 为底层类型的所有类型、string、实现了 F1F2 方法的所有类型};
  • M1 的类型集合:{实现了 M1 的所有类型};
  • M2 的类型集合:{实现了 M2 的所有类型};
  • int | ~string | Intf2 的类型集合:{以 int 为底层类型的所有类型,以 float64 为底层类型的所有类型,以 string 为底层类型的所有类型}

接下来我们取一下上面集合的交集,也就是 {以 int 为底层类型的且实现了 F1F2M1M2 这个四个方法的所有类型}。

现在我们用代码来验证一下:

// typeset.gofunc doSomething[T I](t T) {
}type MyInt intfunc (MyInt) F1() {
}
func (MyInt) F2() {
}
func (MyInt) M1() {
}
func (MyInt) M2() {
}func main() {var a int = 11//doSomething(a) //int does not implement I (missing F1 method)var b = MyInt(a)doSomething(b) // ok
}

如上代码,我们定义了一个以 int 为底层类型的自定义类型 MyInt 并实现了四个方法,这样 MyInt 就满足了泛型函数 doSomething 中约束 I 的要求,可以作为类型实参传递。

六、简化版的约束形式

在前面的介绍和示例中,泛型参数的约束都是一个完整的接口类型,要么是独立定义在泛型函数外面(比如下面代码中的 I 接口),要么以接口字面值的形式,直接放在类型参数列表中对类型参数进行约束,比如下面示例中 doSomething2 类型参数列表中的接口类型字面值:

type I interface { // 独立于泛型函数外面定义~int | ~string
}func doSomething1[T I](t T)
func doSomething2[T interface{~int | ~string}](t T) // 以接口类型字面值作为约束

但**在约束对应的接口类型中仅有一个接口元素,且该元素为类型元素时,**Go 提供了简化版的约束形式,我们不必将约束独立定义为一个接口类型,比如上面的 doSomething2 可以简写为下面简化形式:

func doSomething2[T ~int | ~string](t T) // 简化版的约束形式

你看,这个简化版的约束形式就是去掉了 interface 关键字和外围的大括号,如果用一个一般形式来表述,那就是:

func doSomething[T interface {T1 | T2 | ... | Tn}](t T)等价于下面简化版的约束形式:func doSomething[T T1 | T2 | ... | Tn](t T) 

这种简化形式也可以理解为一种类型约束的语法糖。不过有一种情况要注意,那就是定义仅包含一个类型参数的泛型类型时,如果约束中仅有一个 *int 型类型元素,我们使用上述简化版形式就会有问题,比如:

type MyStruct [T * int]struct{} // 编译错误:undefined: T// 编译错误:int (type) is not an expression

当遇到这种情况时,Go 编译器会将该语句理解为一个类型声明:MyStruct 为新类型的名字,而其底层类型为 [T *int]struct{},即一个元素为空结构体类型的数组。

那么怎么解决这个问题呢?目前有两种方案,一种是用完整形式的约束:

type MyStruct[T interface{*int}] struct{} 

另外一种则是在简化版约束的 *int 类型后面加上一个逗号:

type MyStruct[T *int,] struct{} 

七、约束的类型推断

在大多数情况下,我们都可以使用类型推断避免在调用泛型函数时显式传入类型实参,Go 泛型可以根据泛型函数的实参推断出类型实参。但当我们遇到下面示例中的泛型函数时,光依靠函数类型实参的推断是无法完全推断出所有类型实参的:

func DoubleDefined[S ~[]E, E constraints.Integer](s S) S {

因为像 DoubleDefined 这样的泛型函数,其类型参数 E 在其常规参数列表中并未被用来声明输入参数,函数类型实参推断仅能根据传入的 S 的类型,推断出类型参数 S 的类型实参,E 是无法推断出来的。所以为了进一步避免开发者显式传入类型实参,Go 泛型支持了约束类型推断(constraint type inference),即基于一个已知的类型实参(已经由函数类型实参推断判断出来了),来推断其他类型参数的类型。

我们还以上面 DoubleDefined 这个泛型函数为例,当通过实参推断得到类型 S 后,Go 会尝试启动约束类型推断来推断类型参数 E 的类型。但你可能也看出来了,约束类型推断可成功应用的前提是 S 是由 E 所表示的。

八、小结

本文我们先从 Go 泛型内置的约束 anycomparable 入手,充分了解了约束对于泛型函数的类型参数以及泛型函数中的实现代码的限制与影响。然后,我们了解了如何自定义约束,知道了因为 Go 不支持操作符重载,单纯依赖基于行为的接口类型(仅包含方法元素)作约束是无法满足泛型函数的要求的。这样我们进一步学习了 Go 接口类型的扩展语法:支持类型元素

既有方法元素,也有类型元素,对于作为约束的非基本接口类型,我们就不能像以前那样仅凭是否实现方法集合来判断是否实现了该接口,新的判定手段为类型集合。并且,类型集合不是一个运行时概念,我们目前还无法通过运行时反射直观看到一个接口类型的类型集合是什么!

Go 内置了像 anycomparable 的约束,后续随着 Go 核心团队在 Go 泛型使用上的经验的逐渐丰富,Go 标准库中会增加更多可直接使用的约束。原计划在 Go 1.18 版本加入 Go 标准库的一些泛型约束的定义暂放在了 Go 实验仓库中,你可以自行参考。

相关文章:

Go 泛型之泛型约束

Go 泛型之泛型约束 文章目录 Go 泛型之泛型约束一、引入二、最宽松的约束&#xff1a;any三、支持比较操作的内置约束&#xff1a;comparable四、自定义约束五、类型集合&#xff08;type set&#xff09;六、简化版的约束形式七、约束的类型推断八、小结 一、引入 虽然泛型是…...

【数据仓库与联机分析处理】数据仓库

目录 一、数据仓库的概念 二、数据仓库与操作性数据库的区别 三、发展前期 四、数据仓库的系统结构 五、建模划分 六、主要案例 一、数据仓库的概念 目前很难给数据仓库&#xff08;Data Warehouse&#xff09;一个严格的定义&#xff0c;不准确地说&#xff0c;数据仓库…...

机器学习:贝叶斯估计在新闻分类任务中的应用

文章摘要 随着互联网的普及和发展&#xff0c;大量的新闻信息涌入我们的生活。然而&#xff0c;这些新闻信息的质量参差不齐&#xff0c;有些甚至包含虚假或误导性的内容。因此&#xff0c;对新闻进行有效的分类和筛选&#xff0c;以便用户能够快速获取真实、有价值的信息&…...

[C#]基于deskew算法实现图像文本倾斜校正

【算法介绍】 让我们开始讨论Deskeweing算法的一般概念。我们的主要目标是将旋转的图像分成文本块&#xff0c;并确定它们的角度。为了让您详细了解我将使用的方法&#xff1a; 照常-将图像转换为灰度。应用轻微的模糊以减少图像中的噪点。现在&#xff0c;我们的目标是找到带…...

Qt通过pos()获取坐标信息

背景&#xff1a;这是一个QWidget窗体&#xff0c;里面是各种布局的组合&#xff0c;一层套一层。 我希望得到绿色部分的坐标信息(x,y) QPoint get_pos(QWidget* w, QWidget* parent) {if ((QWidget*)w->parent() parent) {return w->pos();}else {QPoint pos(w->po…...

【Webpack】资源输入输出 - 配置资源出口

所有与出口相关的配置都集中在 output对象里 output对象里可以包含数十个配置项&#xff0c;这里介绍几个常用的 filename 顾名思义&#xff0c;filename的作用是控制输出资源的文件名&#xff0c;其形式为字符串&#xff0c;如&#xff1a; module.exports {entry: ./src/a…...

【XR806开发板试用】XR806串口驱动CM32M对小厨宝的控制实验

一.说明 非常感谢基于安谋科技STAR-MC1的全志XR806 Wi-FiBLE开源鸿蒙开发板试用活动,并获得开发板试用。 XR806是全志科技旗下子公司广州芯之联研发设计的一款支持WiFi和BLE的高集成度无线MCU芯片&#xff0c;支持OpenHarmony minisystem和FreeRTOS&#xff0c;具有集成度高、…...

中介者模式-Mediator Pattern-1

如果在一个系统中对象之间的联系呈现为网状结构&#xff0c; 对象之间存在大量的多对多联系&#xff0c;将导致系统非常复杂。 这些对象既会影响别的对象&#xff0c;也会被别的对象所影响。 这些对象称为同事对象&#xff0c;它们之间通过彼此的相互作用实现系统的行为。 在网…...

ASP.NET Core基础之图片文件(一)-WebApi图片文件上传到文件夹

阅读本文你的收获&#xff1a; 了解WebApi项目保存上传图片的三种方式学习在WebApi项目中如何上传图片到指定文件夹中 在ASP.NET Core基础之图片文件(一)-WebApi访问静态图片文章中&#xff0c;学习了如何获取WebApi中的静态图片&#xff0c;本文继续分享如何上传图片。 那么…...

精准掌控 Git 忽略规则:定制化 .gitignore 指南

&#x1f9d9;‍♂️ 诸位好&#xff0c;吾乃诸葛妙计&#xff0c;编程界之翘楚&#xff0c;代码之大师。算法如流水&#xff0c;逻辑如棋局。 &#x1f4dc; 吾之笔记&#xff0c;内含诸般技术之秘诀。吾欲以此笔记&#xff0c;传授编程之道&#xff0c;助汝解技术难题。 &…...

Harmony 开始支持 Flutter ,聊聊 Harmony 和 Flutter 之间的因果

原创作者&#xff1a;恋猫de小郭 相信大家都已经听说过&#xff0c;明年的 Harmony Next 版本将正式剥离 AOSP 支持 &#xff0c;基于这个话题我已经做过一期问题汇总 &#xff0c;当时在 现有 App 如何兼容 Harmony Next 问题上提到过&#xff1a; 华为内部也主导适配目前的主…...

k8s 之7大CNI 网络插件

一、介绍 网络架构是Kubernetes中较为复杂、让很多用户头疼的方面之一。Kubernetes网络模型本身对某些特定的网络功能有一定要求&#xff0c;但在实现方面也具有一定的灵活性。因此&#xff0c;业界已有不少不同的网络方案&#xff0c;来满足特定的环境和要求。 CNI意为容器网络…...

stable diffusion 人物高级提示词(一)头部篇

一、女生发型 prompt描述推荐用法Long hair长发一定不要和 high ponytail 一同使用Short hair短发-Curly hair卷发-Straight hair直发-Ponytail马尾high ponytail 高马尾&#xff0c;一定不要和 long hair一起使用&#xff0c;会冲突Pigtails2条辫子-Braid辫子只写braid也会生…...

限制哪些IP能连接postgre

打开C:\Program Files\PostgreSQL\9.4\data\pg_hba.conf 以下代表本机能连&#xff0c;172.16.73.xx都能连&#xff08;/24就代表最后一位是0-255&#xff09;&#xff0c;如果是172.16.73.11/32那就是限制了172.16.73.11才能连&#xff08;实际我设置/32是无效的&#xff09;&…...

可狱可囚的爬虫系列课程 08:新闻数据爬取实战

前言 本篇文章中我带大家针对前面所学 Requests 和 BeautifulSoup4 进行一个实操检验。 相信大家平时或多或少都有看新闻的习惯&#xff0c;那么我们今天所要爬取的网站便是新闻类型的&#xff1a;中国新闻网&#xff0c;我们先来使用爬虫爬取一些具有明显规则或规律的信息&am…...

mysql2pgsql

使用pgloader进行迁移 pgloader是一个强大的数据迁移工具&#xff0c;专为将不同数据库之间的数据迁移到PostgreSQL而设计。它支持从MySQL到PostgreSQL的迁移&#xff0c;并提供了一种简单且灵活的方式来转移数据。 安装pgloader 使用pgloader迁移数据 1、命令行方式 2、脚…...

设计模式-流接口模式

设计模式专栏 模式介绍模式特点应用场景流接口模式和工厂模式的区别代码示例Java实现流接口模式Python实现流接口模式 流接口模式在spring中的应用 模式介绍 流接口模式是一种面向对象的编程模式&#xff0c;它可以使代码更具可读性和流畅性。流接口模式的核心思想是采用链式调…...

Java 堆与栈的作用与区别

栈是运行时的单位&#xff0c;而堆是存储的单位&#xff0c;栈解决程序的运行问题&#xff0c;堆解决数据存储的问题。 一个线程对应一个线程栈&#xff0c;栈是运行单位&#xff0c;里面存储的信息都是跟当前线程相关的信息&#xff0c;包括局部变量、程序运行状态、方法返回…...

再谈小米汽车

文章目录 1. 外观2. 电机3. 电池4. 风阻5. 强度6. 智能驾驶 我在两年前分析过小米造车的形势&#xff0c;大家可以 点击这里查看。今天小米官宣传了新汽车。看一下它公布的主要信息&#xff1a; 1. 外观 汽车外观是向保时捷致敬&#xff0c;因此它的外观特别像保时捷。不过外…...

Power Apps 学习笔记 - IOrganizationService Interface

文章目录 1. IOrganization Interface1.1 基本介绍1.2 方法分析 2. Entity对象2.1 Constructor2.2 Properties2.3 Methods 3. 相关方法3.1 单行查询 Retrive3.2 多行查询 RetriveMultiple3.3 增加 Create3.4 删除 Delete3.5 修改 Update 4. 数据查询的不同实现方式4.1 QueryExp…...

常见函数的4种类型(js的问题)

• 匿名函数 • 回调函数 • 递归函数 • 构造函数 1、匿名函数 定义时候没有任何变量引用的函数 匿名函数自调&#xff1a;函数只执行一次 (function(a, b){console.log(a b);} )(1, 2);// 等价于 function foo (a, b){console.log(a b); }foo(1, …...

DNS主从服务器、转发(缓存)服务器

一、主从服务器 1、基本含义 DNS辅助服务器是一种容错设计&#xff0c;考虑的是一旦DNS主服务器出现故障或因负载太重无法及时响应客户机请求&#xff0c;辅助服务器将挺身而出为主服务器排忧解难。辅助服务器的区域数据都是从主服务器复制而来&#xff0c;因此辅助服务器的数…...

第二十一章 网络编程

第二十一章 网络编程 1.网络相关概念2.IP地址3.域名与端口4.网络协议5.TCP与UDP6.InetAddress7.Socket8.TCP字节流编程19.TCP字节流编程210.TCP字节流编程311.网络上传文件112.网络上传文件213.网络上传文件314.Netstat15.TCP连接秘密16.UPD原理17.UPD网络编程118.UDP网络编程2…...

scratch新跳7游戏 2023年12月中国电子学会图形化编程 少儿编程 scratch编程等级考试四级真题和答案解析

目录 scratch新跳7游戏 一、题目要求 1、准备工作 2、功能实现 二、案例分析...

三、C#面向对象编程(接口与实现)

在C#中&#xff0c;接口是一种定义方法但不包含实现的方式&#xff0c;可以被多个类实现以支持不同的行为。通过接口&#xff0c;我们可以定义一组标准的成员&#xff0c;让类遵循特定的契约。 下面是一个关于接口和实现的简单示例&#xff1a; // 定义一个接口 public inter…...

【java爬虫】股票数据获取工具前后端代码

前面我们有好多文章都是在介绍股票数据获取工具&#xff0c;这是一个前后端分离项目 后端技术栈&#xff1a;springboot&#xff0c;sqlite&#xff0c;jdbcTemplate&#xff0c;okhttp 前端技术栈&#xff1a;vue&#xff0c;element-plus&#xff0c;echarts&#xff0c;ax…...

Scikit-Learn线性回归(四)

Scikit-Learn线性回归四:梯度下降 1、梯度下降1.1、梯度下降概述1.2、梯度下降及原理1.3、梯度下降的实现2、梯度下降法求解线性回归的最优解2.1、梯度下降法求解的原理2.2、梯度下降法求解线性回归的最优解2.3、梯度下降法求解线性回归案例(波士顿房价预测)3、Scikit-Learn…...

SCT2330C——3.8V-28V输入,3A,低EMI,超低功耗同步降压DCDC转换器

描述&#xff1a; SCT2330C是3A同步buck变换器&#xff0c;输入电压范围高达28V&#xff0c;完全集成了80mΩ高压侧MOSFET和42mΩ低压侧MOSFET&#xff0c;提供高效降压DC-DC转换。SCT2330C采用峰值电流模式控制&#xff0c;集成补偿网络&#xff0c;通过最小化片外元件数量&a…...

php生成唯一ID的5种方法介绍

php生成唯一ID的5种方法介绍 工作中使用到唯一ID的场景非常多&#xff0c;如临时缓存文件、临时变量、临时安全码等。 uniqid()函数基于以微妙计的当前时间&#xff0c;生成一个唯一的ID。由于生成唯一ID与微妙时间关联&#xff0c;因此生成ID的唯一性非常可靠。 生成的唯一…...

向日葵远程工具安装Mysql的安装与配置

目录 一、向日葵远程工具安装 1.1 简介 1.2 下载地址 二、Mysql 5.7 安装与配置 2.1 简介 2.2 安装 2.3 初始化mysql服务端 2.4 启动mysql服务 2.5 登录mysql 2.6 修改密码 2.7 设置外部访问 三、思维导图 一、向日葵远程工具安装 1.1 简介 向日葵远程控制是一款用…...

做新闻网站开发和测试的硬件/宽带推广方案

Less-16 POST - Blind- Boolian/Time Based - Double quotes (基于bool型/时间延迟的双引号POST型盲注) 布尔型注入&#xff0c;跟less-15差不多&#xff0c;只需要把换成) 今天发现另一个新方式&#xff0c;只需要在Google的hackbar操作&#xff0c;比较简单 新方法来自wken…...

石家庄市建设局质监站网站/网络站点推广的方法有哪些

前言 在SpringBoot中使用自定义注解、aop切面打印web请求日志。主要是想把controller的每个request请求日志收集起来&#xff0c;调用接口、执行时间、返回值这几个重要的信息存储到数据库里&#xff0c;然后可以使用火焰图统计接口调用时长&#xff0c;平均响应时长&am…...

poi player wordpress/怎样打小广告最有效

9月22日消息&#xff0c;据国外媒体报道&#xff0c;日前微软开始通过发布补丁清理关于Windows 10的免费升级应用。 此前微软一直通过弹窗提醒要求Windows 7以及Windows 8用户免费升级至最新操作系统Windows 10。随着7月29日免费升级的到期&#xff0c;拖了近两个月后&#xff…...

wordpress安装成功后怎么进后台/百度指数官网入口登录

为什么80%的码农都做不了架构师&#xff1f;>>> 请确定您已经了解了 nGrinder 的 Groovy 脚本结构&#xff1a;nGrinder 的 Groovy 脚本使用指南&#xff08;Groovy 脚本结构&#xff09; 在 nGrinder 上创建 Groovy 脚本时&#xff0c;会自动生成一个基础的脚本&a…...

wordpress自动更新电视剧/网站推广四个阶段

这一题的思路想对了&#xff0c;就是middle justify的时候&#xff0c;当一行空格不均匀分配的处理没想清楚。 不知道这题考的点在哪里 class Solution {public List<String> fullJustify(String[] words, int maxWidth) {//res保存每一个string里的单词个数List<Int…...

活动 wordpress主题/网络软文范文

前言在前端项目的规模和复杂性不断提升的情况下&#xff0c;各类构建思想和相应工具层出不穷。本文竭己所能对比了当下13个构建工具&#xff0c;包括 Browserify、 Webpack、 Rollup、 Grunt、 Gulp和 Yeoman6个广为流行的工具&#xff0c; FIS、 Athena、 WeFlow和 Cooking等4…...