go语言基础之泛型
1.泛型
泛型是一种独立于所使用的特定类型的编写代码的方法。使用泛型可以编写出适用于一组类型中的任何一种的函数和类型。
1.1 为什么需要泛型
func reverse(s []int) []int {l := len(s)r := make([]int, l)for i, e := range s {r[l-i-1] = e}return r
}fmt.Println(reverse([]int{1, 2, 3, 4})) // [4 3 2 1]
可是这个函数只能接收[]int
类型的参数,如果我们想支持[]float64
类型的参数,我们就需要再定义一个reverseFloat64Slice
函数。
func reverseFloat64Slice(s []float64) []float64 {l := len(s)r := make([]float64, l)for i, e := range s {r[l-i-1] = e}return r
}
如果要想支持[]string
类型切片就要定义reverseStringSlice
函数,如果想支持[]xxx
就需要定义一个reverseXxxSlice.
一遍一遍地编写相同的功能是低效的,实际上这个反转切片的函数并不需要知道切片中元素的类型,但为了适用不同的类型我们把一段代码重复了很多遍。
Go1.18之前我们可以尝试使用反射去解决上述问题,但是使用反射在运行期间获取变量类型会降低代码的执行效率并且失去编译期的类型检查,同时大量的反射代码也会让程序变得晦涩难懂。
从Go1.18开始,使用泛型就能够编写出适用所有元素类型的“普适版”reverse
函数。
func reverseWithGenerics[T any](s []T) []T {l := len(s)r := make([]T, l)for i, e := range s {r[l-i-1] = e}return r
}
1.2 泛型语法
泛型为Go语言添加了三个新的重要特性:
- 函数和类型的类型参数。
- 将接口类型定义为类型集,包括没有方法的类型。
- 类型推断,它允许在调用函数时在许多情况下省略类型参数。
类型参数
类型形参和类型实参
函数定义时可以指定形参,函数调用时需要传入实参。
func min(a, b int) int {//a,b两个形参
if a <= b {return a
}
return b
}min(10, 20)//调用函数min,传入两个实参 10,20
现在,Go语言中的函数和类型支持添加类型参数。类型参数列表看起来像普通的参数列表,只不过它使用方括号([]
)而不是圆括号(()
)。
借助泛型,我们可以声明一个适用于一组类型的min
函数。
func min[T int | float64](a, b T) T {if a <= b {return a}return b
}
类型实例化
这次定义的min
函数就同时支持int
和float64
两种类型,也就是说当调用min
函数时,我们既可以传入int
类型的参数。
m1 := min[int](1, 2) // 1
也可以传入float64
类型的参数。
m2 := min[float64](-0.1, -0.2) // -0.2
向 min
函数提供类型参数(在本例中为int
和float64
)称为实例化( instantiation )。
类型实例化分两步进行:
- 首先,编译器在整个泛型函数或类型中将所有类型形参(type parameters)替换为它们各自的类型实参(type arguments)。
- 其次,编译器验证每个类型参数是否满足相应的约束。
在成功实例化之后,我们将得到一个非泛型函数,它可以像任何其他函数一样被调用。例如:
fmin := min[float64] // 类型实例化,编译器生成T=float64的min函数
m2 = fmin(1.2, 2.3) // 1.2
min[float64]
得到的是类似我们之前定义的minFloat64
函数——fmin
,我们可以在函数调用中使用它。
类型参数的使用
除了函数中支持使用类型参数列表外,类型也可以使用类型参数列表。
type Slice[T int | string] []Ttype Map[K int | string, V float32 | float64] map[K]Vtype Tree[T interface{}] struct {left, right *Tree[T]value T
}
在上述泛型类型中,T
、K
、V
都属于类型形参,类型形参后面是类型约束,类型实参需要满足对应的类型约束。
泛型类型可以有方法,例如为上面的Tree
实现一个查找元素的Lookup
方法。
func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }
要使用泛型类型,必须进行实例化。Tree[string]
是使用类型实参string
实例化 Tree
的示例。
var stringTree Tree[string]
类型约束
普通函数中的每个参数都有一个类型; 该类型定义一系列值的集合。例如,我们上面定义的非泛型函数minFloat64
那样,声明了参数的类型为float64
,那么在函数调用时允许传入的实际参数就必须是可以用float64
类型表示的浮点数值。
类似于参数列表中每个参数都有对应的参数类型,类型参数列表中每个类型参数都有一个类型约束。类型约束定义了一个类型集——只有在这个类型集中的类型才能用作类型实参。
Go语言中的类型约束是接口类型。
就以上面提到的min
函数为例,我们来看一下类型约束常见的两种方式。
类型约束接口可以直接在类型参数列表中使用。
// 类型约束字面量,通常外层interface{}可省略
func min[T interface{ int | float64 }](a, b T) T {if a <= b {return a}return b
}
作为类型约束使用的接口类型可以事先定义并支持复用。
// 事先定义好的类型约束类型
type Value interface {int | float64
}
func min[T Value](a, b T) T {if a <= b {return a}return b
}
在使用类型约束时,如果省略了外层的interface{}
会引起歧义,那么就不能省略。例如:
type IntPtrSlice [T *int] []T // T*int ?type IntPtrSlice[T *int,] []T // 只有一个类型约束时可以添加`,`
type IntPtrSlice[T interface{ *int }] []T // 使用interface{}包裹
类型集
Go1.18开始接口类型的定义也发生了改变,由过去的接口类型定义方法集(method set)变成了接口类型定义类型集(type set)。也就是说,接口类型现在可以用作值的类型,也可以用作类型约束。
把接口类型当做类型集相较于方法集有一个优势: 我们可以显式地向集合添加类型,从而以新的方式控制类型集。
Go语言扩展了接口类型的语法,让我们能够向接口中添加类型。例如
type V interface {int | string | bool
}
上面的代码就定义了一个包含 int
、 string
和 bool
类型的类型集。
从 Go 1.18 开始,一个接口不仅可以嵌入其他接口,还可以嵌入任何类型、类型的联合或共享相同底层类型的无限类型集合。
当用作类型约束时,由接口定义的类型集精确地指定允许作为相应类型参数的类型。
-
|
符号T1 | T2
表示类型约束为T1和T2这两个类型的并集,例如下面的Integer
类型表示由Signed
和Unsigned
组成。type Integer interface {Signed | Unsigned }
-
~
符号~T
表示所以底层类型是T的类型,例如~string
表示所有底层类型是string
的类型集合。type MyString string // MyString的底层类型是string
注意:
~
符号后面只能是基本类型。
接口作为类型集是一种强大的新机制,是使类型约束能够生效的关键。目前,使用新语法表的接口只能用作类型约束。
any接口
空接口在类型参数列表中很常见,在Go 1.18引入了一个新的预声明标识符,作为空接口类型的别名。
// src/builtin/builtin.gotype any = interface{}
由此,我们可以使用如下代码:
func foo[S ~[]E, E any]() {// ...
}
类型推断
最后一个新的主要语言特征是类型推断。从某些方面来说,这是语言中最复杂的变化,但它很重要,因为它能让人们在编写调用泛型函数的代码时更自然。
函数参数类型推断
对于类型参数,需要传递类型参数,这可能导致代码冗长。回到我们通用的 min
函数:
func min[T int | float64](a, b T) T {if a <= b {return a}return b
}
类型形参T
用于指定a
和b
的类型。我们可以使用显式类型实参调用它:
var a, b, m float64
m = min[float64](a, b) // 显式指定类型实参
在许多情况下,编译器可以从普通参数推断 T
的类型实参。这使得代码更短,同时保持清晰。
var a, b, m float64m = min(a, b) // 无需指定类型实参
这种从实参的类型推断出函数的类型实参的推断称为函数实参类型推断。函数实参类型推断只适用于函数参数中使用的类型参数,而不适用于仅在函数结果中或仅在函数体中使用的类型参数。例如,它不适用于像 MakeT [ T any ]() T
这样的函数,因为它只使用 T
表示结果。
约束类型推断
Go 语言支持另一种类型推断,即约束类型推断。接下来我们从下面这个缩放整数的例子开始:
// Scale 返回切片中每个元素都乘c的副本切片
func Scale[E constraints.Integer](s []E, c E) []E {r := make([]E, len(s))for i, v := range s {r[i] = v * c}return r
}
这是一个泛型函数适用于任何整数类型的切片。
现在假设我们有一个多维坐标的 Point
类型,其中每个 Point
只是一个给出点坐标的整数列表。这种类型通常会实现一些业务方法,这里假设它有一个String
方法。
type Point []int32func (p Point) String() string {b, _ := json.Marshal(p)return string(b)
}
由于一个Point
其实就是一个整数切片,我们可以使用前面编写的Scale
函数:
func ScaleAndPrint(p Point) {r := Scale(p, 2)fmt.Println(r.String()) // 编译失败
}
不幸的是,这代码会编译失败,输出r.String undefined (type []int32 has no field or method String
的错误。
问题是Scale
函数返回类型为[]E
的值,其中E
是参数切片的元素类型。当我们使用Point
类型的值调用Scale
(其基础类型为[]int32)时,我们返回的是[]int32
类型的值,而不是Point
类型。这源于泛型代码的编写方式,但这不是我们想要的。
为了解决这个问题,我们必须更改 Scale
函数,以便为切片类型使用类型参数。
func Scale[S ~[]E, E constraints.Integer](s S, c E) S {r := make(S, len(s))for i, v := range s {r[i] = v * c}return r
}
我们引入了一个新的类型参数S
,它是切片参数的类型。我们对它进行了约束,使得基础类型是S
而不是[]E
,函数返回的结果类型现在是S
。由于E
被约束为整数,因此效果与之前相同:第一个参数必须是某个整数类型的切片。对函数体的唯一更改是,现在我们在调用make
时传递S
,而不是[]E
。
现在这个Scale
函数,不仅支持传入普通整数切片参数,也支持传入Point
类型参数。
这里需要思考的是,为什么不传递显式类型参数就可以写入 Scale
调用?也就是说,为什么我们可以写 Scale(p, 2)
,没有类型参数,而不是必须写 Scale[Point, int32](p, 2)
?
新 Scale
函数有两个类型参数——S
和 E
。在不传递任何类型参数的 Scale(p, 2)
调用中,如上所述,函数参数类型推断让编译器推断 S
的类型参数是 Point
。但是这个函数也有一个类型参数 E
,它是乘法因子 c
的类型。相应的函数参数是2
,因为2
是一个非类型化的常量,函数参数类型推断不能推断出 E
的正确类型(最好的情况是它可以推断出2
的默认类型是 int
,而这是错误的,因为Point 的基础类型是[]int32
)。相反,编译器推断 E
的类型参数是切片的元素类型的过程称为约束类型推断。
约束类型推断从类型参数约束推导类型参数。当一个类型参数具有根据另一个类型参数定义的约束时使用。当其中一个类型参数的类型参数已知时,约束用于推断另一个类型参数的类型参数。
通常的情况是,当一个约束对某种类型使用 ~type 形式时,该类型是使用其他类型参数编写的。我们在 Scale
的例子中看到了这一点。S
是 ~[]E
,后面跟着一个用另一个类型参数写的类型[]E
。如果我们知道了 S
的类型实参,我们就可以推断出E
的类型实参。S
是一个切片类型,而 E
是该切片的元素类型。
2.什么时候使用泛型
2.1 泛型使用方式
使用语言定义的容器类型时
当我们编写的是操作 Go 语言定义的特殊容器类型(slice、map和chennel)的函数。如果函数具有包含这些类型的参数,并且函数的代码并不关心元素的类型,那么使用类型参数可能是有用的。
例如:返回任何类型map中所有的key
// MapKeys 返回m中所有key组成的切片
func MapKeys[Key comparable, Val any](m map[Key]Val) []Key {s := make([]Key, 0, len(m))for k := range m {s = append(s, k)}return s
}
通用数据结构
类型参数另一个适用场景就是用于通用数据结构。通用数据结构类似于slice或map,但不是内置在语言中的,例如链表或二叉树。
对于类型参数,优先选择函数而不是方法
Tree 示例说明了另一个一般原则:当你需要比较函数之类的东西时,更喜欢使用函数而不是方法。
实现通用方法
类型参数可能有用的另一种情况是,不同类型需要实现某些公共方法,而不同类型的实现看起来都是相同的。
例如,考虑标准库的 sort.Interface
,它要求类型实现三个方法: Len
、 Swap
和 Less
。
下面是一个泛型类型 SliceFn
的示例,它为切片类型实现 sort.Interface
:
// SliceFn 为T类型切片实现 sort.Interface
type SliceFn[T any] struct {s []Tless func(T, T) bool
}func (s SliceFn[T]) Len() int {return len(s.s)
}
func (s SliceFn[T]) Swap(i, j int) {s.s[i], s.s[j] = s.s[j], s.s[i]
}
func (s SliceFn[T]) Less(i, j int) bool {return s.less(s.s[i], s.s[j])
}
2.2 不应该使用类型参数
不要用类型参数替换接口类型
众所周知,Go有接口类型。接口类型允许一种通用编程。
例如,广泛使用的io.Reader
接口提供了一种通用机制,用于从包含信息(例如文件)或产生信息(例如随机数生成器)的任何值读取数据。如果对某个类型的值只需要调用该值的方法,则使用接口类型,而不是类型参数。io.Reader
易于阅读、高效且有效。不需要使用类型参数,通过调用read方法从值中读取数据。
例如,你可能会尝试将这里的第一个函数签名(仅使用接口类型)更改为第二个版本(使用类型参数)。
func ReadSome(r io.Reader) ([]byte, error)func ReadSome[T io.Reader](r T) ([]byte, error)
不要做出那种改变。省略type参数使函数更容易编写,更容易读取,并且执行时间可能相同。
最后一点值得强调。虽然可以用几种不同的方式实现泛型,而且随着时间的推移,实现也会发生变化和改进,但在许多情况下,Go 1.18中使用的实现将处理类型为类型参数的值,就像处理类型为接口类型的值一样。这意味着使用类型参数通常不会比使用接口类型快。因此,不要为了速度而从接口类型更改为类型参数,因为它可能不会运行得更快。
如果方法实现不同,不要使用类型参数
在决定是否使用类型参数或接口类型时,请考虑方法的实现。前面我们说过,如果一个方法的实现对于所有类型都是相同的,那么就使用一个类型参数。相反,如果每种类型的实现都不同,则使用接口类型并编写不同的方法实现,不要使用类型参数。
例如,从文件读取的实现与从随机数生成器读取的实现完全不同。这意味着我们应该编写两个不同的Read方法,并使用像io.Reader这样的接口类型。
在适当的地方使用反射
Go具有运行时反射。反射允许一种泛型编程,因为它允许你编写适用于任何类型的代码。
如果某些操作甚至必须支持没有方法的类型(不能使用接口类型),并且每个类型的操作都不同(不能使用类型参数),请使用反射。
encoding/json包就是一个例子。我们不想要求我们编码的每个类型都有MarshalJSON方法,所以我们不能使用接口类型。但对接口类型的编码与对结构类型的编码不同,因此我们不应该使用类型参数。相反,该包使用反射。代码不简单,但它有效。有关详细信息,请参阅源代码。
参考文章:
https://www.fansimao.com/1006524.html
相关文章:

go语言基础之泛型
1.泛型 泛型是一种独立于所使用的特定类型的编写代码的方法。使用泛型可以编写出适用于一组类型中的任何一种的函数和类型。 1.1 为什么需要泛型 func reverse(s []int) []int {l : len(s)r : make([]int, l)for i, e : range s {r[l-i-1] e}return r }fmt.Println(reverse…...

Vue.js 中子组件向父组件传值的方法
Vue.js 是一款流行的 JavaScript 前端框架,它提供了一套完整的工具和 API,使得开发者可以更加高效地构建交互式的 Web 应用程序。其中,组件化是 Vue.js 的一个核心概念,通过组件化可以将一个复杂的应用程序拆分成多个独立的部分&a…...

数据可视化 pycharts实现地理数据可视化(全球地图)
自用版 紧急整理一点可能要用的可视化代码,略粗糙 以后有机会再改 requirements: python3.6及以上pycharts1.9 数据格式为: 运行结果为: import pandas as pd from pyecharts.charts import Map, Timeline from pyecharts im…...

Mac下查看、配置和使用环境变量
Mac下查看、配置和使用环境变量 一:Mac怎么查看环境变量命令 printenv一:这个命令会一次性列出所有环境变量的键值对,输出格式为: VAR1value1 VAR2value2 ...二: 也可以通过给这个命令加上环境变量名参数࿰…...
虚拟机克隆的三种方式:全量克隆、快速全量克隆、链接克隆
虚拟机克隆的三种方式:全量克隆、快速全量克隆、链接克隆 快速全量克隆 特点:虚拟机启动快、拍平后数据独立 场景:快速发放独立的虚拟机,减少等待虚拟机部署完成时间,能够快速提供用户使用虚拟机。 实现方式:通过对…...

如何隐藏Selenium特征实现自动化网页采集
Selenium是一个流行的自动化网页测试工具,可以通过模拟用户在Chrome浏览器中的操作来完成网站的测试。然而,有些网站会检测浏览器是否由Selenium驱动,如果是,就会返回错误的结果或拒绝访问。为了避免这种情况,我们需要…...

springboot149智慧图书管理系统设计与实现
智慧图书管理系统的设计与实现 摘 要 如今社会上各行各业,都在用属于自己专用的软件来进行工作,互联网发展到这个时候,人们已经发现离不开了互联网。互联网的发展,离不开一些新的技术,而新技术的产生往往是为了解决现…...

3D词云图
工具库 tagcanvas.min.js vue3(框架其实无所谓,都可以) 实现 <script setup> import { onMounted, ref } from vue; import ./tagcanvas.min.js;const updateFlag ref(false);// 词云图初始化 const initWordCloud () > {let …...
opencv-python 视频读取: VideoCapture.get()参数详解
视频读取demo import cv2 from tqdm import tqdmvideoCapture cv2.VideoCapture(video_path) if not videoCapture.isOpened(): # 若视频文件读取失败,读取下一段视频print(视频打开失败!!!)print(video_path)return False total_frames int(videoCapture.get(c…...

python封装的.exe文件是如何在cmd中获取.xml路径的?
这段日子搞项目算法封装,愁死我。来回改了三遍,总算把相对路径、绝对路径,还有cmd给.exe传参的方式搞懂了。 主要是这个语句 workspace sys.argv[1] sys.argv[]的作用就是,在运行python文件的时候从外部输入参数往文件里面传递参数。 外部就…...

【学网攻】 第(18)节 -- 网络地址转换动态NAT
系列文章目录 目录 系列文章目录 文章目录 前言 一、NAT是什么? 二、实验 1.引入 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识及使用【学网攻】 第(3)节 -- 交换机配置聚合端口【学网攻】 第(4)节 -- 交换机划分Vlan【学网攻】…...

nosql数据库期末考试知识点总结
目录 1、什么是nosql数据库,它包括哪些 文档数据库 建数据 哪一种是最简单的 2、什么是文档数据库 3、创建mongodb时默认会建造三个数据库,是哪三个 4、mongodb支持的数据类型有哪些 5、它的常规语句有哪些 6、副本集和分片集有什么作用 复制 …...

字节大佬含泪吐血总结系列之 《计算机网络》(谢希仁)
字节大佬含泪吐血总结系列之 《计算机网络》(谢希仁) 原文地址:https://github.com/Snailclimb/JavaGuide 文章目录 字节大佬含泪吐血总结系列之 《计算机网络》(谢希仁)1. 计算机网络概述1.1. 基本术语1.2. 重要知识…...

多输入多输出 | Matlab实现PSO-LSTM粒子群优化长短期记忆神经网络多输入多输出预测
多输入多输出 | Matlab实现PSO-LSTM粒子群优化长短期记忆神经网络多输入多输出预测 目录 多输入多输出 | Matlab实现PSO-LSTM粒子群优化长短期记忆神经网络多输入多输出预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现PSO-LSTM粒子群优化长短期记忆神经网络…...

Ubuntu远程连接登录信息解读(ubuntu登录信息、远程登录信息)
文章目录 1. Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-100-generic aarch64)2. 三个链接是官方提供的文档、管理工具和技术支持3. System information as of Thu 01 Feb 2024 03:30:45 PM HKT4. System load: 1.16:系统负载指数5. Processes: 1096系统正在运…...
Oracle RMAN全备脚本(正式测试可行)
Oracle RMAN全备脚本 正式环境测试可行 请参考。 run{ allocate channel c1 type disk maxpiecesize20G; allocate channel c2 type disk maxpiecesize20G; allocate channel c3 type disk maxpiecesize20G; allocate channel c4 type disk maxpiecesize20G; crosscheck arch…...
【LUA】转载github自用二改模版——调节音量、显示七日天气、历史剪贴板、系统信息显示
二改模版笔记 自动重新加载HS function reloadConfig(files)doReload falsefor _,file in pairs(files) doif file:sub(-4) ".lua" thendoReload trueendendif doReload thenhs.reload()end end myWatcher hs.pathwatcher.new(os.getenv("HOME") .. &…...

Pymysql将爬取到的信息存储到数据库中
爬取平台为电影天堂 获取到的数据仅为测试学习而用 爬取内容为电影名和电影的下载地址 创建表时需要建立三个字段即可 import urllib.request import re import pymysqldef film_exists(film_name, film_link):"""判断插入的数据是否已经存在""&qu…...

linux中常用的命令
一:tree命令 (码字不易,关注一下吧,w~~w) 以树状形式查看指定目录内容。 tree --树状显示当前目录下的文件信息。 tree 目录 --树状显示指定目录下的文件信息。 注意: tree只能查看目录内容,不能…...

关闭idea之后,项目还在运行,端口被占用
今天在写项目的时候,中途安装了一个插件,而且插件显示需要重启idea,重启的时候项目正在运行,重启之后发现idea没有显示有项目正在运行,当我要开启项目的时候,发现无法开启,显示端口被占用了&…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...