golang slice总结

news2024/11/15 9:41:02

目录

概述

一、什么是slice

二、slice的声明

三、slice的初始化、创建

make方式创建

创建一个包含指定长度的切片

创建一个指定长度和容量的切片

创建一个空切片

创建一个长度和容量都为 0 的切片

new方式创建

短声明初始化切片

通过一个数组来创建切片

声明一个 nil 切片

四、nil切片和空切片的区别

空切片(Empty Slice)

nil 切片(nil Slice)

共同点

注意事项和易错点

五、常见导致panic的slice操作

索引越界

切片越界

小结

六、slice的相关操作

slice底层数组指针的获取方式

方式1、unsafe.SliceData

方式2、%p

方式3、reflect

slice的长度

slice的容量

slice的遍历

基于索引的遍历

使用range实现遍历

slice的截取

slice的追加

内置append函数

append不扩容的情况

append扩容的情况

append的返回值很重要

slice的拷贝

浅拷贝

深拷贝

内置copy函数

内置copy函数实现深拷贝

内置append函数实现深拷贝

七、slice在函数调用时是header传递

总结


概述

        本文主要是对slice的总结,从slice的基本概念、创建方式、常用操作,自己经历过的坑点易错点,以及append的机制,slice的header传递,尽可能用最简单的代码说明问题,本文的代码是基于golang 1.22.1版本验证。

一、什么是slice

        切片是用来描述底层数组的连续片段的数据结构,本身并不保存数组数据,而只是保存数组连续片段的描述信息,通过结构体里的array段指针引用底层数组,长度和容量属性限制底层数组的读写片段。

type slice struct { 
    array unsafe.Pointer 
    len int 
    cap int 
}

描述切片的数据结构由指向数组的指针、长度和容量三部分组成:

  • array 指针指向通过slice访问底层数组的的的第一个元素的指针,这里的被指位置不一定是数组的第一个元素,在对slice进行切片操作时,会移动这个指针。
  • len 长度代表切片当前包含的元素数量,len的大小不能超过cap的大小。
  • cap 容量表示底层数组从切片开始位置到数组末尾的元素个数,即当前切片的容量,cap总是大于或者等于len。

为什么指针的类型是 unsafe.Pointer 呢?

        这是因为在底层实现中,Go 语言的切片并不直接指向底层数组的数据,而是通过指针间接引用。unsafe.Pointer 类型是 Go 语言中用于处理底层指针的一种特殊类型,它可以指向任意类型的数据,包括未导出的结构体和数组。这种设计的目的是为了支持切片的动态扩展和收缩。当切片需要扩容时,底层的数组可能会被重新分配内存,新的数组可能位于内存的不同位置。使用指针可以更灵活地引用底层数组,而不受具体类型的限制。需要注意的是,由于 unsafe之所以叫unsafe包,是因为这里面的大部分操作绕开里golang的data type system的约束,直接操作内存单元,是比较危险,容易引起程序运行安全的操作,unsafe.Pointer 类型可以指向任意类型的数据,因此在使用时需要特别小心,避免造成内存访问越界或者类型不匹配的问题。通常情况下,开发者应该尽量避免直接使用 unsafe.Pointer,除非是在必要的情况下进行底层操作。

二、slice的声明

var slicename []T //slicevar slicename []T //slice

其中,slicename是切片的变量名,T 是切片中元素的类型 ,注意中括号里并没有数值n。

区分数组的声明

var arrayname [n]T //array

三、slice的初始化、创建

make方式创建

golang提供了内置的函数make创建

func make([]T, len, cap) []T
  • T 表示切片中元素的类型。
  • len 表示切片的长度,即切片中包含的元素个数。
  • cap 表示切片的容量,即底层数组的长度,这个参数可选

make函数调用后,它其实分配了一个T类型的数组, 并返回一个slice指向该数组

创建一个包含指定长度的切片

        当cap参数未指定时cap的值与len的值相同

