Go-知识泛型

news2024/11/23 20:46:11

Go-知识泛型

  • 1. 认识泛型
    • 1.1 不使用泛型
    • 1.2 使用泛型
  • 2. 泛型的特点
    • 2.1 函数泛化
    • 2.2 类型泛化
  • 3. 类型约束
    • 3.1 类型集合
    • 3.2 interface 类型集合
      • 3.2.1 内置interface类型集合
      • 3.2.2 自定义interface类型集合
        • 3.2.2.1 任意类型元素
        • 3.2.2.2 近似类型元素
        • 3.2.2.3 联合类型元素
      • 3.2.3 interface类型集合运算
      • 3.2.4 基于操作的类型集合
  • 4. 小例子
    • 4.1 map.Keys 获取map的全部key
    • 4.2 Set
    • 4.3 排序 Sort
  • 5. 总结

泛型是程序设计语言的一种风格或范式,允许程序员在编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。
Java,C++等多种编程语言都支持泛型,Go语言从1.18版本起也开始支持泛型。

1. 认识泛型

1.1 不使用泛型

实现两个函数对map的value值进行累加,一个是int64类型的值,一个是float64类型的值

func SumInt64(m map[string]int64) int64 {
	var sum int64
	for _, v := range m {
		sum += v
	}
	return sum
}

func SumFloat64(m map[string]float64) float64 {
	var sum float64
	for _, v := range m {
		sum += v
	}
	return sum
}

接着使用两个函数

func TestSum(t *testing.T) {
	ints := map[string]int64{
		"one":   234,
		"two":   6755,
		"three": 78675,
	}
	floats := map[string]float64{
		"one":   123.456,
		"two":   7865.9658,
		"three": 87906.865,
	}
	t.Logf("sum ints : %v , floats : %v", SumInt64(ints), SumFloat64(floats))
}

执行如下:
在这里插入图片描述

实现同样的功能,就因为处理的数据类型不一样,就需要为每种类型编写类似的重复代码。

1.2 使用泛型

泛型函数,就是吧函数的参数和返回值“泛化”,使逻辑通用。 通用并不是对所有类型都使用,所以在声明泛型函数时,需要声明适用的“参数类型”(类型约束).

func SumValue[K comparable, V int64 | float64](m map[K]V) V {
	var sum V
	for _, v := range m {
		sum += v
	}
	return sum
}

SumValue泛型函数通过[K comparable, V int64|fload64]声明了两个类型参数K,V,供函数参数和返回值使用。
类型参数K的类型必须为comparable类型,因为被用作map的key值,在Go语言中map的key值必须是可比较的类型。
类型参数V的类型可以是int64或float64,在声明时使用|组合支持的类型。
普通参数m 表示一个泛化的map,相应的返回值也是一个泛化的类型。
使用:

func TestSumValue(t *testing.T) {
	ints := map[string]int64{
		"one":   234,
		"two":   6755,
		"three": 78675,
	}
	floats := map[string]float64{
		"one":   123.456,
		"two":   7865.9658,
		"three": 87906.865,
	}
	t.Logf("sum ints : %v , floats : %v", SumValue(ints), SumValue[string, float64](floats))
}

执行结果
在这里插入图片描述

在调用泛型函数的地方,编译器会将泛型函数实例化,即使用真实的类型来替换类型参数,在调用时有两种方式:

  • 隐式调用: 调用泛型函数时使用缺省类型参数,让编译器根据实际的参数进行推导。(SumValue(ints))
  • 显示调用: 调用泛型函数时显式的指明类型参数。(SumValuestring, float64)

需要注意的是,编译器之所以能够推导出参数类型是因为函数存在入参,编译器通过传入的变量和函数的参数声明可以推导出参数类型,但对于没有函数参数的泛型函数来说,编译器
无法进行推导,也就无法实例化泛型函数,此时必须显式地调用并指定类型参数。
比如:
在这里插入图片描述

就无法推导出来返回值的类型了
而且显式调用,必须全部指定,不能指定部分类型
在这里插入图片描述

2. 泛型的特点

