Go语言Slice切片底层

Go语言(Golang)中切片(slice)的相关知识、包括切片与数组的关系、底层结构、扩容机制、以及切片在函数传递、截取、增删元素、拷贝等操作中的特性。并给出了相关代码示例和一道面试题。关键要点包括:

  1. 数组特性:Go语言中数组是一个值、数组变量表示整个数组、不同于C语言中指向第一个元素的指针。传递数组到函数或拷贝数组时、会有不同的内存地址和数据独立性表现。

  2. 切片定义:切片是建立在Go数组之上的抽象类型、其底层结构包含指向底层数组的指针、长度和容量。

  3. 切片扩容

  • 新切片长度大于旧切片容量两倍时、新容量为新长度

  • 旧容量小于256时、新容量为旧容量两倍

  • 否则按1.25倍增速扩容、还会进行内存对齐。

  1. 函数传递:切片通过函数传递时、传的是切片结构、在函数内改变切片可能影响函数外的切片、取决于底层数组是否变化。

  2. 切片操作

  • 通过 “:” 作截取切片、新切片与原切片共享底层数组

  • 删除元素可通过拼接切片实现

  • 新增元素使用append操作

  • 深度拷贝可使用copy函数。

1.切片是什么

在Go语言中 切片(slice)是建立在数组之上的一种抽象类型。切片提供了一种更灵活的方式来处理数组、它允许动态地改变数组的大小、并且可以方便地进行切片操作。理解切片之前、我们需要先了解数组。

Go的数组 在Go语言中、数组的长度是类型的一部分、这意味着数组的长度是固定的、不能改变。

数组的传递和拷贝行为与C语言不同、Go语言中的数组是值类型、传递数组时会进行值拷贝。

1.1 示例一:

将数组传递到函数中 数组的地址不一样

package mainimport "fmt"func main() {array := [3]int{1, 2, 3}// 数组传递到函数中test(array)fmt.Printf("array 外: %p\n", &array)
}func test(array [3]int) {fmt.Printf("array 内: %p\n", &array)}
  • 由于数组是值类型、传递数组时会进行值拷贝、因此在test函数中打印的地址与main函数中打印的地址不同。

1.2 值拷贝

值拷贝意味着拷贝的是变量的内容、而不是内存地址。因此拷贝出来的变量有自己的独立副本、内容相同、但它们存储在不同的内存地址中。

Go 语言中的切片(slice)是动态扩容的。当你向切片中添加元素时、Go 会自动管理切片的大小、并在需要时进行扩容。

具体行为:

  • 初始容量:当你创建一个切片时、Go 会为切片分配一个初始容量。如果你添加的元素超过了切片当前的容量Go 会自动扩容。

  • 扩容规则:Go 会根据当前切片的容量自动扩展切片的大小、通常是原来容量的2倍。扩容后、切片的长度和容量都会增加。

  • 内部机制:当切片扩容时、Go 会为新切片分配新的底层数组、并将原数组的元素拷贝到新数组中。这是一个代价比较高的操作、尤其是在需要多次扩容的情况下

2.底层结构

type slice struct {// 底层数组指针(或者说是指向一块连续内存空间的起点)array unsafe.Pointer// 长度len  int// 容量cap  int
}

在这个结构中:

  • array:指向底层数组的指针、或者说是指向一块连续内存空间的起点。

  • len:切片的长度、即切片中实际包含的元素数量。

  • cap:切片的容量、即切片可以包含的元素的最大数量,不包括可能的扩展空间。

切片扩容

  1. 计算目标容量

    1. case1: 如果新切片的长度大于旧切片容量的两倍、则新切片容量就为新切片的长度。

    2. case2:

      • 如果旧切片的容量小于256、那么新切片的容量就是旧切片的容量的两倍。

      • 反之需要用旧切片容量按照1.25倍的增速、直到大于新切片长度。

    3. 为了更平滑的过渡、每次扩大1.25倍、还会加上3/4 * 256

    4. 进行内存对齐、需要按照Go内存管理的级别去对齐内存、最终容量以这个为准。

3.切片问题