s := make([]int, 5) // 创建一个包含 5 个整数的切片,初始值为对应类型的零值 
//[0 0 0 0 0] cap(s)= 5 len(s)= 5

创建一个指定长度和容量的切片

s := make([]int, 5, 10) // 创建一个包含 5 个整数的切片,并且底层数组的长度为 10 
//[0 0 0 0 0] cap(s)= 10 len(s)= 5

创建一个空切片

s :=make([]int, 0) // 创建一个空切片,长度为 0
//[] cap(s)= 0 len(s)= 0

创建一个长度和容量都为 0 的切片

s := make([]int, 0, 0) // 创建一个长度和容量都为 0 的切片 
//[] cap(s)= 0 len(s)= 0

new方式创建

         golang的内置函数new是用来分配内存,并返回指向该类型的零值的指针,而slice本身是一个包含指针、长度和容量的复合类型,使用new来创建slice不是很合适,这种方式还会涉及到unsafe包的使用,它绕过了Go的类型安全,并且需要手动管理内存,这很容易引发错误和内存泄漏。因此,在实际开发中,最好避免使用这种方法,而是使用make函数来创建slice,对于new不展开介绍

短声明初始化切片

s := []int{1, 2, 3, 4, 5} //[1 2 3 4 5] cap(s)= 5 len(s)= 5

通过一个数组来创建切片

arr := [5]int{1, 2, 3, 4, 5}
s := arr[:]
//[1 2 3 4 5]     cap(s)= 5  len(s)= 5

s1 := arr[:]
s2 := arr[0:]
s3 := arr[:5]
// 以上 s s1 s2 s3都是相等的 都是[1 2 3 4 5]

s4 := arr[:0]
//而s4是创建一个空的slice

声明一个 nil 切片

var s []int // 这是一个 nil 切片 //[] cap(s)= 0 len(s)= 0

四、nil切片和空切片的区别

        在 Go 中,空切片(empty slice)和 nil 切片(nil slice)是两种不同的概念,

它们有着共同点和某些不同的含义和用途:

空切片(Empty Slice)

  • 空切片是一个长度为 0 的切片,但其底层数组已经被分配了。
  • 可以通过 make 函数创建一个空切片,也可以通过切片字面量 []T{} 创建。
  • 空切片可以被使用,可以进行追加元素、遍历等操作,但不会引发 panic,因为底层数组已经被分配。
  • 通常用于表示一个空的集合或者没有元素的情况。
  • encoding/json编码时空切片会被编码为 JSON 数组 [],表示一个空的数组。当你有一个空的切片时,你期望它在 JSON 中被表示为一个空数组,这与空的集合或序列的语义一致。

示例:

// 使用 make 函数创建空切片
emptySlice := make([]int, 0)

// 使用切片字面量创建空切片
emptySlice2 := []int{}

fmt.Println("emptySlice ", emptySlice, " address of underlying array:", unsafe.SliceData(emptySlice), "\nlen=", len(emptySlice), "\ncap=", cap(emptySlice))
fmt.Println("emptySlice2 ", emptySlice2, " address of underlying array:", unsafe.SliceData(emptySlice2), "\nlen=", len(emptySlice2), "\ncap=", cap(emptySlice2))

 运行结果:

nil 切片(nil Slice)

  • nil 切片是一个指向 nil 的切片引用,即没有指向任何底层数组。
  • 一个 nil 切片的长度和容量都是 0,并且它的指针为 nil。
  • 可以将一个切片赋值为 nil,或者声明一个没有初始化的切片,它们都会被视为 nil 切片。
  • encoding/json编码时nil 切片会被编码为 JSON 的 null 值,表示不存在的值。当你有一个 nil 切片时,JSON 编码将把它解释为一个缺失值,这在某些情况下可能会引起问题,特别是在期望一个数组而不是 null 值的情况下

示例:

// 声明一个未初始化的切片,会被初始化为 nil 切片
var nilSlice []int
fmt.Println("nilSlice ", nilSlice, " address of underlying array:", unsafe.SliceData(nilSlice), "\nlen=", len(nilSlice), "\ncap=", cap(nilSlice))