泛型的英文表述为generic,即一般化,泛化,具体来讲就是函数和类型的泛化。在泛型被引入之前一个函数所能接收的参数类型及
所能处理的数据类型是确定的,但泛型函数却能接受和处理多种类型,对于类型也是同样的道理。
泛型主要包含三方面的内容:

  1. 函数的泛化
  2. 类型的泛化
  3. 接口的扩展

2.1 函数泛化

为了支持泛型,Go语言函数扩展为可以接受一个使用方括弧表示的类型参数;
func SumValue1[K comparable, V int64 | float64, T int64 | float64](m map[K]V) T
同普通的函数参数类似,类型参数中的每个参数也有一个类型,比如参数K,V的类型分别为 comparable(伴随泛型而引入的内置interface类型,表示可比类型)
和int64|float64(表示int64或float64)。
函数中的类型参数是可选的,没有类型参数的函数是传统的函数,带有类型参数的函数则是泛型函数。
即便Go 1.18 引入了泛型并且扩展了函数,但仍然保持兼容(在这里小小的谴责一下 python 和 scala )。
类型参数中的类型正式的名称是类型约束,用来约束类型的范围。 上面额函数可以接受多种类型的map为参数,考虑到所有的map的key的类型都是comparable类型,
那么只要一个map的值类型是int64或float64就能调用泛型函数。
比如: map[int]int64,map[int64]int64,map[float64]float64,...

2.2 类型泛化

泛型同样扩展了类型的表示方法,允许在创建自定义类型时也能接受一个使用方括弧表示的类型参数。
type arr[T int|int64] []T
声明arr类型,可以容纳int或者int64的切片。
这种声明中带有类型参数的类型被称为泛型类型。
泛型类型必须通过类型参数实例化后才可以使用。
var arrInt arr[int]
var arrInt64 arr[int64]
但是当实例化允许范围之外的类型时,会编译异常
在这里插入图片描述

在实例化一个泛型类型时,必须指定类型参数(编译器无法自动推导)

泛型类型同普通类型一样,同样允许定义方法,但是其类型必须带上类型参数:

func (a *arr[T]) add(x T) {
	*a = append(*a, x)
}
func TestArrAdd(t *testing.T) {
	var a arr[int]
	a.add(3)
	a.add(4)
	t.Logf("res : %v", a)
}

执行结果
在这里插入图片描述

未泛型类型定义方法时,必须指定类型参数,但参数名可以与泛型类型声明不同。

func (a *arr[X]) add1(x X) {
	*a = append(*a, x)
}

定义时使用T,但是在使用的时候,可以与声明时的名字不同。
如果方法体中并未使用类型参数,甚至可以使用_省略

func (a *arr[_]) add2(x _) {
	*a = append(*a, x)
}

但是不管是换个名字还是使用_并没有任何好处,反而降低了可读性。

3. 类型约束

无论函数和类型如何泛化,都需要类型参数来限定其泛化的范围,类型参数使用类型的集合表示范围。

3.1 类型集合

func SumValue1[K comparable, V int64 | float64, T int64 | float64](m map[K]V) T
该函数的类型参数中K的类型限定为comparable,V 的类型限定为int64或float64。
comparable是interface类型,int64|float64是组合类型,都代表一个类型集合,用于约束泛化的范围。
使用|来组合多个类型,从而形成一个类型集合。

3.2 interface 类型集合

在泛型特性被引入之前,interface仅表示一个方法集合,实现了该方法结合的所有类型都可认为实现了这个interface。
在泛型的设计中,对interface进行了扩展,interface将不在仅仅表示方法集合,它还可用于表示类型集合,同理,集合内所有类型都可认为实现了这个interface。

3.2.1 内置interface类型集合

comparable就是跟随泛型被引入的内置interface类型
在这里插入图片描述

除了comparable还有any。
comparable表示可比较类型的集合,仅能用于类型参数中。
any不仅在类型参数中表示任意类型的集合,还可以在非泛型场景中作为interface{}的别名使用。

3.2.2 自定义interface类型集合

