Go 切片常用操作与使用技巧

news2024/11/20 13:34:14

1.什么是切片

在 Go 语言中的切片(slice)是一种灵活的动态数组,它可以自动扩展和收缩,是 Go 语言中非常重要的数据结构之一。切片是基于数组实现的,它的底层是数组,可以理解为对底层数组的抽象。它会生成一个指向数组的指针,并通过切片长度关联到底层数组部分或者全部元素。

2.切片底层结构

在 Go 语言的源码包中,src/runtime/slice.go 定义了切片的数据结构:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   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函数有三个参数:

  1. 第一个参数为切片类型,可以是[]int,[]string,[]float32等。
  2. 第二个参数为切片初始长度。
  3. 第三个为切片容量,该参数为可选参数。

注意点:

var slice1 []int 这是声明,此时切片为nil

例如:

package main

import "fmt"

func main() {
	var slice1 []int
	if slice1 == nil {
		fmt.Println("slice is nil!")
	}
	// slice1[0] = 9 报错panic: runtime error: index out of range [0] with length 0
	slice1 = 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])) // 5
 fmt.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,容量等于4
	fmt.Printf("%p\n", n2) // 0xc0000220c8
	n2 = append(n2, 5)
	fmt.Printf("%p\n", n2) // 0xc0000220c8
	n2 = 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] = 1
	fmt.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]) // 0

	s2 := 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 []int
    
      func 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] = x
  n++
 }
}
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-i
 a[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 required
 in[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 := needle
 for i, elem := range haystack {
  switch {
  case i == 0:
   haystack[0] = needle
   prev = elem
  case elem == needle:
   haystack[i] = prev
   return haystack
  default:
   haystack[i] = prev
   prev = 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 element
 if len(input) <= size {
  return [][]int{input}
 }

 // allocate slice at the precise size we need
 r := 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切片与技巧(附图解)

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

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

相关文章

智能车竞赛指南:从零到一,驶向自动驾驶的未来

智能车竞赛指南&#xff1a;从零到一&#xff0c;驶向自动驾驶的未来 一、智能车竞赛概览1.1 竞赛介绍1.2 竞赛分类 二、智能车开发技术基础2.1 硬件平台2.2 软件开发 三、实战案例&#xff1a;循线小车开发3.1 系统架构3.2 代码示例 四、技术项目&#xff1a;基于ROS的视觉导航…

一款功能强大的安卓虚拟机应用——VMOS Pro使用分享

前段时间我刚刚分享一个WeChat平板模块能够允许用户自由修改系统设置&#xff0c;让你的Android备用手机焕发新生&#xff0c;实现手机PAD化&#xff0c;实现两台设备同时登录微信号。今天我分享的这个相比WeChat更为简单&#xff0c;因为它可以通过虚拟机的方式进行多种androi…

【Numpy】深入解析numpy.diag()函数

numpy.diag()&#xff1a;深入探索NumPy库中的对角矩阵操作 &#x1f308; 欢迎莅临我的个人主页&#x1f448;这里是我深耕Python编程、机器学习和自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;并乐于分享知识与经验的小天地&#xff01;&#x1f387; &#x1f3…

MGRE实验——路由配置

对134环回 ping一下发现都可以通 配置3&#xff0c;4同3 再注册 然后内网要互通&#xff0c;起rip 宣告1的左边和右边 对3 对4 当3&#xff0c;4之间要互通时&#xff0c;首先在1上 关闭之后&#xff0c;3就能学到4上的用户网段&#xff0c;4也能学到3 局域网要访问广域网一定…

Web 3D 框架简介

前言 3D游戏引擎的历史可以追溯到20世纪80年代末和90年代初。当时,计算机技术迅速发展,人们开始对图形和游戏感兴趣。以下是3D游戏引擎的历史故事: 早期引擎的诞生(1980-1990年代) 在这个时期,一些早期的3D游戏引擎开始出现。其中一个著名的例子是id Software开发的Do…

单细胞分析(Signac): PBMC scATAC-seq 聚类

引言 在本教学指南中&#xff0c;我们将探讨由10x Genomics公司提供的人类外周血单核细胞&#xff08;PBMCs&#xff09;的单细胞ATAC-seq数据集。 加载包 首先加载 Signac、Seurat 和我们将用于分析人类数据的其他一些包。 if (!requireNamespace("EnsDb.Hsapiens.v75&qu…

Dijkstra算法在《庆余年》中的应用:范闲的皇宫之旅

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

英语学习笔记24——Give me/us/him/her/them some ...

Give me/us/him/her/them some … 给我/我们/他/她/他们一些…… 词汇 Vocabulary desk n. 课桌&#xff08;有书桌堂&#xff09;&#xff0c;写字台 复数&#xff1a;desks 搭配&#xff1a;desk mate 同桌    构成&#xff1a;desk mate 桌子上的伙伴 同桌    cl…

FBB-Frontiers in Bioengineering and Biotechnology

文章目录 一、期刊简介二、征稿信息三、期刊表现四、投稿须知五、投稿咨询 一、期刊简介 Frontiers in Bioengineering and Biotechnology是专注生物工程和生物技术领域的开放获取期刊。 研究范围涵盖生物材料、生物力学、生物工艺工程、生物安全和生物安保&#xff0c;生物传…

计算机系统基础 7 分支程序的实现

简单条件转移指令 根据单个标志位的值&#xff08;CF&#xff0c; SF&#xff0c;OF&#xff0c;PF&#xff0c;ZF&#xff09;来确定是否转移&#xff0c; 如果条件成立&#xff0c;则&#xff08;EIP&#xff09; 位移量 ➡ EIP&#xff0c;否则什么也不做。 注意&#xff0…

【Andoird开发】android获取蓝牙权限,搜索蓝牙设备MAC

<!-- Android 12以下才需要定位权限&#xff0c; Android 9以下官方建议申请ACCESS_COARSE_LOCATION --><uses-permission android:name"android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name"android.permission.ACCES…

参考文献交叉引用两个文献,逗号隔开

1.引用两个参考文献&#xff0c;定位到word正文中需要引用的位置&#xff0c;然后插入-交叉引用&#xff0c;引好文献 2.选中两个参考文献&#xff0c;切换域代码&#xff0c;然后进行修改&#xff1a; 改为 上面的两张图片中的点是空格的含义&#xff0c;word中按ctrlshift8就…

【计算机网络实验】TCP协议的抓包分析:三次握手四次挥手UDP和TCP的区别(超详细教程)

计算机网络实验——TCP协议抓包分析 文章目录 计算机网络实验——TCP协议抓包分析一、基础知识点1、运输层两个重要协议的特点对比&#xff08;TCP和UDP&#xff09;2、TCP报文的格式3、常见的TCP报文标识字段&#xff08;FLAG字段&#xff09;4、TCP连接的建立过程及理解——三…

CTFshow之文件上传web入门151关-161关解密。包教包会!!!!

这段时间一直在搞文件上传相关的知识&#xff0c;正好把ctf的题目做做写写给自字做个总结&#xff01; 不过有一个确定就是所有的测试全部是黑盒测试&#xff0c;无法从代码层面和大家解释&#xff0c;我找个时间把upload-labs靶场做一做给大家讲讲白盒的代码审计 一、实验准…

多台Centos快速区分,让Centos开机自动显示它的IP地址!

背景说明&#xff1a;当公司拥有多台Centos服务器&#xff0c;管理员很容易弄混淆导致不好区分&#xff0c;在这样的情况下我们可以写个简单脚本来实现开机自动显示它的IP地址&#xff0c;从而达到区分开来的结果&#xff01; 首先我们来开下效果&#xff0c;登录之前的 下面是…

调试时JSON库一直提示 PDB找不到 使用需要对象文件来进行调试的 /DEBUG:Fastlink生成的

最近调试时一直提示上面的提示框&#xff0c;很是烦躁。 为什么会出现这个错误呢&#xff0c;我一直使用的是/DEBUG。出现原因没有找出来&#xff0c;理论上市使用了/DEBUG:Fastlink这个模式才会出&#xff0c;但是就是一直在报这个错误。 /DEBUG&#xff08;生成调试信息&am…

Linux进程的地址空间

Linux进程的地址空间 1. 前言 在编写程序语言的代码时&#xff0c;打印输出一个变量的地址时&#xff0c;这个地址在内存中是以什么形式存在的&#xff1f;一个地址可以存储两个不同的值吗&#xff1f; 运行以下代码&#xff1a; #include <stdio.h> #include <un…

云和恩墨海外首秀在吉隆坡召开的2024中国智能科技与文化展览会

作为中马建交50周年官方重点推荐的活动之一&#xff0c;2024中国智能科技与文化展览会&#xff08;第四届&#xff09;于5月20至21日在毗邻吉隆坡双子塔的吉隆坡国际会展中心举办。本次展览会获得马来西亚科学技术创新部、马来西亚通讯部、中国驻马来西亚大使馆和马来西亚中华总…

向郭老师学习研发项目管理

学习研发项目管理思路 通过以下思路来学习研发项目管理&#xff1a; 1、研发项目管理分3级 2、研发项目管理分4类 3、研发项目管理分5大过程组 4、新产品开发项目生命周期分6个阶段 5、研发项目管理分10大知识体系 项目组合、项目集、简单项目3级管理 针对Portfolio组合…

融合基因组序列识别scATAC-seq的细胞类型

利用scATAC-seq技术进行单细胞分析&#xff0c;可以在单细胞分辨率下深入了解基因调控和表观遗传异质性&#xff0c;但由于数据的高维性和极端稀疏性&#xff0c;scATAC-seq的细胞注释仍然具有挑战性。现有的细胞注释方法大多集中在细胞峰矩阵上&#xff0c;没有充分利用潜在的…