运行结果:

共同点

        nil切片和空切片在进行如下操作:len(), cap(), append(), and for .. range loop时的行为和结果都是一致,都不会引发panic,都表示空的集合或序列(有的地方说对nil进行这些操作会引发panic,应该是跟go的版本不同)但是对nil slice的索引还是会抛出panic的,多一嘴:对nil map进行append操作会引发panic

注意事项和易错点

  • 使用空切片和 nil 切片的场景不同,需要根据实际情况进行选择。如果需要表示一个空的集合或者没有元素的情况,应该使用空切片;如果需要表示一个未初始化或者未指定的切片,可以使用 nil 切片。
  • 在函数返回值中,通常使用 nil 切片来表示某些特定条件下的空值。
  • 当需要传递一个切片作为函数参数,并且可能为空时,建议使用 nil 切片而不是空切片,以便明确表明该切片是未初始化的。
  • 在判断切片是否为空时,应该使用 len(slice) == 0 来判断,而不是 slice == nil,因为后者只能用于判断切片是否为 nil。
  • 对于 JSON 编码,需要注意的是:
  • 当你希望表示一个空的切片时,使用空切片 [];
  • 当你希望表示一个不存在的切片时,使用nil切片nil。

五、常见导致panic的slice操作

索引越界

        当访问slice的元素时,如果使用的索引超出了slice的有效范围(即小于0或大于等于slice的长度),程序会触发运行时panic,常见的是因为忽略slice或者array的起始索引是0,最后一个是len(slice)-1导致的。

这种panic提示一般是这样的:

panic: runtime error: index out of range ** with length **

fruits := []string{"apple", "orange", "grape"} 
fmt.Println("my favorite fruit is:", fruits[len(fruits)]) 
// panic: runtime error: index out of range [3] with length 3

注意索引index是小于等于len(slice)-1,而不是容量cap(slice)-1

numbers := make([]int, 3, 5) 
numbers[3] = 5 
//panic: runtime error: index out of range [3] with length 3

切片越界

        在通过切片操作(slice[low:high])创建新的slice时,如果low或high超出了原slice的界限,也会触发panic。

这样panic提示一般是这样的:

panic: runtime error: slice bounds out of range **

slice := []int{1, 2, 3} 
newSlice := slice[4:] 
// low超出原slice的界限,会触发panic

小结

        在某些情况下,对slice的底层数组进行不安全的操作(比如直接修改slice的结构体字段或使用unsafe包进行底层操作),可能会导致slice处于不一致的状态,进而引发panic。这种情况较为罕见,通常发生在底层编程或处理复杂数据结构的场景中,Go语言本身对slice操作有严格的类型检查和边界检查,因此大多数常见的错误在编译时就能被捕获。然而,由于运行时动态分配内存和扩展slice的特性,上述提到的几种情况仍可能导致运行时panic。为了避免这些问题,需要仔细检查slice的索引和切片操作,确保它们始终在有效范围内。

六、slice的相关操作

slice底层数组指针的获取方式

        为了调试方面、以及更好地说明slice的操作机制,有时候需要看到slice底层数组的位置

这里有三种方式可提供:

方式1、unsafe.SliceData

golang的标准库src\unsafe\unsafe.go里提供了一个很好的获取slice底层数组地址的函数

s0 := make([]int, 2, 3)
fmt.Println("\n\n", s0, "\n address :", unsafe.SliceData(s0), "\n len:", len(s0), "\n cap:", cap(s0))

输出结果:

方式2、%p

        slice是个包含底层数组指针,len,cap的值,是个值,和其他变量一样,获取slice的地址可以用&,同时%p在fmt文档中介绍,也有着特殊的作用,参考fmt的the documentation,可以看到

%p可以打印slice底层数组的第一个元素的地址,也就是slice底层数组的地址

示例:

s0 := make([]int, 2, 3)