除了内置的comparable和any两种类型可作为类型约束使用,用户还可以使用interface来定义类型集合。
在泛型之前,interface类型中仅允许包含方法或内嵌interface两种元素,引入泛型后,interface类型将允许使用另外三种元素以表示一个类型集合:

  1. 任意类型元素(如 int)
  2. 近似类型元素(使用表示法,如int)
  3. 联合类型元素(使用|表示法,如int|int64)

需要注意的是,如果interface类型中使用了这三种元素的任意一种,那么这个interface只能用于泛型的类型参数

3.2.2.1 任意类型元素

任意类型(包含interface类型)都可以出现在一个新的interface类型中,用于表示一个仅用于类型参数的集合

type Mint interface {
	int
}

func addMint[M Mint](m1, m2 M) M {
	return m1 + m2
}
func TestMint(t *testing.T) {
	t.Log(addMint(3, 4))
}

在这里插入图片描述

此时该interface表示的数据集仅包含一种类型,且仅能用于泛型场景中的类型参数中。
interface类型和自定义类型都可以出现在interface中从而表示一个类型集合。
可以定义新的泛型interface,不能使用泛型interface定义interface 方法集合
interface泛型用于interface
在这里插入图片描述

但是不能将interface泛型用于interface方法集合
在这里插入图片描述

但是如果显式的声明了泛型,那么就可以使用
在这里插入图片描述

并且该interface的方法集合也能像之前一样实现
在这里插入图片描述

因为在定义interface泛型的时候,限定是int,所以只有int类型才算是实现了方法
在这里插入图片描述

这样来看,使用interface泛型类型,可以限定什么样的方法算是实现。

如果将float64加入到interface泛型中,那么float64的方法也算是实现
在这里插入图片描述

3.2.2.2 近似类型元素

在使用 interface声明类型集合时,可以使用~<type>来制定一组类型,只要其底层类型为同一类型即包含在这个集合中。
因为在Go中,可以通过type取别名,而泛型又时通用这个含义。
比如创建一个string的泛型函数,但是因为使用了type对string取了别名,结果别名类型就无法使用泛型函数。
近似类型元素就是可以让type取了别名的类型也能使用
不使用近似类型
在这里插入图片描述

使用近似类型
在这里插入图片描述

只要底层类型相同,就能使用泛型函数

需要注意的是,~之后的类型必须是某个底层类型,换句话说,一个类型的底层类型不是自身就不能使用~
在这里插入图片描述

另外,interface 自身也不能用于定义近似类型集合,因为interface的底层类型并不确定。

3.2.2.3 联合类型元素

前面使用~定义的元素集合仅能包括一组底层类型一致的类型,又是可能需要联合多种类型,甚至联合多种底层类型一致的类型,此时可以用
|定义一个更宽泛的类型集合

type MInteger interface {
	int | int8 | int16 | int32 | int64
}

但是上述定义仅能支持底层类型,不支持别名
更进一步,可以把所有底层类型也包含进来

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

这样即使是别名类型,也能支持。

3.2.3 interface类型集合运算

前面使用interface声明类型集合时,均使用一行代码制定一个集合(一个子集),事实上interface支持按行制定多个自己和,这些自己和取交集形成最终的集合

type NewString interface {
	~string
	string
}

NewString的类型集合由两个子集组成,一个是所有底层类型为string的集合,另一个是string单一类型,两个子集取交集,最终的类型集合将只包含string单一类型
在这里插入图片描述

3.2.4 基于操作的类型集合

假设顶一个泛型函数来比较元素大小

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

func Equals[T Ordered](a, b T) bool {
	if a == b {
		return true
	}
	return false
}

定义的Ordered泛型类型是全部可以使用==比较的底层类型,并且包含别名类型,泛型函数限定了Ordered泛型类型。
copmarable和Ordered类似,范围更大。

4. 小例子

4.1 map.Keys 获取map的全部key

将map中的所有key取出来,然后存入切片中返回

func Keys[K comparable, V any](m map[K]V) []K {
	res := make([]K, 0, len(m))
	for k, _ := range m {
		res = append(res, k)
	}
	return res
}

