Go 切片常用操作与使用技巧
1.什么是切片
在 Go 语言中的切片(slice)是一种灵活的动态数组,它可以自动扩展和收缩,是 Go 语言中非常重要的数据结构之一。切片是基于数组实现的,它的底层是数组,可以理解为对底层数组的抽象。它会生成一个指向数组的指针,并通过切片长度关联到底层数组部分或者全部元素。
2.切片底层结构
在 Go 语言的源码包中,src/runtime/slice.go
定义了切片的数据结构:
type slice struct {array unsafe.Pointerlen intcap int
}
切片占用 24 个字节:
-
array
:指向底层数组的指针,占用 8 个字节。当你创建一个切片时,Go 会分配一个底层数组,并将切片的指针指向该数组。 -
len
:切片中元素的数量,占用 8 个字节。 -
cap
:切片的容量,cap
总是大于等于len
,占用 8 个字节。
3.初始化方式
通常用以下三种初始化切片:
-
通过下标的方式获得数组的一部分;
slice := arr[1:3]
-
使用字面量初始化新的切片;
slice := []int{1, 2, 3}
-
使用关键字
make
创建切片:
slice := make([]int, 10)
//或
slice := make([]int, 3, 5)
make函数有三个参数:
- 第一个参数为切片类型,可以是
[]int,[]string,[]float32
等。 - 第二个参数为切片初始长度。
- 第三个为切片容量,该参数为可选参数。
注意点:
var slice1 []int 这是声明,此时切片为nil
例如:
package mainimport "fmt"func main() {var slice1 []intif slice1 == nil {fmt.Println("slice is nil!")}// slice1[0] = 9 报错panic: runtime error: index out of range [0] with length 0slice1 = append(slice1, 2) //可以fmt.Println(slice1)
}
结果:
slice is nil!
[2]
4.追加元素
append
函数是Go语言内置的一个函数,用于向切片中追加元素。
s := []int{1, 2, 3}
s = append(s, 4)
fmt.Println(s) // 输出:[1 2 3 4]
或
//如果你要追加的元素是另一个切片,那么可以使用...运算符,这样可以把那个切片的所有元素都追加进来。
s := []int{1, 2, 3}
t := []int{4, 5, 6}
s = append(s, t...)
fmt.Println(s) // 输出:[1 2 3 4 5 6]
5.切片表达式
Golang 中通常的 slice 语法是 a[low:high]
,您可能很熟悉。还有另一种切片语法,形式为 a[low:high:max]
,它采用三个索引而不是两个索引。第三索引 max
是做什么的?
提示: 不是 Python 切片语法 a[low:high:step]
中的 step 步长索引。
答: 第三个索引用于设置切片的容量!在 Golang 规范中称为 “全切片表达式”。
简单切片表达式的格式[low:high]
。
如下面例子,n为一个切片,当用这个表达式[1:4]
表示的是左闭右开[low, high)区间
截取一个新的切片(例子中结果是[2 3 4]),切片被截取之后,截取的长度是high-low
。
n := []int{1, 2, 3, 4, 5, 6}
fmt.Println(n[1:4]) // [2 3 4]
切片表达式的开始low和结束索引high是可选的;它们分别默认为零和切片的长度:
n := []int{1, 2, 3, 4, 5, 6}
fmt.Println(n[:4]) // [1 2 3 4],:前面没有值,默认表示0
fmt.Println(n[1:]) // [2 3 4 5 6],:后面没有值,默认表示切片的长度
边界问题
-
1、当n为数组或字符串表达式n[low:high]中low和high的取值关系:
0 <= low <=high <= len(n)
-
2、当n为切片的时候,表达式n[low:high]中high最大值变成了cap(n),low和high的取值关系:
0 <= low <=high <= cap(n)
不满足以上条件会发送越界panic。
全切片表达式
n[low:high:max]
max表示新生成切片的容量,新切片容量等于max-low
,表达式中low、high、max关系:
0 <= low <= high <= max <= cap(n)
继续刚才的例子,当计算n[1:4]
的容量,用cap得到值等于5,用扩展表达式n[1:4:5]
,用cap重新计算得到新的容量值(5-1)等于4:
fmt.Println(cap(n[1:4])) // 5fmt.Println(cap(n[1:4:5])) // 4
关于容量
n[1:4]的长度是3好理解(4-1),容量为什么是5?
因为切片n[1:4]和切片n是共享底层空间,所以它的容量并不等于他的长度3,根据1等于索引1的位置(等于值2),从值2这个元素开始到末尾元素6,共5个,所以n[1:4]
容量是5。
如果append超过切片的长度会重新生产一个全新的切片,不会覆盖原来的:
func main() {n := []int{1, 2, 3, 4, 5, 6}n2 := n[1:4:5] // 长度等于3,容量等于4fmt.Printf("%p\n", n2) // 0xc0000220c8n2 = append(n2, 5)fmt.Printf("%p\n", n2) // 0xc0000220c8n2 = append(n2, 6)fmt.Printf("%p\n", n2) // 地址发生改变,0xc00001e080
}
6.常见使用陷阱
切片作为参数传递陷阱
-
在函数里修改切片元素的值,原切片的值也会被改变;
-
在函数里通过
append
方法,对切片执行追加元素的操作,可能会引起切片扩容,导致内存分配的问题,可能会对程序的性能 造成影响; -
在函数里通过
append
函数,对切片执行追加元素的操作,原切片里不存在新元素。
看一下代码示例:
func main() {s := []int{0, 2, 3}fmt.Printf("main中1 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [0, 2, 3]sliceOperation(s)fmt.Printf("main中2 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [1, 2, 3]
}func sliceOperation(s []int) {s[0] = 1fmt.Printf("sliceOperation中 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [1, 2, 3]
}
结果:
main中1 切片的长度:3, 切片的容量:3, 切片的元素:[0 2 3]
sliceOperation中 切片的长度:3, 切片的容量:3, 切片的元素:[1 2 3]
main中2 切片的长度:3, 切片的容量:3, 切片的元素:[1 2 3]
看到切片中的元素会被改变,就认为改变切片中的元素数据就直接将切片作为参数传过去就可以了,但是如果用了append追加元素,就会发现追加的元素并不会加到原切片中:
func main() {s := []int{0, 2, 3}fmt.Printf("main中1 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [0, 2, 3]sliceOperation(s)fmt.Printf("main中2 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [1, 2, 3]
}func sliceOperation(s []int) {s = append(s, 4)fmt.Printf("sliceOperation中 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 4 6 [0, 2, 3]
}
结果:
main中1 切片的长度:3, 切片的容量:3, 切片的元素:[0 2 3]
sliceOperation中 切片的长度:4, 切片的容量:6, 切片的元素:[0 2 3 4]
main中2 切片的长度:3, 切片的容量:3, 切片的元素:[0 2 3]
-
若想实现执行
append
函数之后,原切片也能得到新元素;需将函数的参数类型由 切片类型 改成 切片指针类型。
func main() {s := []int{0, 2, 3}fmt.Printf("main中1 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) sliceOperation(&s)fmt.Printf("main中2 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s)
}func sliceOperation(s *[]int) {*s = append(*s, 4)fmt.Printf("sliceOperation中 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(*s), cap(*s), s)
}
结果:
main中1 切片的长度:3, 切片的容量:3, 切片的元素:[0 2 3]
sliceOperation中 切片的长度:4, 切片的容量:6, 切片的元素:[0 2 3 4]
main中2 切片的长度:4, 切片的容量:6, 切片的元素:[0 2 3 4]
slice 通过 make 函数初始化,后续操作不当所造成的陷阱
1.使用 make
函数初始化切片后,如果在后续操作中没有正确处理切片长度,容易造成以下陷阱:
func main() {s := make([]int, 0, 4)s[0] = 1 // panic: runtime error: index out of range [0] with length 0
}
通过 make([]int, 0, 4)
初始化切片,虽说容量为 4,但是长度为 0,如果通过索引去赋值,会发生panic;为避免 panic
,可以通过 s := make([]int, 4)
或 s := make([]int, 4, 4)
对切片进行初始化。
2.切片初始化不当,通过 append
函数追加新元素的位置可能于预料之外
func main() {s := make([]int, 4)s = append(s, 1)fmt.Println(s[0]) // 0s2 := make([]int, 0, 4)s2 = append(s2, 1)fmt.Println(s2[0]) // 1
}
-
通过打印结果可知,对于切片
s
,元素1
没有被放置在第一个位置,而对于切片s2
,元素1
被放置在切片的第一个位置。这是因为通过make([]int, 4)
和make([]int, 0, 4)
初始化切片,底层所指向的数组的值是不一样的:-
第一种初始化的方式,切片的长度和容量都为
4
,底层所指向的数组长度也是4
,数组的值为[0, 0, 0, 0]
,每个位置的元素被赋值为零值,s = append(s, 1)
执行后,s
切片的值为[0, 0, 0, 0, 1]
; -
第二种初始化的方式,切片的长度为
0
,容量为4
,底层所指向的数组长度为0
,数组的值为[]
,s2 = append(s2, 1)
执行后,s2
切片的值为[1]
; -
通过
append
向切片追加元素,会执行尾插操作。如果我们需要初始化一个空切片,然后从第一个位置开始插入元素,需要避免make([]int, 4)
这种初始化的方式,否则添加的结果会在预料之外。
-
-
性能陷阱
-
内存泄露
内存泄露是指程序分配内存后不再使用该内存,但未将其释放,导致内存资源被浪费。
切片引用切片场景:如果一个切片有大量的元素,而它只有少部分元素被引用,其他元素存在于内存中,但是没有被使用,则会造成内存泄露。代码示例如下:
var s []intfunc main() {sliceOperation()fmt.Println(s)}func sliceOperation() {a := make([]int, 0, 10)a = append(a, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)s = a[0:4]}
上述代码中,切片
a
的元素有10
个,而切片s
是基于a
创建的,它底层所指向的数组与a
所指向的数组是同一个,只不过范围为前四个元素,而后六个元素依然存在于内存中,却没有被使用,这样会造成内存泄露。为了避免内存泄露,我们可以对代码进行改造:s = a[0:4]
→s = append(s, a[0:4]...)
,通过append
进行元素追加,这样切片a
底层的数组没有被引用,后面会被gc
。
7.切片技巧
Go在Github的官方Wiki上介绍了切片的技巧SliceTricks[4],另外这个项目Go Slice Tricks Cheat Sheet[5]基于SliceTricks做了一系列的图,比较直观。
AppendVector
这个是添加一个切片的操作,上面我们在切片操作中已经介绍过。
a = append(a, b...)
Copy
这边给我们展示了三种copy的写法:
b := make([]T, len(a))
copy(b, a)// 效率一般比上面的写法慢,但是如果有更多,他们效率更好
b = append([]T(nil), a...)
b = append(a[:0:0], a...)// 这个实现等价于make+copy。
// 但在Go工具链v1.16上实际上会比较慢。
b = append(make([]T, 0, len(a)), a...)
Cut
截掉切片[i,j)
之间的元素:
a = append(a[:i], a[j:]...)
Cut(GC)
上面的Cut
如果元素是指针的话,会存在内存泄露,所以我们要对删除的元素设置nil
,等待GC。
copy(a[i:], a[j:])
for k, n := len(a)-j+i, len(a); k < n; k++ {a[k] = nil // or the zero value of T
}
a = a[:len(a)-j+i]
Delete
删除索引位置i的元素:
a = append(a[:i], a[i+1:]...)
// or
a = a[:i+copy(a[i:], a[i+1:])]
Delete(GC)
删除索引位置i的元素:
copy(a[i:], a[i+1:])
a[len(a)-1] = nil // or the zero value of T
a = a[:len(a)-1]
Delete without preserving order
删除索引位置i的元素,把最后一位放到索引位置i上,然后把最后一位元素删除。这种方式底层并没有发生复制操作。
a[i] = a[len(a)-1]
a = a[:len(a)-1]
Delete without preserving order(GC)
上面的删除操作,元素是一个指针的类型或结构体指针字段,会存在最后一个元素不能被GC掉,造成泄露,把末尾的元素设置nil,等待GC。
a[i] = a[len(a)-1]
a[len(a)-1] = nil
a = a[:len(a)-1]
Expand
这个本质上是多个append的组合操作。
a = append(a[:i], append(make([]T, j), a[i:]...)...)
Extend
用新列表扩展原来的列表
a = append(a, make([]T, j)...)
Filter (in place)
下面代码演示原地删除Go切片元素:
n := 0
for _, x := range a {if keep(x) {a[n] = xn++}
}
a = a[:n]
Insert
a = append(a[:i], append([]T{x}, a[i:]...)...)
第二个append
会产生新的切片,产生一次copy,可以用以下代码方式,可免去第二次的copy:
s = append(s, 0 /* 先添加一个0值*/)
copy(s[i+1:], s[i:])
s[i] = x
InsertVector
下面代码演示插入向量(封装了动态大小数组的顺序容器)的实现:
a = append(a[:i], append(b, a[i:]...)...)
func Insert(s []int, k int, vs ...int) []int {if n := len(s) + len(vs); n <= cap(s) {s2 := s[:n]copy(s2[k+len(vs):], s[k:])copy(s2[k:], vs)return s2}s2 := make([]int, len(s) + len(vs))copy(s2, s[:k])copy(s2[k:], vs)copy(s2[k+len(vs):], s[k:])return s2
}a = Insert(a, i, b...)
Push
a = append(a, x)
Pop
x, a = a[len(a)-1], a[:len(a)-1]
Push Front/Unshift
a = append([]T{x}, a...)
Pop Front/Shift
x, a = a[0], a[1:]
7 切片额外技巧
Filtering without allocating
下面例子演示数据过滤的时候,b基于原来的a存储空间来操作,并没有重新生成新的存储空间。
b := a[:0]
for _, x := range a {if f(x) {b = append(b, x)}
}
为了让截取之后没有使用的存储被GC掉,需要设置成nil:
for i := len(b); i < len(a); i++ {a[i] = nil // or the zero value of T
}
Reversing
反转操作演示:
for i := len(a)/2-1; i >= 0; i-- {opp := len(a)-1-ia[i], a[opp] = a[opp], a[i]
}
还有一种方法:
for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {a[left], a[right] = a[right], a[left]
}
Shuffling
洗牌算法。算法思想就是从原始数组中随机抽取一个新的数字到新数组中。
for i := len(a) - 1; i > 0; i-- {j := rand.Intn(i + 1)a[i], a[j] = a[j], a[i]
}
go1.10之后有内置函数Shuffle[6]
Batching with minimal allocation
做批处理大的切片的时候,这个技巧可以了解下:
actions := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
batchSize := 3
batches := make([][]int, 0, (len(actions) + batchSize - 1) / batchSize)for batchSize < len(actions) {actions, batches = actions[batchSize:], append(batches, actions[0:batchSize:batchSize])
}
batches = append(batches, actions)// 结果:
// [[0 1 2] [3 4 5] [6 7 8] [9]]
In-place deduplicate (comparable)
删除有序数组中的重复项:
import "sort"in := []int{3,2,1,4,3,2,1,4,1} // any item can be sorted
sort.Ints(in)
j := 0
for i := 1; i < len(in); i++ {if in[j] == in[i] {continue}j++// preserve the original data// in[i], in[j] = in[j], in[i]// only set what is requiredin[j] = in[i]
}
result := in[:j+1]
fmt.Println(result) // [1 2 3 4]
Move to front, or prepend if not present, in place if possible.
下面代码演示移动指定元素到头部:
// moveToFront moves needle to the front of haystack, in place if possible.
func moveToFront(needle string, haystack []string) []string {if len(haystack) != 0 && haystack[0] == needle {return haystack}prev := needlefor i, elem := range haystack {switch {case i == 0:haystack[0] = needleprev = elemcase elem == needle:haystack[i] = prevreturn haystackdefault:haystack[i] = prevprev = elem}}return append(haystack, prev)
}haystack := []string{"a", "b", "c", "d", "e"} // [a b c d e]
haystack = moveToFront("c", haystack) // [c a b d e]
haystack = moveToFront("f", haystack) // [f c a b d e]
Sliding Window
下面实现根据size的滑动窗口输出:
func slidingWindow(size int, input []int) [][]int {// returns the input slice as the first elementif len(input) <= size {return [][]int{input}}// allocate slice at the precise size we needr := make([][]int, 0, len(input)-size+1)for i, j := 0, size; j <= len(input); i, j = i+1, j+1 {r = append(r, input[i:j])}return r
}func TestSlidingWindow(t *testing.T) {result := slidingWindow(2, []int{1, 2, 3, 4, 5})fmt.Println(result) // [[1 2] [2 3] [3 4] [4 5]]
}
最后的技巧章节参考微信公众号太白技术: Go切片与技巧(附图解)
相关文章:

Go 切片常用操作与使用技巧
1.什么是切片 在 Go 语言中的切片(slice)是一种灵活的动态数组,它可以自动扩展和收缩,是 Go 语言中非常重要的数据结构之一。切片是基于数组实现的,它的底层是数组,可以理解为对底层数组的抽象。它会生成一…...

2024 中青杯高校数学建模竞赛(A题)数学建模完整思路+完整代码全解全析
你是否在寻找数学建模比赛的突破点?数学建模进阶思路! 作为经验丰富的数学建模团队,我们将为你带来2024 长三角高校数学建模竞赛(A题)的全面解析。这个解决方案包不仅包括完整的代码实现,还有详尽的建模过…...
开源与闭源:AI模型发展的双重路径之争
前言 随着人工智能(AI)技术的飞速发展,AI模型的应用已经渗透到各行各业,从医疗、金融到制造、教育,无不受到AI技术的深刻影响。在讨论一个AI模型“好不好”“有没有发展”时,绕不过“开源”和“闭源”两条…...

微信小程序---小程序文档配置(2)
一、小程序文档配置 1、小程序的目录结构 1.1、目录结构 小程序包含一个描述整体程序的 app 和多个描述各自页面的 page 一个小程序主体部分由三个文件组成,必须放在项目的根目录 比如当前我们的《第一个小程序》项目根目录下就存在这三个文件: 1…...

15:00面试,15:08就出来了,问的问题有点变态。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到8月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…...

电磁兼容(EMC):去耦电容设计详解
目录 1. 概念 2. 去耦电容工作机理 3. 去耦电容大小选择 4. 去耦电容PCB布局 电容在电路中不同作用有不同的称呼去耦电容、旁路电容、储能电容,而这些作用又可以统称为滤波。本文将详细解读一下三者之间的差别,并着重说明一下去耦电容的设计方法。 …...
《数组逆序输出》
描述 编写程序,输入10个整数n存入,再按逆序重新存放后再输出。 输入描述 输入共10个数。 输出描述 输出共1行,每个数字用空格隔开。 样例输入 1 -5 -4 -3 -2 -1 0 1 2 3 4 样例输出 1 4 3 2 1 0 -1 -2 -3 -4 -5 提示 对于100%的数据…...

必应崩了?
目录 今天使用必应发现出现了不能搜索,弹出乱码的情况。 搜了一下,发现其他人也出现了同样的问题。 使用Edge浏览器的话,可以试着改一下DNS,有可能会恢复正常(等官方修复了记得改回来) 使用谷歌浏览器打开…...

Elasticsearch集群和Logstash、Kibana部署
1、 Elasticsearch集群部署 服务器 安装软件主机名IP地址系统版本配置ElasticsearchElk10.3.145.14centos7.5.18042核4GElasticsearchEs110.3.145.56centos7.5.18042核3GElasticsearchEs210.3.145.57centos7.5.18042核3G 软件版本:elasticsearch-7.13.2.tar.gz 示…...

网络的基础理解
文章目录 网络的基础认识 网络协议协议分层OSI七层模型TCP/IP 五层/四层 模型 网络的基础认识 先来看下面几个问题 什么是网络? 网络就是有许多台设备包括计算机单不仅限于计算机,这些设备通过相互通信所组成起来系统,我们称之为网络所以如…...

Android Studio 与 Gradle 及插件版本兼容性
Android Studio 开始新项目时,会自动创建其中部分文件,并为其填充合理的默认值。 项目文件结构布局: 一、Android Gradle 及插件作用: Android Studio 构建系统以 Gradle 为基础,并且 Android Gradle 插件 (AGP) 添加…...

【BUG】Edge|联想电脑 Bing 搜索报错“Ref A: 乱码、 Ref B:乱码、Ref C: 日期” 的解决办法
文章目录 省流版前言解决办法 详细解释版前言问题描述与排查过程解决办法与总结 省流版 我原以为我解决了,才发的博客,晚上用了一下其他设备发现还是会出现这个问题… 这篇博客并未解决该问题,如果评论里有人解决了这个问题不胜感激&#x…...
深度学习小车操作手册全
深度学习小车_操作手册_全 资源链接 分享文件:深度学习小车_操作手册_全.pdf 链接:https://pan.xunlei.com/s/VNy-KXPDZw64RqQGXiWVEDMRA1?pwdymu4# 复制这段内容后打开手机迅雷App,查看更方便智能车简介 2019 年的特斯拉自动驾驶开放日上…...

Python实现天气数据采集
Python实现天气数据采集 一、需求介绍二、完整代码一、需求介绍 本次天气数据采集的需求是获取每日的最高温、最低温、风力、风向、天气状况、AQI指数,如图所示,完整代码附后: 本次采集的目标网址是2345天气网: 上图的URL中,beijing是城市名称的缩写,54511即为城市代码…...

05 JavaSE-- 异常、IOStream、多线程、反射、Annotation、泛型、序列化
Exception 异常 异常也是对象,也有自己的体系,在这个体系中,所有异常对象的根类是 throwable 接口。异常和 error 错误是不同的概念。 错误是严重的 JVM 系统问题,一般不期待程序员去捕获、处理这些错误,同时…...
c++/c语法基础【2】
目录 1.memset 数组批量赋值 2.字符数组 编辑输入输出: 字符数组直接输入输出%s: gets! string.h 1.strlen:字符串去掉末尾\0的长度...
python 庆余年2收视率数据分析与可视化
为了对《庆余年2》的收视率进行数据分析与可视化,我们首先需要假设有一组收视率数据。由于实际数据可能无法直接获取,这里我们将使用模拟数据来演示整个过程。 以下是一个简单的步骤,展示如何使用Python(特别是pandas和matplotli…...

yolov8训练自己数据集时出现loss值为nan。
具体原因目前暂未寻找到。 解决办法 将参数amp改成False即可。 相关资料: https://zhuanlan.zhihu.com/p/165152789 https://github.com/ultralytics/ultralytics/issues/1148...

[Chapter 5]线程级并行,《计算机系统结构》,《计算机体系结构:量化研究方法》
文章目录 一、互连网络1.1 互连网络概述1.1 互连函数1.1.1 互连函数1.1.2 几种基本的互连函数1.1.2.1 恒等函数1.1.2.2 交换函数1.1.2.3 均匀洗牌函数1.1.2.4 碟式函数1.1.2.5 反位序函数1.1.2.6 移数函数1.1.2.7 PM2I函数 1.2 互连网络的结构参数与性能指标1.2.1 互连网络的结…...

首发!飞凌嵌入式FETMX6ULL-S核心板已适配OpenHarmony 4.1
近日,飞凌嵌入式在FETMX6ULL-S核心板上率先适配了OpenHarmony 4.1,这也是业内的首个应用案例,嵌入式核心板与OpenHarmony操作系统的结合与应用,将进一步推动千行百业的数智化进程。 飞凌嵌入式FETMX6ULL-S核心板基于NXP i.MX 6ULL…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...

Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...

企业大模型服务合规指南:深度解析备案与登记制度
伴随AI技术的爆炸式发展,尤其是大模型(LLM)在各行各业的深度应用和整合,企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者,还是积极拥抱AI转型的传统企业,在面向公众…...

2.3 物理层设备
在这个视频中,我们要学习工作在物理层的两种网络设备,分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间,需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质,假设A节点要给…...
Yii2项目自动向GitLab上报Bug
Yii2 项目自动上报Bug 原理 yii2在程序报错时, 会执行指定action, 通过重写ErrorAction, 实现Bug自动提交至GitLab的issue 步骤 配置SiteController中的actions方法 public function actions(){return [error > [class > app\helpers\web\ErrorAction,],];}重写Error…...