fmt.Printf("the address of the 0th element of the slice s0 :%p\n", s0)

输出结果:

方式3、reflect

示例:

s0 := make([]int, 2, 3)

fmt.Printf("underlying array of slice addr=%#v\n", *((*reflect.SliceHeader)(unsafe.Pointer(&s0))))

输出结果:

slice的长度

内置的len函数

示例:

s0 := []int{10, 20, 30, 40, 50}
fmt.Println("length of s0:", len(s0))

运行结果:

slice的容量

内置的cap函数

示例:

s0 := []int{10, 20, 30, 40, 50}
fmt.Println("capcity of s0:", cap(s0))

运行结果:

slice的遍历

基于索引的遍历

s0 := []int{10, 20, 30, 40, 50}

for i := 0; i < len(s0); i++ {
	fmt.Println(i, s0[i])
}

使用range实现遍历

s0 := []int{10, 20, 30, 40, 50}

for index, value := range s0 {
	fmt.Println(index, value)
}

slice的截取

如图,通过对slice的截取运算,并不会重新分配内存片段,而是通过指针的移动实现,新旧切片指向底层数组的指针仍然指向同一个内存片段

示例:

var ptr *int // 声明一个int类型的指针 
fmt.Printf("\n pointer occupy size: %d bytes\n", unsafe.Sizeof(ptr)) 

s0 := []int{10, 20, 30, 40, 50} 
fmt.Println("\n\n", s0, "\n address :", unsafe.SliceData(s0), "\n len:", len(s0), "\n cap:", cap(s0)) 

s1 := s0[2:4] 
fmt.Println("\n\n", s1, "\n address :", unsafe.SliceData(s1), "\n len:", len(s1), "\n cap:", cap(s1))

运行结果:

slice的追加

内置append函数

func append(slice []Type, elems ...Type) []Type
  • slice 是Type类型的目标切片
  • elems 是要追加到切片末尾的元素,也是Type类型
  • append() 函数返回一个新的切片,依然是Type类型,其中包含了原始切片的所有元素以及追加的元素
append不扩容的情况

示例:

s0 := make([]int, 3, 5)

fmt.Println("before append:s0 slice:", s0, " address of of the underlying array:", unsafe.SliceData(s0), " len(s0):", len(s0), "cap(s0):", cap(s0))

s0 = append(s0, 1, 2)

fmt.Println("after append:s0 slice:", s0, " address of of the underlying array:", unsafe.SliceData(s0), " len(s0):", len(s0), "cap(s0):", cap(s0))

运行结果:

 追加前后underlying array没有变化,还是指向同一个底层数组

 

append扩容的情况

示例:

s0 := make([]int, 3, 5)

fmt.Println("before append:s0 slice:", s0, " address of of the underlying array:", unsafe.SliceData(s0), " len(s0)=", len(s0), "cap(s0)=", cap(s0))

s0 = append(s0, 1, 2, 3)

fmt.Println("after append:s0 slice:", s0, " address of of the underlying array:", unsafe.SliceData(s0), " len(s0)=", len(s0), "cap(s0)=", cap(s0))

运行结果:

append的返回值很重要

        一个slice传递给append函数,append会接受这个slice的一个副本,然后append内部对这个副本操作,注意这个副本包含了跟原始slice实参同一个底层数组的引用,

  • 如果不扩容,append返回这个副本的值,依然指向同原始slice实参同一个底层数组,容量没变,如果只是更改值,长度不变,如果是追加元素不扩容,则长度会变化,容量不变
  • 如果需要扩容,append函数内部会先算出一个合理的新的容量值,新建一个这个容量值的数组,然后将老元素拷贝到这个数组里,再将新元素追加到这个数组的后面,然后将计算后新的长度和新的容量作为新slice的len和cap,然后将这个新数组的地址作为slice的指针段,然后返回新的slice,之前的副本的slice里的指针被GC收回
  • 注意如果append内部在需要扩容的情况下,不返回这个新的slice,append的操作可能就被阉割隐匿了,是无效的;如果返回了,需要注意的是这个新的切片是否重新赋值给原始的切片变量,如果赋值给一个新的切片变量值,则是两个变量如下面的情况:
    slice := make([]init,3,5)
    
    slice = append(slice,1,2,3)
    //返回给调用者的同一个变量
    
    
    sliceNew := append(slice,1,2,3)
    //返回给新的切片变量
    
    //上面两种情况是两个不同的切片

