云原生系列Go语言篇-泛型Part 1
“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语言&云原生自我提升系列,欢迎关注后续文章。
相关文章:
云原生系列Go语言篇-泛型Part 1
“Don’t Repeat Yourself”是常见的软件工程建议。与其重新创建一个数据结构或函数,不如重用它,因为对重复的代码保持更改同步非常困难。在像 Go 这样的强类型语言中,每个函数参数及每个结构体字段的类型必须在编译时确定。这种严格性使编译…...
力扣1089题 复写零 双指针解法
2. 复写零 给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。 注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。 示例 1&…...
Redis基础与运用
一、redis介绍 简介 Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。 Redis 与其他 key - value 缓存产品有以下三个特点: Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可…...
PTA:猜帽子游戏 ,C语言
题目 宝宝们在一起玩一个猜帽子游戏。每人头上被扣了一顶帽子,有的是黑色的,有的是黄色的。每个人可以看到别人头上的帽子,但是看不到自己的。游戏开始后,每个人可以猜自己头上的帽子是什么颜色,或者可以弃权不猜。如…...
ESP32基于IDF框架OTA学习记录
ESP32基于IDF框架OTA学习记录 参考: 空中升级 (OTA) - ESP32 - — ESP-IDF 编程指南 v5.1.1 文档 (espressif.com) 目录 ESP32基于IDF框架OTA学习记录1.分区表2.native_ota_example上手2.1配置分区表2.2配置OTA的bin文件2.3修改esp32的https证书验证方法2.4修改当…...

分布式技术(一)分布式的架构的演进
💌 所属专栏:【微服务】😀 作 者:长安不及十里💻 工作:目前从事电力行业开发🌈 目标:全栈开发🚀 个人简介:一个正在努力学技术的Java工程师,专注基…...

webpack 打包优化
在vue.config.js中配置 下载 uglifyjs-webpack-plugin 包 const { defineConfig } require("vue/cli-service"); var path require("path");module.exports defineConfig({transpileDependencies: true,filenameHashing: false, // 去除Vue打包后.cs…...

electron windows robotjs 安装教程
Robotjs 安装 前言第一步 : 安装python第二步 : 安装Visual Studio 2022第三步 : 安装robotjs 前言 robotjs可以控制鼠标键盘,获取屏幕内容,配合electron可做很多自动化操作。windows下配置环境有很多坑,很多文章都太旧了。试了很多次发现了…...

IDEA解决Git冲突详解
目录 前言: 何为冲突 冲突演示 IDEA冲突解决 小结: 前言: 相信大家多多少少都有了解和使用过Git,作为Java程序员idea可谓是无敌的存在了,那么如何使用idea解决Git冲突呢?不瞒大家前段时间在公司把同事…...

Vue3使用kkFileView预览文件pdf
kkFileView - 在线文件预览kkFileView官网 - kkFileView使用Spring Boot搭建,易上手和部署,基本支持主流办公文档的在线预览,如doc,docx,Excel,pdf,txt,zip,rar,图片等等https://kkfileview.keking.cn/zh-cn/docs/usage.html业务场景…...

建造者模式-C语言实现
UML类图: 代码实现: #include <stdio.h> #include <stdlib.h>// 产品类 typedef struct {char* part1;char* part2;char* part3; } Product;// 抽象建造者类 typedef struct {void (*buildPart1)(void*, const char*);void (*buildPart2)(v…...

Jmeter+influxdb+grafana监控平台在windows环境的搭建
原理:Jmeter采集的数据存储在infuxdb数据库中,grafana将数据库中的数据在界面上进行展示 一、grafana下载安装 Download Grafana | Grafana Labs 直接选择zip包下载,下载后解压即可,我之前下载过比较老的版本,这里就…...

关注这两点 或能避开一些现货黄金交易的陷阱
在现货黄金投资中,交易机会是处处都有,但是亏损的情况也可能出现。投资者要在陷阱处处的市场中获得稳定盈利,就需要懂得如何规避现货黄金投资的陷阱。下面我们就来介绍两个很常用的避开陷阱的方法。 看交易的活跃度。交易越活跃,市…...

Python 文件读写
Python 文件读写笔记整理 参数说明 open(path, flag[, encoding][,errors]) path:要打开文件的路径 flag:打开方式 encoding:编码方式 errors:错误处理 Flag打开方式表 模式 描述 r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 rb 以二进制格…...

线性分组码的奇偶校验矩阵均匀性分析
回顾信道编解码知识,我们知道信道编码要求编码具有检纠错能力,作为FEC(forward error correction)前向纠错编码的一类,线性分组码表示校验位与信息位的关系能够线性表示。 在这篇文章中,并不是要讨论信道编…...

leetcode算法之链表
目录 1.两数相加2.两两交换链表中的节点3.重排链表4.合并K个升序链表5.K个一组翻转链表 1.两数相加 两数相加 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(…...
2023.11.27 滴滴P0级故障或为k8s升级造成
滴滴11.27 P0级故障|打车|宕机|网约车|出租车|滴滴出行|系统故障_网易订阅 (163.com) 如何看待滴滴11月27日故障,对日常生产生活有哪些影响? - 知乎 (zhihu.com) 最新消息滴滴P0故障原因,是由于k8s集群升级导致的,后面又进行版本…...

Ubuntu16.04.4系统本地提权实验
目录 1.介绍: 2.实验: 3.总结: 1.介绍: 1.1:eBPF简介:eBPF(extendedBerkeleyPacketFilter)是内核源自于BPF的一套包过滤机制,BPF可以理解成用户与内核之间的一条通道,有非常强大的…...
Vue中使用正则表达式进行文本匹配和处理的方法
1. 正则表达式基础 正则表达式是一种用来匹配字符串的模式。它由普通字符(例如字符 a 到 z)和特殊字符(称为"元字符")组成。以下是一些基本的正则表达式示例: 匹配邮箱的正则表达式: /^[\w-](\…...
php许愿墙代码包括前端和后端部分
以下是一个简单的PHP许愿墙代码示例,包括前端和后端部分: 前端HTML代码(index.html): <!DOCTYPE html> <html> <head><title>许愿墙</title> </head> <body><h1>许…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...