类型参数K被用于声明了一个泛型的切片,然后把遍历到的key添加到切片中并返回。
任意的map都能使用Keys泛型函数

func TestKeys(t *testing.T) {
	t.Log(Keys(map[string]struct{}{
		"one":   {},
		"tow":   {},
		"three": {},
	}))
	t.Log(Keys(map[int]int{
		1: 1,
		2: 2,
		3: 3,
	}))
}

在这里插入图片描述

4.2 Set

Set可以存储一组不重复的数据,广泛用于需要去重的场景。很多编程语言比如Java,C++都提供了相应的实现,但是在Go语言中并没有Set类型。
有了泛型可以自己实现了

// 定义 Set
type Set[T comparable] map[T]struct{}

// 创建 Set
func MakeSet[T comparable]() Set[T] {
	return make(Set[T])
}

// 添加元素
func (s Set[T]) Add(k T) {
	s[k] = struct{}{}
}

// 删除元素
func (s Set[T]) Delete(k T) {
	delete(s, k)
}

// 判断是否包含
func (s Set[T]) Contains(k T) bool {
	_, ok := s[k]
	return ok
}

// 返回长度
func (s Set[T]) Len() int {
	return len(s)
}

// 遍历
func (s Set[T]) Iterate(f func(T)) {
	for k := range s {
		f(k)
	}
}

func TestSet(t *testing.T) {
	set := MakeSet[int]()
	set.Add(1)
	set.Add(2)
	set.Add(1)
	set.Add(3)
	t.Log(set.Len()) // 预期 3
	t.Log(set.Contains(2)) // 预期 true
	set.Delete(1) 
	t.Log(set.Len()) // 预期 2
	t.Log(set.Contains(1)) // 预期 false
	sum := 0
	set.Iterate(func(i int) {
		sum += i
	})
	t.Log(sum) // 预期 5
}

在这里插入图片描述

使用泛型实现的Set可适用于任意的可比较类型,行为与其他语言实现的Set基本类似,但是只能函数调用,不能使用下标操作访问元素。
上面实现的Set底层使用一个map实现,并不是线程安全的,还可以进一步使用自定义扩展

type SyncSet[C comparable] struct {
	l sync.RWMutex
	m map[C]struct{}
}

在读操作的时候,加读锁,在写操作的时候加写锁。

4.3 排序 Sort

要对切片中的元素进行排序,在标准库提供sort.Slice之前,每种类型的切片都需要实现sort.Interface接口中的三个方法
在这里插入图片描述

然后使用sort.Sort方法进行排序,即便后来标准库中引入了sort.Slice,但是使用时仍然需要提供一个排序函数。
使用泛型实现一个针对切片的通用排序函数

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

type orderSlice[O Ordered] []O

func (o orderSlice[O]) Len() int {
	return len(o)
}
func (o orderSlice[O]) Less(i, j int) bool {
	return o[i] < o[j]
}
func (o orderSlice[O]) Swap(i, j int) {
	o[i], o[j] = o[j], o[i]
}
func OrderSlice[O Ordered](s []O) {
	// s 被转换为 []Ordered 类型,也就是 orderSlice,然后排序
	// 因为 orderSlice已经实现了排序的接口,不需要额外实现了
	sort.Sort(orderSlice[O](s))
}
func TestOrder(t *testing.T) {
	is := []int{3, 4, 5, 1, 2}
	OrderSlice(is)
	t.Log(is)
	ss := []string{"he", "ww", "ss"}
	OrderSlice(ss)
	t.Log(ss)
}

在这里插入图片描述

使用泛型实现排序幻术也有一定的局限性,因为不容易处理复杂的符合类型,比如自定义的struct类型。

5. 总结

反形式衡量编程语言技术完备度的一个重要参考指标,但是也是一个比较争议的技术。
泛型的缺失导致开发者不得不编写重复的代码,或者编写相对通用但缺少类型安全的代码,甚至有些项目使用代码自动生成技术来摆脱编写
重复代码的烦恼,从这方面来看,Go确实需要泛型。
但是引入泛型也是有一定成本的,比如泛型的三个困局:

  • 没有泛型(C语言)会降低程序员的生产力,但不会增加语言的复杂度
  • 泛型会增加编译器的负担(C++),可能会编译出很多冗余的代码,进而拖慢编译时间
  • 泛型会降低运行时的性能(Java),避免编译大量冗余代码的后果是增加运行时的开销