append扩容依据及实现步骤:

  1. 检测切片容量:切片的扩容与不扩容的依据主要取决于当前切片的长度(len)和容量(cap)以及要追加的元素数量,当向切片追加元素时,如果追加后的长度(len(slice) + 追加元素数量)超过了当前容量(cap(slice)),切片就会发生扩容;如果追加后的长度不超过当前容量,切片就不会发生扩容,而是直接将新元素添加到切片的末尾,然后更新切片长度。
  2. 如果容量不够: 如果切片的容量不足以容纳新元素,append 将会执行扩容操作:
  3. 选择新容量:Go 将根据一定的策略选择一个新的容量。一般来说,新容量会比当前容量大,并且通常会选择一个相对较大的值,以减少后续的扩容次数,现有的选择新容量的依据是:原 slice 的容量小于 1024,则新 slice 的容量将扩大为原来的 2 倍;如果原 slice 的容量大于 1024,则新的 slice 的容量将扩大为原来的 1.25 倍;但不同版本可能会有不同的扩容策略,具体可参考src\runtime\slice.go里的growslice函数
  4. 分配新内存:一旦确定了新的容量,Go 将分配一个新的数组,其长度为新容量,然后将当前切片的所有元素复制到新的数组中,将新元素追加到新数组的末尾。
  5. 更新切片:最后,Go 更新原始切片的指向新数组的指针,并更新切片的长度,
  6. 返回更新后的切片。

注:

由于底层数组的更改,append 操作可能会导致原始切片的引用失效,因此通常情况下,我们会将结果分配给原始切片变量,以确保更新后的切片引用得到正确的处理。

slice =append(slice,10)

直接调用slice =append(slice,10)的时候也许会记得,将结果返回给原始变量,但是append在某个如change函数内部调用,而调用change函数时,往往忘记返回而造成错误,如

func addItemToSlice(s []int) {
	s = append(s, 10)
}

slice的拷贝

浅拷贝

        简单地将一个切片赋值给另一个变量并不会创建一个新的、独立的副本。相反,两个变量引用同一个底层数组,这意味着一个切片中的更改会反映在另一个切片中,

如果只是将src slice新赋值给一个新的dst变量,并不会创建一个新的,独立的副本,尽管两个变量都有这个各自的slice header,但是这两个值保存的内容是一样的,所以两个slice的array pointer是一样的,指向了一个底层数组,这种方式,修改一个 slice 的元素会影响到另一个 slice。

示例:

src := []int{10, 20, 30, 40, 50}
dst := src