3.1 切片通过函数传的是什么

package mainimport ("fmt""reflect""unsafe"
)func main() {s := make([]int, 5, 10)PrintSliceStruct(&s)test(s)
}func test(s []int) {PrintSliceStruct(&s)
}func PrintSliceStruct(s *[]int) {// 代码 将slice 转换成 reflect.SliceHeaderss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 查看slice的结构fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}

控制台输出:

slice struct: &{Data:1374389649568 Len:5 Cap:10}, slice is &[0 0 0 0 0]
slice struct: &{Data:1374389649568 Len:5 Cap:10}, slice is &[0 0 0 0 0]
  • 切片的定义:你创建了一个切片 s、通过 make([]int, 5, 10) 创建了一个长度为 5、容量为 10 的切片。

  • 也就是说它初始化了一个包含 5 个元素且最大容量为 10 的底层数组

总结:

  • 切片传递:当切片通过参数传递到函数时、传递的是切片的值、但切片内部的底层数组地址(指针)并没有被复制。

  • PrintSliceStruct 打印的结构:无论是在 main 函数还是 test 函数中、切片的底层数组地址、长度和容量都是相同的、因为底层数组是共享的。

为什么输出相同:

输出显示的 Data 地址、Len 和 Cap 是一致的、因为 test(s) 传递的是切片的值(即切片的结构),但切片中的指针指向相同的底层数组。所以无论是传递给 PrintSliceStruct 函数的 s、还是 test 函数中的 s、它们指向的是同一个底层数组、并且它们的长度和容量保持一致

3.2 在函数里面改变切片 函数外的切片会被影响吗

package mainimport ("fmt""reflect""unsafe"
)func main() {s := make([]int, 5) // 创建一个长度为 5 的切片case1(s)             // 调用 case1 函数case2(s)             // 调用 case2 函数PrintSliceStruct(&s) // 打印切片结构
}// 底层数组不变
func case1(s []int) {s[1] = 1 // 修改切片中的元素PrintSliceStruct(&s) // 打印切片结构
}// 底层数组变化
func case2(s []int) {s = append(s, 0) // 扩容切片s[1] = 1         // 修改切片中的元素PrintSliceStruct(&s) // 打印切片结构
}func PrintSliceStruct(s *[]int) {// 将切片转换成 reflect.SliceHeaderss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底层结构fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

关键点:

  • case1 函数:

  • case1 中、你传入一个长度为 5 的切片 s、并修改切片中的元素。

  • 切片在函数内的操作是对原切片的修改、因此底层数组没有发生变化、切片的容量、长度仍然相同。

  • 打印的 slice struct 的 Data、LenCap 字段显示的是切片的原始底层数据结构。

  • case2 函数:

  • case2 中、你向切片添加一个元素(通过 append 操作)、这将可能导致切片的底层数组扩容。

  • 因为 append 操作在超出当前容量时会触发扩容、所以 s 的底层数组会发生变化、容量也可能增加。

  • case2 中、s 被赋值为 append(s, 0)、这将导致原有切片 s 的底层数组被扩展、并且一个新的数组被分配给 s(s 指向的是新的底层数组)

  • 打印时会看到 slice struct 中的 Data 指向一个新的地址、表示底层数组已经发生了变化。

  • append(s, 0) 函数会检查切片 s 是否有足够的容量来存储新的元素。如果切片的容量不足、append 函数会分配一个新的更大的数组、并复制旧数组的内容到新数组中、然后将新元素添加到新数组的末尾、并更新切片的指针以指向包含新元素的新底层数组。

3.3 截取切片

package mainimport ("fmt""reflect""unsafe"
)func main() {s := make([]int, 5)  // 创建一个长度为 5 的切片,默认初始化为 [0 0 0 0 0]case1(s)  // 调用 case1,修改切片内容case2(s)  // 调用 case2,修改切片并改变底层数组case3(s)  // 调用 case3,截取切片并改变其长度case4(s)  // 调用 case4,截取切片的部分元素PrintSliceStruct(&s)  // 最后打印切片的底层结构
}// case1:修改切片元素、底层数组不变
func case1(s []int) {s[1] = 1  // 修改切片中的第二个元素,s[1] = 1PrintSliceStruct(&s)  // 打印修改后的切片底层结构
}// case2:重新赋值为新的切片
func case2(s []int) {s = s[:]  // 这里实际上并没有改变切片的内容、它只是重新赋值为原切片的一个新引用。PrintSliceStruct(&s)  // 打印新的切片底层结构
}// case3:截取切片、底层数组不变
func case3(s []int) {s = s[:len(s)-1]  // 截取切片、去掉最后一个元素、新的切片长度为 4PrintSliceStruct(&s)  // 打印截取后的切片底层结构
}// case4:截取切片的部分元素、底层数组不变
func case4(s []int) {sl := s[1:2]  // 截取 s[1:2],即取出切片中索引为 1 的元素PrintSliceStruct(&sl)  // 打印截取后的新切片底层结构
}// PrintSliceStruct 打印切片的底层结构
func PrintSliceStruct(s *[]int) {// 将切片的指针转换为 reflect.SliceHeader 结构体,通过 unsafe.Pointer 获取底层数据ss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底层数据结构、包括:指向底层数组的内存地址、切片的长度和容量fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

总结:

  • 切片操作的影响

  • 修改切片元素不会改变底层数组的地址。

  • 重新赋值切片并没有改变底层数组、除非涉及扩容(例如 append)。

  • 截取切片时、底层数组不变、切片的长度和容量可能会变化。

3.4 删除元素

package mainimport ("fmt""reflect""unsafe"
)func main() {// 创建一个包含5个整数的切片s := []int{0, 1, 2, 3, 4}// 打印切片的底层结构PrintSliceStruct(&s)// 删除切片中的最后一个元素,正确的做法是通过切片截取_ = s[4]                      // 访问并丢弃切片中的最后一个元素s1 := append(s[:1], s[2:]...) // 删除元素 s[1],//s[:1](即切片 [0])和 s[2:](即切片 [2, 3, 4])拼接在一起。// 打印修改后的切片fmt.Println(s)  // [0 2 3 4 4]fmt.Println(s1) // [0, 2, 3, 4]// 打印切片底层结构PrintSliceStruct(&s)PrintSliceStruct(&s1)// 访问切片的元素s = s[:4] // 截取切片、删除最后一个元素_ = s[3]  // 访问切片中的最后一个元素(索引为3的元素)
}// 打印切片的底层结构
func PrintSliceStruct(s *[]int) {// 将切片转换为 reflect.SliceHeaderss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底层结构fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}
  • 在 Go 中、切片操作需要特别注意切片的索引和截取。访问切片中的元素时要小心类型不匹配(例如不能将一个切片元素赋值给切片)。

控制台输出:

slice struct: &{Data:1374390755328 Len:5 Cap:5}, slice is [0 1 2 3 4]
[0 2 3 4 4]
[0 2 3 4]
slice struct: &{Data:1374390755328 Len:5 Cap:5}, slice is [0 2 3 4 4]
slice struct: &{Data:1374390755328 Len:4 Cap:5}, slice is [0 2 3 4]

打印原切片 s 时、它仍然指向原底层数组(长度为 5、容量为 5)、而且由于 s[4] 在内存中并没有被移除、原底层数组中的最后一个元素 4 被保留、因此 s 显示为 [0 2 3 4 4]。

简而言之s 显示为 [0 2 3 4, 4] 是因为原始切片的底层数组并没有被修改、而 append 操作生成了一个新的切片(s1)并分配了新的底层数组。所以s 中仍然包含原数组中的所有元素、最后一个 4 仍然存在。

为什么 s变成了 [0, 2, 3, 4, 4]

  • append 会根据切片的容量决定是否会使用原来的底层数组。如果原切片的容量足够大、append 就会直接修改原切片。

  • 在这段代码中、由于原始切片 s 的容量足够大(原始切片 s 的容量为 5)、append 仍然修改了原始切片 s 的内容。切片的 s 和 s1 都指向相同的底层数组。

重点:

  • 原切片 s 的容量没有改变:s 底层的数组仍然包含原来 s 的所有元素。

  • append 没有重新分配新的底层数组:由于原切片的容量足够、所以 append 在修改原底层数组时、并没有创建新的底层数组。因此原始切片 s 中的 4 仍然存在。

  • 修改后 s 中的元素为 [0, 2, 3, 4, 4]:虽然你删除了 s[1] 这个元素、但 append 使得 s 的底层数组没有发生变化,因此原始的 4 元素仍然保留在切片中。

结论:

append 操作有时会创建新的底层数组(如果容量不足)、但如果原切片的容量足够、append 直接修改原切片的底层数组。在这种情况下原切片 s 会保持原来的容量和数据、导致 s 显示为 [0, 2, 3, 4, 4],即最后一个 4 保留下来了。

3.5 新增元素

package mainimport ("fmt""reflect""unsafe"
)func main() {case1()case2()case3()
}// case1 函数展示了使用 append 在切片末尾添加元素的行为
func case1() {// 创建一个长度为 3,容量为 3 的切片s1 := make([]int, 3, 3)// 向切片添加一个元素 1,append 返回一个新的切片s1 = append(s1, 1)// 打印切片的底层结构PrintSliceStruct(&s1) //1
}// case2 函数展示了在原切片上使用 append 并打印切片结构的变化
func case2() {// 创建一个长度为 3,容量为 4 的切片s1 := make([]int, 3, 4)// 向切片添加一个元素 1,append 会扩展切片的长度s2 := append(s1, 1)// 打印原切片 s1 和新切片 s2 的底层结构PrintSliceStruct(&s1)//2PrintSliceStruct(&s2)//3
}// case3 函数与 case2 类似,展示了切片长度、容量变化的行为
func case3() {// 创建一个长度为 3,容量为 3 的切片s1 := make([]int, 3, 3)// 向切片添加一个元素 1,append 返回一个新的切片s2 := append(s1, 1)// 打印原切片 s1 和新切片 s2 的底层结构PrintSliceStruct(&s1)//4PrintSliceStruct(&s2)//5
}// PrintSliceStruct 打印切片的底层结构
func PrintSliceStruct(s *[]int) {// 使用 reflect 和 unsafe 包将切片转换成 reflect.SliceHeader 结构体ss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底层结构fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

控制台输出

slice struct: &{Data:1374390755328 Len:4 Cap:6}, slice is [0 0 0 1]
slice struct: &{Data:1374390779936 Len:3 Cap:4}, slice is [0 0 0]
slice struct: &{Data:1374390779936 Len:4 Cap:4}, slice is [0 0 0 1]
slice struct: &{Data:1374390673552 Len:3 Cap:3}, slice is [0 0 0]
slice struct: &{Data:1374390755376 Len:4 Cap:6}, slice is [0 0 0 1]

case1:

  • 使用 make([]int, 3, 3) 创建了一个长度为 3,容量为 3 的切片 s1,初始内容为 [0, 0, 0]。

  • 然后append(s1, 1) 会将元素 1 添加到切片的末尾、生成一个新的切片并返回。由于容量是 3、append 会自动扩容新的切片长度是 4

  • 最后调用 PrintSliceStruct 打印 s1 切片的底层结构。

case2:

  • make([]int, 3, 4) 创建了一个长度为 3、容量为 4 的切片 s1

  • 使用 append(s1, 1) 向切片添加元素 1、生成一个新切片 s2。由于 s1 的容量已足够、不会触发扩容。

  • 通过 PrintSliceStruct 打印切片 s1 和 s2 的底层结构。

s3 和 s4 参考上面

3.6 操作原来切片会影响新的切片吗

在 Go 中、切片是引用类型,这意味着当你创建一个新切片时,它实际上可能会指向同一个底层数组。因此,如果你修改了原切片(比如通过 append 或其他操作),它可能会影响到新切片,特别是在底层数组没有被重新分配的情况下。

切片和底层数组

  • 切片(slice)是一个非常轻量级的抽象,它包含了三个部分:指向底层数组的指针、切片的长度和切片的容量。

  • 当你对切片进行操作时(例如使用 append、copy 或直接修改),这些操作通常会影响到底层数组。

  • 如果多个切片引用同一个底层数组,改变其中一个切片的内容可能会影响到其他切片,尤其是在没有扩容时。

append操作

  • 当使用 append 函数时、如果切片的容量足够、append 会直接在原底层数组上操作、不会创建新的底层数组。在这种情况下、修改原切片的内容会影响到新切片、因为它们指向相同的底层数组。

  • 如果容量不足、append 会创建一个新的底层数组、并将原切片的数据复制到新数组中、这时原切片和新切片就指向不同的底层数组了、它们互不影响。

例子:

没有扩容:

s1 := []int{1, 2, 3}
s2 := s1      // s2 指向与 s1 相同的底层数组
s1[0] = 100   // 修改 s1 中的第一个元素
fmt.Println(s1) // 输出 [100, 2, 3]
fmt.Println(s2) // 输出 [100, 2, 3]

这里s1 和 s2 指向相同的底层数组,因此修改 s1 会影响到 s2。

扩容时:

s1 := []int{1, 2, 3}
s2 := append(s1, 4)  // s2 创建了新的底层数组
s1[0] = 100          // 修改 s1 中的第一个元素
fmt.Println(s1) // 输出 [100, 2, 3]
fmt.Println(s2) // 输出 [1、2、3、 4]

这里s2 创建了一个新的底层数组,因此修改 s1 不会影响 s2。

结论:

  • 修改原切片会影响新切片:如果新切片是通过引用原切片的底层数组创建的(没有触发扩容)、修改原切片的内容会影响到新切片。

  • 扩容时不影响:如果 append 或其他操作导致了扩容、原切片和新切片就会指向不同的底层数组、互不影响。

4.字节面试题

下面这道题的输出是什么

package mainimport "fmt"func main() {// 定义一个匿名函数 doAppend,用来执行 append 操作并打印切片的长度和容量doAppend := func(s []int) {s = append(s, 1)         // 向切片中添加元素 1printLengthAndCapacity(s) // 打印切片的长度和容量}// 创建一个长度为 8,容量为 8 的切片 ss := make([]int, 8, 8)// 传递 s 的前 4 个元素(即 s[:4])到 doAppenddoAppend(s[:4])  // 只传递前4个元素的切片// 打印原始切片 s 的长度和容量printLengthAndCapacity(s)// 传递整个切片 s 到 doAppenddoAppend(s)// 打印原始切片 s 的长度和容量printLengthAndCapacity(s)
}func printLengthAndCapacity(s []int) {fmt.Println() fmt.Printf("len=%d cap=%d \n", len(s), cap(s))  // 打印切片的长度和容量
}
  • len(s) 是切片的长度。

  • cap(s) 是切片的容量、表示切片底层数组的大小。

len=5 cap=8 len=8 cap=8 len=9 cap=16 len=8 cap=8

调用 doAppend(s[:4]):

  • s[:4] 是 s 切片的前 4 个元素、创建一个新的切片 [0, 0, 0, 0],长度为 4、容量为 8(因为它引用的是原切片的底层数组)。

  • 在 doAppend 中、执行 append(s, 1)、这会向切片添加一个元素 1、导致切片的长度变为 5、容量保持为 8(因为它没有触发扩容)。

  • 打印结果为:len=5 cap=8。

调用 doAppend(s)

  • 这次传递整个 s 切片、长度为 8、容量为 8。

  • 执行 append(s 1)、这会向切片 s 添加一个元素 1。因为 s 的容量是 8、不能再容纳更多元素、因此会触发扩容、新的底层数组的容量将是原来的 2 倍、即 16、长度变为 9。

  • 打印结果为:len=9 cap=16。

  • 注意:

  • append 创建了一个新的底层数组、并返回了一个新的切片。如果你不把返回的新切片赋值回 s、原始切片 s 不会改变、仍然指向旧的底层数组。

  • 由于 append(s 1) 返回的是一个新的切片、但并没有将它赋值回 s、所以原始切片 s 的长度和容量没有变化、仍然是 len=8 和 cap=8

如果改成将新切片赋值回s

package mainimport "fmt"func main() {// 定义一个匿名函数 doAppend 用于向切片添加元素doAppend := func(s []int) {s = append(s, 1)          // 向切片中添加元素 1printLengthAndCapacity(s) // 打印切片的长度和容量}// 定义一个匿名函数 doAppend 用于向切片添加元素doAppends := func(s []int) []int {s = append(s, 1) // 使用 append 向切片添加一个元素 1printLengthAndCapacity(s)return s}// 创建一个长度为 8,容量为 8 的切片 ss := make([]int, 8, 8)// 传递前 4 个元素的切片doAppend(s[:4]) // 只传递前4个元素的切片printLengthAndCapacity(s)// 传递整个切片 ss = doAppends(s) // 将返回的新切片赋值回 sprintLengthAndCapacity(s)
}func printLengthAndCapacity(s []int) {fmt.Println()fmt.Printf("len=%d cap=%d \n", len(s), cap(s))
}
len=5 cap=8 len=8 cap=8 len=9 cap=16 len=9 cap=16 

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

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

相关文章

vue3 ts 自定义指令 app.directive

在 Vue 3 中,app.directive 是一个全局 API,用于注册或获取全局自定义指令。以下是关于 app.directive 的详细说明和使用方法 app.directive 用于定义全局指令,这些指令可以用于直接操作 DOM 元素。自定义指令在 Vue 3 中非常强大&#xff0…

基于python的机器学习(五)—— 聚类(二)

一、k-medoids聚类算法 k-medoids是一种聚类算法,它是基于k-means聚类算法的一种改进。k-medoids算法也是一种迭代算法,但是它将中心点限定为数据集中的实际样本点,而不是任意的点。 具体来说,k-medoids算法从数据集中选择k个初…

解释:指数加权移动平均(EWMA)

指数加权移动平均(EWMA, Exponential Weighted Moving Average) 是一种常用于时间序列平滑、异常检测、过程控制等领域的统计方法。相比普通移动平均,它对最近的数据赋予更高权重,对旧数据逐渐“淡化”。 ✅ 一、通俗理解 想象你…

Spring Boot 项目基于责任链模式实现复杂接口的解耦和动态编排!

全文目录: 开篇语前言一、责任链模式概述责任链模式的组成部分: 二、责任链模式的核心优势三、使用责任链模式解耦复杂接口1. 定义 Handler 接口2. 实现具体的 Handler3. 创建订单对象4. 在 Spring Boot 中使用责任链模式5. 配置责任链6. 客户端调用 四、…

COMSOL仿真遇到的两个小问题

最近跑热仿真的时候跑出了两个问题,上网查发现也没什么解决方式,最后自己误打误撞的摸索着解决了,在这里分享一下。 问题一 我当时一准备跑仿真就弹出了这个东西,但在此之前从未遇到 然后我试着在它说的路径中建立recoveries文件…

如何在英文学术写作中正确使用标点符号?

标点符号看似微不足道,但它们是书面语言的无名英雄。就像熟练的指挥家指挥管弦乐队一样,标点符号可以确保您的写作流畅、传达正确的含义并引起读者的共鸣。正如放错位置的音符会在音乐中造成不和谐一样,放错位置的逗号或缺少分号也会使您的写…

【深度学习与大模型基础】第10章-期望、方差和协方差

一、期望 ——————————————————————————————————————————— 1. 期望是什么? 期望(Expectation)可以理解为“长期的平均值”。比如: 掷骰子:一个6面骰子的点数是1~6&#x…

JAVA虚拟机(JVM)学习

入门 什么是JVM JVM:Java Virtual Machine,Java虚拟机。 JVM是JRE(Java Runtime Environment)的一部分,安装了JRE就相当于安装了JVM,就可以运行Java程序了。JVM的作用:加载并执行Java字节码(.class&#…

【数据结构与算法】——堆(补充)

前言 上一篇文章讲解了堆的概念和堆排序,本文是对堆的内容补充 主要包括:堆排序的时间复杂度、TOP 这里写目录标题 前言正文堆排序的时间复杂度TOP-K 正文 堆排序的时间复杂度 前文提到,利用堆的思想完成的堆排序的代码如下(包…

什么是柜台债

柜台债(柜台债券业务)是指通过银行等金融机构的营业网点或电子渠道,为投资者提供债券买卖、托管、结算等服务的业务模式。它允许个人、企业及机构投资者直接参与银行间债券市场的交易,打破了以往仅限机构参与的壁垒。以下是综合多…

【Android读书笔记】读书笔记记录

文章目录 一. Android开发艺术探索1. Activity的生命周期和启动模式1.1 生命周期全面分析 一. Android开发艺术探索 1. Activity的生命周期和启动模式 1.1 生命周期全面分析 onPause和onStop onPause后会快速调用onStop,极端条件下直接调用onResume 当用户打开新…

Java对象内存结构详解

Java对象内存结构详解 Java对象在JVM内存中的存储结构可以分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。以下是64位JVM(开启压缩指针)下…

【TI MSPM0】Printf重定向学习

一、新建工程 通过XDS110与电脑进行通信。 选择这两个引脚 需要添加这两个头文件 在程序中添加这三个函数即可对printf进行重定向 二、封装函数 另一种方法 封装一个函数,定义一个数组

深度强化学习基础 0:通用学习方法

过去自己学习深度强化学习的痛点: 只能看到各种术语、数学公式勉强看懂,没有建立清晰且准确关联 多变量交互关系浮于表面,有时候连环境、代理控制的变量都混淆 模型种类繁多,概念繁杂难整合、对比或复用,无框架分析所…

asm汇编源代码之-字库转换程序

将标准的16x16点阵汉字库(下载16x16汉字库)转换成适合VGA文本模式下显示的点阵汉字库 本程序需要调用file.asm中的子程序,所以连接时需要把file连接进来,如下 C:\> tlink chghzk file 调用参数描述如下 C:\> chghzk ; 无调用参数,转换标准库文件(SRC16.FNT)为适合VGA…

uniapp转换markdown

效果 AI智能体 微信小程序 流式 1.安装Node.js 参考:2024最新版Node.js下载安装及环境配置教程(非常详细)_node.js 安装-CSDN博客 2.需要克隆项目到本地或直接到项目地址下载压缩包。 参考:uniapp中解析markdown支持网页和小程序_uniapp ma…

用java代码如何存取数据库的blob字段

一.业务 在业务中我们被要求将文件或图片等转成 byte[] 或 InputStream存到数据库的Blob类型的字段中. 二.Blob类型介绍 在 MySQL 中,Blob 数据类型用于存储二进制数据。MySQL 提供了四种不同的 Blob 类型: TINYBLOB: 最大存储长度为 255 个字节。BL…

qemu(2) -- 定制开发板

1. 前言 qemu支持自定义开发板,本文就记录一下折腾的过程。基于qemu-10.0.0-rc3添加x210vb3s开发板。 2. 添加板卡文件 网上参考了一些文章,有些文章使用的版本和我的不一样,折腾起来费了点时间,最后发现还是直接参考qemu中已有…

Python在糖尿病分类问题上寻找具有最佳 ROC AUC 分数和 PR AUC 分数(决策树、逻辑回归、KNN、SVM)

Python在糖尿病分类问题上寻找具有最佳 ROC AUC 分数和 PR AUC 分数(决策树、逻辑回归、KNN、SVM) 问题模板解题思路1. 导入必要的库2. 加载数据3. 划分训练集和测试集4. 数据预处理5. 定义算法及其参数6. 存储算法和对应指标7. 训练模型并计算指标8. 找…

CPU(中央处理器)

一、CPU的定义与核心作用 CPU 是计算机的核心部件,负责 解释并执行指令、协调各硬件资源 以及 完成数据处理,其性能直接影响计算机的整体效率。 核心功能: 从内存中读取指令并译码。执行算术逻辑运算。控制数据在寄存器、内存和I/O设备间的…