Go语言早在1.17版本时就推出了试用版本,但在1.18中还是用了极大的篇幅说明泛型的种种风险。
https://golang.google.cn/doc/go1.18#generics

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

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

相关文章

腾讯云直播录制相关

直播录制的原理是什么&#xff1f; 对于一条直播流&#xff0c;一旦开启录制&#xff0c;音视频数据就会被旁路到录制系统。主播的手机推上来的每一帧数据&#xff0c;都会被录制系统追加写入到录制文件中。 一旦直播流中断&#xff0c;接入层会立刻通知录制服务器将正在写入的…

for深入学习作业

作业&#xff1a; 写一个程序判断1-100中9的个数 代码: #include<stdio.h> int main() {int i 9,sum0;for (i 9; i < 100; i) {if ((i % 10 9) || (i / 10 9)) {sum;}}printf("%d", sum);return 0; } //9 19 29 39 49 59 69 79 89 99 //91 92 93 94 …

LVGL设计汽车仪表盘(开源!!)

驾驶界面图 有图无真相&#xff0c;下面视频展示&#xff1a; 汽车仪表盘展示 资源已绑定&#xff0c;自行下载哦 关注我&#xff0c;后面出LVGL移植教程&#xff01;

在线拍卖|基于springBoot的在线拍卖系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 摘要 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单管理、留言板管理、系统管理&#xff0c;用户&am…

【动手学电机驱动】 TI InstaSPIN-FOC(1)电机驱动和控制测试平台

【动手学电机驱动】 TI InstaSPIN-FOC&#xff08;1&#xff09;电机驱动和控制测试平台 1. 本系列的资源需求1.1 电机驱动控制概况1.2 InstaSPIN-FOC 电机控制方案1.3 资源需求 2. 软件安装2.1 安装 CCS2.2 安装 MotorWare2.3 安装 ControlSUITE&#xff08;可选&#xff09; …

中科星图GVE(案例)——AI实现地块提取

目录 简介 函数 gve.Services.AI.plotExtraction(image) 代码 结果 知识星球 机器学习 简介 AI可以通过图像处理和机器学习算法实现地块提取。首先&#xff0c;AI可以对高分辨率遥感图像进行预处理&#xff0c;包括图像校正和去噪等处理。然后&#xff0c;AI可以使用图…

如何挑选Axure元件库? Axure原型赏析

在挑选Axure元件库时&#xff0c;可以从以下几个方面进行考虑和赏析&#xff0c;以确保选择到最适合项目需求的元件库&#xff1a; 一、元件库的全面性和丰富度 组件全面&#xff1a;优秀的Axure元件库应包含丰富的元件类型&#xff0c;以高效应对各种复杂业务流程的原型设计…

探索OAuth 2.0授权模式:全面解析与场景应用选择

文章目录 1. 什么是OAuth 2.0授权模式&#xff1f;2. 授权模式详解2.1 客户端凭证模式&#xff08;Client Credentials Grant&#xff09;2.2 授权码模式&#xff08;Authorization Code Grant&#xff09;2.3 简化模式/隐藏式&#xff08;Implicit Grant&#xff09;2.4 密码模…

解决:Ubuntu连接不上网络

今天莫名奇妙&#xff0c;我的ubuntu20.04断网了。检查了一下&#xff0c;使用的也是桥接模式&#xff0c;啥也没有变化。 然后我上Ubuntu16.04版本看了&#xff0c;那里又可以成功上网&#xff0c;所以&#xff0c;不是电脑的问题。 看了网上两个教程&#xff0c;解决了。 …

深入理解 Maven Profiles

前言 在现代软件开发中&#xff0c;项目通常需要部署到多种环境中&#xff0c;比如开发&#xff08;development&#xff09;、测试&#xff08;test&#xff09;和生产&#xff08;production&#xff09;。每种环境可能具有不同的配置需求。为了满足这种多样性&#xff0c;A…