fmt.Println("src slice:", src, " address of of the underlying array:", unsafe.SliceData(src), " len(src):", len(src), "cap(src):", cap(src))
fmt.Println("dst slice:", dst, " address of of the underlying array:", unsafe.SliceData(dst), " len(dst):", len(dst), "cap(dst):", cap(dst)

运行结果:

深拷贝

         深拷贝后源切片和目的切片的指针部分指向了独立的两个underlying array,修改其中一个切片,不会对另一个切片产生影响

内置copy函数

func copy(dst, src []Type) int

  • dst 是目标 slice

  • src 是源 slice

  • Type 是 slice 中元素的类型。

  • int是返回的实际复制的元素

        这个函数copy多少元素取决于源切片和目的切片长度的较小的值min(len(dst), len(src)),

如果目标切片为空或nil,则不会进行任何复制,需要注意的反而是src为nil的情况。

示例:

var src []int
dst := make([]int, len(src))

copy(dst, src)
fmt.Println(src == nil, dst == nil) // true false

fmt.Println("src slice:", src, " address of of the underlying array:", unsafe.SliceData(src), " len(src):", len(src), "cap(src):", cap(src))
fmt.Println("dst slice:", dst, " address of of the underlying array:", unsafe.SliceData(dst), " len(dst):", len(dst), "cap(dst):", cap(dst))

运行结果:

想实现src=nil的时候,拷贝后dst=nil

则可以加个判断

var src []int
dst := make([]int, len(src))

if src == nil {
    dst = nil
} else {
	copy(dst, src)
}
fmt.Println(src == nil, dst == nil) // true false
内置copy函数实现深拷贝

        我们想用内置copy函数实现深拷贝的话,需要新建目的切片的时候要用dst := make([]int, len(src))这种方式。

示例:

src := []int{10, 20, 30, 40, 50}
dst := make([]int, len(src))

copy(dst, src)

fmt.Println("src slice:", src, " address of of the underlying array:", unsafe.SliceData(src), " len(src):", len(src), "cap(src):", cap(src))
fmt.Println("dst slice:", dst, " address of of the underlying array:", unsafe.SliceData(dst), " len(dst):", len(dst), "cap(dst):", cap(dst))

运行结果:

内置append函数实现深拷贝

示例:

src := []int{10, 20, 30, 40, 50}
dst := append([]int{}, src...)

fmt.Println("src slice:", src, " address of of the underlying array:", unsafe.SliceData(src), " len(src):", len(src), "cap(src):", cap(src))
fmt.Println("dst slice:", dst, " address of of the underlying array:", unsafe.SliceData(dst), " len(dst):", len(dst), "cap(dst):", cap(dst))
}

运行结果

七、slice在函数调用时是header传递

      我们平时在开发中遇到的值传递和引用传递的特点是:

  • 值传递的特点是:当使用值传递时,函数会接收参数的副本,而不是参数本身,意味着函数内部对参数的修改不会影响到原始实参的值
  • 引用传递的特点是:函数会接收参数的引用(即内存地址),而不是参数的副本,意味着在函数内部对参数的修改会影响到原始数据

      严格意义上将golang的函数传递是值传递,参数在传递给函数时,会复制一份副本传递给函数,只不过有的是普通的变量的副本,有的是包含了指针的变量的副本,而golang语言内部的、自动的、隐式的、引用或者解引用的对调用者不可见,往往给调用者造成困扰。

在我们给slice的函数传递方式定义为值传递或者引用传递之前,不妨先看两个示例:

change示例:

func change(s []int) {
	s[0] = 1
	fmt.Println("in change ", s)
}

func main() {

	s0 := []int{10, 20, 30, 40, 50}

	fmt.Println("before change ", s0)
	change(s0)
	fmt.Println("after chang ", s0)

}

运行结果:

上述的change示例,似乎slice是引用传递的结果,然后再看一个示例:

addItemToSlice示例

func addItemToSlice(s []int) {
	s = append(s, 10)

}

func main() {

	s0 := []int{10, 20, 30, 40, 50}

	fmt.Println("before change ", s0)
	addItemToSlice(s0)
	fmt.Println("after chang ", s0)

}

运行结果:

 上述addItemToSlice示例,似乎是值传递的结果

分析:

slice的传递机制是这样的:前面说过slice是一个特殊的结构体

type slice struct {
    array unsafe.Pointer
    len int
    cap int
}

        change函数接收了slice s0的一个副本,这个change内部对副本里指向的底层数组里元素更改,然后长度和容量没变化,函数返回,副本回收,原始实参s0的底层数组与副本指向是一样的,所以更改有效了。

        addItemToSlice函数接收了slice s0的一个副本,这个addItemToSlice内部调用了append函数,并append函数对s0进行了扩容,新建了一个更大容量的slice,但是这个append并没有将这个新的slice f返回,而传递给addItemToSlice的slice副本,在函数调用完也就被GC回收了,所以更改是无效的。具体解释也可以参考本文章的append返回值很重要这一节对append函数的解释

总结

        slice的函数传递时究竟是值传递还是引用传递,都是受限制,从语义上很容易误导,不能只从语义上简单归类于是值传递或者引用传递,否则会造成值传递不更改Slice,引用传递更改slice的错误结论,关键的是append函数是否将新的slice作为返回值返回给调用者,并且注意是否将这个新的切片返回给原来的切片变量,还是返回给了一个新的切片变量。

        规避这种误导的最好方式slice在函数传递时是header传递或者是slice header传递,在channel的传递时亦是如此。

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

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

相关文章

Coursera上托福专项课程01:TOEFL Reading and Listening Sections Skills Mastery 学习笔记

TOEFL Reading and Listening Sections Skills Mastery Course Certificate 本文是学习 https://www.coursera.org/learn/toefl-reading-listening-sections-skills-mastery 这门课的笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 TOEFL Reading and Listening …

【微服务】面试题(一)

最近进行了一些面试&#xff0c;这几个问题分享给大家 一、分别介绍一下微服务、分布式以及两者的区别 微服务&#xff08;Microservices&#xff09;和分布式系统&#xff08;Distributed Systems&#xff09;是两种不同的软件架构风格&#xff0c;虽然它们之间有些重叠&#…

SV学习笔记(五)

文章目录 线程的使用程序和模块什么是线程线程的概念澄清 线程的控制fork并行线程语句块fork…joinfork…join_any等待所有衍生线程停止单个线程停止多个线程停止被多次调用的任务 线程的通信写在前面event事件通知的需求semaphore旗语mailbox信箱三种通信的比较和应用 参考资料…

探索数据结构:特殊的双向队列

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;数据结构与算法 贝蒂的主页&#xff1a;Betty’s blog 1. 双向队列的定义 **双向队列(double‑ended queue)**是一种特殊的队列…

[羊城杯 2020]Easyphp2 ---不会编程的崽

摆烂一周了&#xff0c;继续更&#xff01;&#xff01;题目还是简单哦。 提示明显要我们修改数据包&#xff0c;第一反应是修改referer。试了一下不太对。url很可能存在文件包含 使用伪协议读取一下源码吧。它过滤了base64关键字。尝试url编码绕过&#xff0c;这里可以使用二…

实景三维技术在推进城市全域数字化转型的作用

4月2日&#xff0c;国家数据局发布《深化智慧城市发展推进城市全域数字化转型的指导意见&#xff08;征求意见稿&#xff09;》&#xff08;下称&#xff1a;《指导意见》&#xff09;&#xff0c;向社会公开征求意见。 《指导意见》作为推进城市数字化转型的重要文件&#xf…

11、子串-滑动窗口最大值

题解&#xff1a; 双端队列是一种特殊的队列&#xff0c;允许你在队列的两端进行插入和删除操作。在滑动窗口问题中&#xff0c;我们使用它来存储可能是当前窗口最大值的元素的索引。 维护队列的顺序&#xff1a; 当新元素进入窗口时&#xff0c;我们将它与队列尾部的元素进…

echarts 毕节区县地图 包含百管委、高新区 (手扣)

百度网盘 链接&#xff1a;https://pan.baidu.com/s/14yiReP8HT_bNCGMOBajexg 提取码&#xff1a;isqi

MQ简介和面试题

一&#xff0c;什么是MQ MQ全称是Mwessage Queue(消息队列)&#xff0c;是在消息传输过程中保存消息的容器&#xff0c;多用于分布式系统之间进行通信&#xff0c;解耦和低耦合性 二&#xff0c;常见的MQ产品 RebbitMQ,RocketMQ, ActiveMQ, Kafka, ZeroMQ, MetaMQ 其中我们…

(学习日记)2024.04.06:UCOSIII第三十四节:互斥量函数接口讲解

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

STM32CubeMX+MDK通过I2S接口进行音频输入输出(全双工读写一个DMA回调)