还在找地图切片工具?这五款免费软件值得一试

地图切片&#xff08;Map Tiling&#xff09;是指将大型地图或影像数据按照一定的规则切割成多个较小的图块&#xff08;称为瓦片&#xff09;&#xff0c;并根据缩放级别和用户请求逐步加载这些瓦片&#xff0c;从而提升地图在网络或应用中的显示速度和效率。地图切片技术广泛…

ABAP SE37创建FUNCTION报错:函数的主程序不是以function-pool开头

问题&#xff1a;SE37在新建函数时&#xff0c;检查语法没有问题&#xff0c;但激活报错&#xff1a;函数"***"的主程序不是以function-pool开头; 原因&#xff1a;新建函数的函数组没有激活&#xff0c;可以通过se80或在SE37跳转进行激活 按一下路径 右键激活即可

变倍镜头参数详解

变倍镜头是一种重要的光学镜头&#xff0c;其参数对于了解镜头的性能和适用场景至关重要。以下是对变倍镜头参数的详细解释&#xff1a; 变焦倍数&#xff1a; 定义&#xff1a;变焦倍数是变倍镜头的一个关键参数&#xff0c;表示镜头最长焦距与最短焦距的比值。作用&#xff1…

Linux_kernel内核定时器14

一、内核定时器 1、内核定时器 使用方法&#xff1a; 2、系统时钟中断处理函数 1&#xff09;更新时间 2&#xff09;检查当前时间片是否耗尽 Linux操作系统是基于时间片轮询的&#xff0c;属于抢占式的内核 3&#xff09;jiffies 3、基本概念 1&#xff09;HZ HZ决定了1秒钟产…

ubuntu24 修改ip地址 ubuntu虚拟机修改静态ip

1. ubuntu 修改地址在/etc/netplan # 进入路径 cd /etc/netplan # 修改文件夹下的配置文件&#xff0c;我的是50-cloud-init.yaml. ye可能你得是20-cloud-init.yaml 2. 修改为&#xff1a; dhcp4: 改为false 192.168.164.50 是我自己分配的ip地址, /24 为固定写法&#xff…

jmeter输出性能测试报告(常见问题处理与处理)

问题1&#xff1a;报错 WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows R 意思是&#xff1a;报没有权限 处理&#xff1a; 操作非gui生成测试报告的方法 cmd界面进入到 jmeter的bin目录 jmeter –n –t -l -e –o …

高价跟低价的宠物空气净化器有什么区别?好用不贵净化器得这样选

有句俗话叫&#xff1a;“便宜电器等于白买”。这让咱们这些普通家庭想买却犹豫不决&#xff0c;毕竟顶级配置的电器价格昂贵&#xff0c;随便一件就得几千上万。而如果选择性价比高的&#xff0c;又担心效果不好&#xff0c;感觉像是在交智商税。但对于我们这些养宠物的家庭来…

NRF24L01无线通信模块学习 来自正点原子标准库

SPI通信 自动ACK&#xff0c;发送完数据后可等对方回你 NRF24L01介绍 时序介绍&#xff0c;数据位多字节传输时&#xff0c;低字节在前&#xff0c;高字节在后 工作模式介绍&#xff0c;当处于发送模式的时候&#xff0c;CE脚电平为1&#xff0c;延时10ms&#xff0c;CE脚电…

Nacos 2.2.x版本配置详解(鉴权版本)

Nacos 2.2.x 一、安装和鉴权二、项目中配置集成1.位置问题2.namespace命名空间3.username和password4.group5.file-extension6.prefix7.shared-configs 三、实战1.新建一个命名空间&#xff0c;取名wq-config&#xff08;这个你随意&#xff09;&#xff0c;会随机生成一个命名…

vulnhub-Kioptrix_Level_2_update靶机的测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、解决检测不到IP的问题 2、信息搜集 3、Getshell 4、提权 四、结论 一、测试环境 1、系统环境 渗透机&#xff1a;kali2021.1(192.168.202.134) 靶 机&#xff1a;Linux kioptrix.l…