一、前言 目前有一个关于通过STM32F411CEUx的I2S总线接口控制SSS1700芯片进行音频输入输出的研究。 SSS1700 是具有片上振荡器的 3S 高度集成的USB音频控制器芯片 。 SSS1700 功能支持96 KHz 24 位采样率&#xff0c;带外部音频编解码器&#xff08;24 位/96KHz I2S 输入和输出…

工具推荐-针对Nacos利器-NacosExploitGUI_v4.0

Nacos是由阿里所开发的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 工具简介 集成Nacos的各种poc Nacos控制台默认口令漏洞(nacos,nacos)Nacostoken.secret.key默认配置(QVD-2023-6271)Nacos-clientYaml反序列化漏洞Nacos Jraft Hessian反序列化漏洞…

【Hadoop技术框架-MapReduce和Yarn的详细描述和部署】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;今天的内容主要是Hadoop的后两个组件&#xff1a;MapReduce和yarn的相关内容。同时还有Hadoop的完整流程。希望对大家有所帮助。感谢大家关注点赞。 &#x1f49e;&#x1f49e;前路漫漫&…

使用GDAL进行简单的坐标系转换

使用GDAL进行简单的坐标系转换 使用python GDAL进行简单的坐标系转换&#xff0c;暂时不考虑不同基准坐标系转换的精度问题。 安装环境 使用UbuntuAnaconda python 环境 conda install gdal 定义坐标系 from osgeo import gdal from osgeo import osrsrs_wgs84 osr.Spati…

ICP配准算法

配准算法 问题定义ICP(point to point)算法思想步骤分解point to point和point to plane的区别ICP配准算法的标准流程NDT 本篇将介绍配准算法&#xff0c;将介绍ICP(point to point)、ICP(point to plane)和NDT算法。其中ICP有两种&#xff0c;point to point表示通过构建点与点…

力扣347. 前 K 个高频元素

思路&#xff1a;记录元素出现的次数用map&#xff1b; 要维护前k个元素&#xff0c;不至于把所有元素都排序再取前k个&#xff0c;而是新建一个堆&#xff0c;用小根堆存放前k个最大的数。 为什么是小根堆&#xff1f;因为堆每次出数据时只出堆顶&#xff0c;每次把当前最小的…

文旅元宇宙|“元宇宙+”全面赋能智慧文旅场景建设

元宇宙作为下一代互联网入口&#xff0c;正在潜移默化的改变着人生的生活方式&#xff0c;不断催生新业态&#xff0c;带给人们前所未有的体验。元宇宙概念的崛起&#xff0c;正以其独特的魅力&#xff0c;引领着一场全新的智慧文旅革命。元宇宙&#xff0c;这个融合了虚拟现实…

物联网实战--入门篇之(九)安卓QT--开发框架

目录 一、QT简介 二、开发环境 三、编码风格 四、设计框架 五、总结 一、QT简介 QT是一款以C为基础的开发工具&#xff0c;已经包含了很多常用的库&#xff0c;除了基本的GUI以外&#xff0c;还有网络、数据库、多媒体、进程通信、串口、蓝牙等常用库&#xff0c;开发起来…

Vue3_2024_7天【回顾上篇watch常见的后两种场景】

随笔&#xff1a;这年头工作不好找咯&#xff0c;大家有学历提升的赶快了&#xff0c;还有外出人多注意身体&#xff0c;没错我在深圳这边阳了&#xff0c;真的绝啊&#xff0c;最尴尬的还给朋友传染了&#xff01;&#xff01;&#xff01; 之前三种的监听情况&#xff0c;监听…

Godot 4 教程《勇者传说》依赖注入 学习笔记(0):环境配置

文章目录 前言相关地址环境配置初始化环境配置文件夹结构代码结构代码运行 资源文件导入像素风格窗口环境设置背景设置,Tileap使用自动TileMap 人物场景动画节点添加站立节点添加移动动画添加 通过依赖注入获取Godot的全局属性项目声明 当前项目逻辑讲解角色下降添加代码位置问…