GO数组切片-线性数据结构

news2024/11/24 12:54:36

数据结构

类型

什么是类型 ?
内存中的二进制数据本身没有什么区别,就是一串0或1的组合。
内存中有一个字节内容是0x63,他究竟是深恶 字符串?字符?还是整数?
本来0x63表示数字 但是文字必须编码成为0和1的组合 才能记录在计算机系统中。在计算机世界里,一切都是数字,但是一定需要指定
类型才能正确的理解它的含义

如果0x63是整数,它就属于整数类型,它是整数类型的一个具体的实列 整数类型就是一个抽象的概念,他是对有一类有着共同特征的事务的抽象概念,
它就属于整数类型,它是整数类型的一个具体的实例。整数类型就是一个抽象的概念,它是对一类有着共同特征的事务的抽象概念。他展示出来就是99,因为多数情况下,程序按照人们习惯采用10进制输出
如果0x63时byte类型或者rune(字符int32)类型,在Go语言中,它是不同于整型的类型,但是展示出来同样时99.
如果0x63时string类型,则展示出一个字符的字符串"c"。

func main(){
	var a = 0x63 
	fmt.Printf("%T %[1]d %[1]c \n",a) //类型  数值 字符
	var b byte = 0x63
	fmt.Printf("%T %[1]d %[1]c\n",b)  //uint8(字节) 99 c
	var c rune = 0x63
	fmt.Printf("%T %[1]d %[1]c\n",c)
	// x:= '1'  //单引号默认是字符类型
	d := "\x63"  //定义字符串  
	fmt.Printf("%T %[1]s\n",d)
	fmt.Printf("%T %[1]s\n", string(a))  //将a 进行类型转换
}
//结果====>
/*
int 99 c 
uint8 99 c
int32 99 c
string c
string c
*/

数值处理

math包的使用

取整

在fmt.Println(1/2, 3/2, 5/2) // "/" 整数除法,截取整数部分 "%"求模
fmt.Println(-1/2, -3/2, -5/2)
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
fmt.Println(math.Ceil(2.01), math.Ceil(2.5), math.Ceil(2.8))  //向上取整 2取3
fmt.Println(math.Ceil(-2.01), math.Ceil(-2.5), math.Ceil(-2.8))
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
fmt.Println(math.Floor(2.01), math.Floor(2.5), math.Floor(2.8))    //向下取整 -2. 取-2
fmt.Println(math.Floor(-2.01), math.Floor(-2.5), math.Floor(-2.8))
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
fmt.Println(math.Round(2.01), math.Round(2.5), math.Round(2.8)) //四舍五入
fmt.Println(math.Round(-2.01), math.Round(-2.5), math.Round(-2.8))
fmt.Println(math.Round(0.5), math.Round(1.5), math.Round(2.5), 
math.Round(3.5))
运行结果
0 1 2
0 -1 -2
~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 3 3
-2 -2 -2
~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 2 2
-3 -3 -3
~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 3 3
-2 -3 -3
1 2 3 4

其他数值处理

fmt.Println(math.Abs(-2.7))                               // 绝对值
fmt.Println(math.E, math.Pi)                              // 常数
fmt.Println(math.MaxInt16, math.MinInt16)                 // 常量,极值
fmt.Println(math.Log10(100), math.Log2(8))                // 对数
fmt.Println(math.Max(1, 2), math.Min(-2, 3))              // 最大值、最小值
fmt.Println(math.Pow(2, 3), math.Pow10(3))                // 幂
fmt.Println(math.Mod(5, 2), 5%2)                          // 取模
fmt.Println(math.Sqrt(2), math.Sqrt(3), math.Pow(2, 0.5)) // 开方

标准输入

Scan: 空白字符分割,回车提交。换行符当作空白字符

package main
import (
 	"fmt"
)
func main() {
 	var n int
 	var err error
 	var word1, word2 string
 	fmt.Print("Plz input two words: ")
 	n, err = fmt.Scan(&word1, &word2) // 控制台输入时,单词之间空白字符分割
 	if err != nil {
 		panic(err)
 	}
 	fmt.Println(n)
 	fmt.Printf("%T %s, %T %s\n", word1, word1, word2, word2)
 	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
 	var i1, i2 int
 	fmt.Println("Plz input two ints: ")
 	n, err = fmt.Scan(&i1, &i2)
 	if err != nil {
 		panic(err)
 	}
 	fmt.Println(n)
 	fmt.Printf("%T %[1]d, %T %[2]d", i1, i2)
}

如果少一个数据,Scan就会堵塞;如果输入数据多了,等下回Scan读取。例如,一次性输入a b 1 2 看看效果
我们发现 当一次性输入 a b 1 2 的时候 第二个Scan会被自动赋值为 1 2 ,无需再手动输入

Scanf: 读取输入,按照格式匹配解析。如果解析失败,立即报错,那么就会影响后面的Scanf。

package main
import (
 	"fmt"
)
func main() {
 	var n int
 	var err error
 	var name string
 	var age int
 	fmt.Print("Plz input your name and age: ")
 	n, err = fmt.Scanf("%s %d\n", &name, &age) // 这里要有\n以匹配回车
 	if err != nil {
		 panic(err)
 	}
 	fmt.Println(n, name, age)
 	var weight, height int
 	fmt.Print("weight and height: ")
 	_, err = fmt.Scanf("%d %d", &weight, &height) //fmt.Scanf("%d,%d", &weight, &height)尽量不要使用这种
 	if err != nil {
 		panic(err)
 	}
 	fmt.Printf("%T %[1]d, %T %[2]d", weight, height)
}

线性数据结构

线性表:
- 线性表(简称表),是一种抽象的数学概念,是一组元素的系列的抽象,它由有穷个元素组成(0个或任意个)
- 顺序表:使用一大块连续的内存顺序存储表中的元素,这样实现的表称为顺序表,或称连续表
- 在顺序表中,元素的关系使用顺序表的存储顺序自然地表示
- 链接表:在存储空间中将分散存储的元素链接起来,这种称为链接表,简称链表

数组等类型,如同地铁站拍好的队伍,有序,可以插队,离队,可以进行索引
链表,就如同一串带线的珠子,随意摆放在桌子上 ,可以插队,离队,可以进行索引。

顺序表数据结构
可以看到 顺序表中的每一个元素的地址是依照顺序所排列的
在这里插入图片描述
链接表数据结构
链接表中的元素 并不是有顺序的排列 他的各个元素 分布在不同的内存地址上,在双向链表中 index(n) 和index(n+1)相互记录了双方的地址,比如index1<->index2,index2<->index3,在单向链表中 前一个元素只记录第二个元素的地址
在这里插入图片描述
我们对比一下 线性数据结构中 顺序表和线性表的增删改查

顺序表:
 - 知道某个元素的地址或者首地址,可以通过偏移量进行查找某个元素
  增加元素:
   - 尾部:直接在偏移量的内存位置放置 代价小 
   - 中间:相当于插队,此后所有的数据向后移
   - 头部:从头开始所有元素依次向后挪动
  修改元素:
   - 使用索引找到元素,最快,找到修改即可 
  删除元素:
   - 尾部: 如果是尾部 不需要挪动 
   - 中间或开头: 删除元素后 其元素需要要前挪动 
链接表:
 - 单向链接表来看,从头开始 效率相对低于顺序表  
 - 知道其中一个元素或者开始地址,不能直接通过偏移量找到某个元素,需要从头遍历一遍 
  增加元素:
   - 尾部:尾巴指针增加元素,改改地址,效率高
   - 中间:遍历查找,找到插入点 断开小手  分别连接
   - 头部:改改地址  效率高 
  修改元素: 
   - 使用索引查找元素,修改其内容,效率较高
  删除元素:
   - 定位元素,索引最快 
   - 删除元素,断开小手重新拉 很快

数组

· 长度不可变
· 内容可变
· 可索引
· 值类型
· 顺序表

定义

// 注意下面2种区别
var a0 [3]int                   // 零值初始化3个元素的数组 [0,0,0] 未赋值 默认0值
var a1 = [3]int{}               // 零值初始化3个元素的数组  [0,0,0]
// [3]int是类型,[3]int{} 是字面量值
var a2 [3]int = [3]int{1, 3, 5} // 声明且初始化,不推荐,啰嗦
var a3 = [3]int{1, 3, 5}        // 声明且初始化,推荐
count := 3
a4 := [count] int{1,3,5} // 错误的长度类型,必须是常量,换成const
fmt.Println(a2, a3)
const count = 3
a4 := [count]int{1, 3, 5} // 正确
fmt.Println(a2, a3, a4)
a5 := [...]int {10, 30, 50} // ...让编译器确定当前数组大小
a6 := [5]int{100, 200}       // 顺序初始化前面的,其余用零值填充
a7 := [5]int{1: 300, 3: 400} // 指定索引位置初始化,其余用零值填充
// 二维数组
a8 := [2][3]int{{100}} // 两行三列 [[100 0 0] [0 0 0]]
// [[10 0 0] [11 12 0] [13 14 15] [16 0 0]]
// 多维数组,只有第一维才能用...推测
// 第一维有4个,第二维有3个。可以看做4行3列的表
a9 := [...][3]int{{10}, {11, 12}, {13, 14, 15}, {16}}

长度和容量
· cap即capacity,容量,表示给数组分配的内存空间可以容纳多少个元素
· len即length,长度,指的是容器中目前有几个元素
由于数组创建时就必须确定的元素个数,且不能改变长度,所以不需要预留多余的内存空间,因此cap和len对数组来说一样。

索引
Go语言不支持负索引。通过[index]来获取该位置上的值。索引范围就是[0, 长度-1]。

修改

func main(){
	//编译器定义数组长度
	var a1 = [...]int{1,2,3}
	a1[0] += 100   //[100,2,3]
}

遍历

索引遍历


func main(){
	//编译器定义数组长度
	var a1 = [...]int{1,2,3}
	a1[0] += 100   //[100,2,3]
	for i :=0;i<len(a1);i++{
		fmt.Println(i,a1[i])
	}
}

for-range 遍历

func main(){
	//编译器定义数组长度
	var a1 = [...]int{1,2,3}
	a1[0] += 100   //[100,2,3]
	//i 索引 v 元素值
	for i,v := range a1{
		fmt.Println(i,v)
	}
}

内存模型

func main(){
	var a [3]int
	for i:=0;i<len(a);i++{
		fmt.Println(i,a[i],&a[i])
	}
	fmt.Printf("%p %p, %v\n", &a, &a[0], a)
	a[0] = 1000
	fmt.Printf("%p %p, %v\n", &a, &a[0], a)
}
===========================================================================
0 0 0xc0000ae078   
1 0 0xc0000ae080
2 0 0xc0000ae088
0xc0000ae078 0xc0000ae078, [0 0 0]  //数组地址  首元素地址 数组元素
0xc0000ae078 0xc0000ae078, [1000 0 0]

  上面的每个元素间隔8字节 正好是64位,符合int定义
· 数组必须在编译时就确定大小,之后不能改变大小  
· 数组首地址就是数组地址  
· 所有元素一个接一个顺序存在内存中
· 元素的值可以改变 但元素的地址不变

如果元素字符串类型呢?

func main() {
	var a = [3]string{"abc","xyzafsdfdsgdgdf","asd"}
	for i := 0; i < len(a); i++ {
		fmt.Println(i,a[i],&a[i])
	}
	fmt.Printf("%p %p, %v\n",&a,&a[0],a)
}
=======================================================================
0 abc 0xc000112480
1 xyzafsdfdsgdgdf 0xc000112490
2 asd 0xc0001124a0
0xc000112480 0xc000112480, [abc xyzafsdfdsgdgdf asd]

· 数组首地址是第一个元素的地址 
· 所有元素顺序存储在内存中 
· 元素的值可以改变,但是元素地址不变

我们可以发现  字符串数组每个元素的地址偏移量相差16个字节,这是为什么? 为什么第二个元素占了这么多位置,
还是和第三个元素相差16个字节?

 原因 :
   字符串实际上是引用类型,Go字符串默认内存中存储16字节,它的元素卸载了堆上 

值类型

func main() {
	var a1 =  [2]int{1,2}
	fmt.Printf("a1 %p %p\n",&a1,&a1[0])

	a2 := a1
	fmt.Printf("a2 %p %p\n",&a2,&a2[0])

	//调用函数  将a1做为实参传入
	showAddr(a1)
}
====================================================================================
a1 0xc0000140a0 0xc0000140a0
a2 0xc0000140d0 0xc0000140d0
a3 0xc0000140e0 0xc0000140e0

可以看出a1,a2,a3的地址都不一样,a2:=a1的地址也不一样,这说明,Go语言在这些地方进行了
值拷贝,都生成了一份副本。

切片

· 长度可变
· 内容可变
· 引用类型
· 底层基于数组

func main() {
	//定义切片  
	var s1  []int
	var s2 = []int{}
	var s3 = []int{1,2,3} //定义字面量 长度为3 容量为3
	var s4  = make([]int,3,5)  //使用make定义切片 make([]type,len,cap) 数据类型是切片引用类型,长度为3 容量位5
	//s4 的结果是 [0,0,0]
}

内存模型

在这里插入图片描述
切片本质是对底层数组一个连续片段的引用,此片段可以是整个底层数组,也可以是起始和终止索引标识的一些项的子集。

//以下是go切片的结构体  他的属性成员是 底层数组的指针,长度,容量
// https://github.com/golang/go/blob/master/src/runtime/slice.go
type slice struct {
    array unsafe.Pointer
    len int
    cap int
}
func main() {
	//定义切片  
	var s1  []int = []int{1,2,3}
	fmt.Printf("切片地址: %p 底层数组地址: %p",&s1,&s1[0])
}
---------------------------------------------------------------------
切片地址: 0xc00009e060 底层数组地址: 0xc0000ae078

引用类型

func showAddr(s []int) ([]int,error){
	fmt.Printf("s %p,%p,%d,%d,%v\n",&s,&s[0],len(s),cap(s))
	//修改一个元素 
	if len(s) <= 0 {
		return nil, errors.New("切片不应为空值")
	}else {
		s[0] = 100
		return s ,nil
	}
}

func main() {
	s1 := []int{10,20,30}
	fmt.Printf("s1 %p, %p, %d, %d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1)

	s2 := s1 
	fmt.Printf("s2 %p, %p, %d, %d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2)
 	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
 	s3,_:= showAddr(s1)
 	fmt.Printf("s1 %p, %p, %d, %d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
 	fmt.Printf("s2 %p, %p, %d, %d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2)
 	fmt.Printf("s3 %p, %p, %d, %d, %v\n", &s3, &s3[0], len(s3), cap(s3), s3)
}
--------------------------------------------------------------------------------------
s1 0xc000004078, 0xc00000c198, 3, 3, [10 20 30]
s2 0xc0000040a8, 0xc00000c198, 3, 3, [10 20 30]
~~~~~~~~~~~~~~~~~~~~~~~~~~~
s 0xc0000040f0,0xc00000c198,3,3,%!v(MISSING)
s1 0xc000004078, 0xc00000c198, 3, 3, [100 20 30]
s2 0xc0000040a8, 0xc00000c198, 3, 3, [100 20 30]
s3 0xc0000040d8, 0xc00000c198, 3, 3, [100 20 30]

可以发现 ,我们定义的这些切片 他们的header值都不一样,但是底层数组用的是同一个。

那如果在上面showAddr函数中对切片增加一个元素会怎么样呢?
我们学习下 如何对切片进行追加元素操作

append:在切片的尾部追加元素,长度加1。
增加元素后,有可能超过当前容量,导致切片扩容。

func main() {
	//追加操作
	s1 := make([]int,3,5)
	fmt.Printf("s1 %p, %p, l=%-2d, c=%-2d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1) //[0,0,0]

	s2 := append(s1,1,2)
	fmt.Printf("s1 %p, %p, l=%-2d, c=%-2d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1) //[0,0,0]
	fmt.Printf("s2 %p, %p, l=%-2d, c=%-2d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2) //[0,0,0,1,2]

	s3 := append(s1,-1)
	fmt.Printf("s1 %p, %p, l=%-2d, c=%-2d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1) //[0,0,0]
	fmt.Printf("s2 %p, %p, l=%-2d, c=%-2d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2) //[0,0,0,-1,2]
	fmt.Printf("s3 %p, %p, l=%-2d, c=%-2d, %v\n", &s3, &s3[0], len(s3), cap(s3), s3) //[0,0,0,-1]

	s4 := append(s3,3,4,5)
	fmt.Printf("s1 %p, %p, l=%-2d, c=%-2d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1) //[0,0,0]
	fmt.Printf("s2 %p, %p, l=%-2d, c=%-2d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2) //[0,0,0,-1,2]
	fmt.Printf("s3 %p, %p, l=%-2d, c=%-2d, %v\n", &s3, &s3[0], len(s3), cap(s3), s3) //[0,0,0,-1]
	fmt.Printf("s4 %p, %p, l=%-2d, c=%-2d, %v\n", &s4, &s4[0], len(s4), cap(s4), s4)//[0,0,0,-1,3,4,5]
}
---------------------------------------------------------------------------------------------------------------
s1 0xc000004078, 0xc00000a450, l=3 , c=5 , [0 0 0]
s1 0xc000004078, 0xc00000a450, l=3 , c=5 , [0 0 0]
s2 0xc0000040a8, 0xc00000a450, l=5 , c=5 , [0 0 0 1 2]
s1 0xc000004078, 0xc00000a450, l=3 , c=5 , [0 0 0]
s2 0xc0000040a8, 0xc00000a450, l=5 , c=5 , [0 0 0 -1 2]
s3 0xc0000040f0, 0xc00000a450, l=4 , c=5 , [0 0 0 -1]
s1 0xc000004078, 0xc00000a450, l=3 , c=5 , [0 0 0]
s2 0xc0000040a8, 0xc00000a450, l=5 , c=5 , [0 0 0 -1 2]
s3 0xc0000040f0, 0xc00000a450, l=4 , c=5 , [0 0 0 -1]
s4 0xc000004150, 0xc00000e230, l=7 , c=10, [0 0 0 -1 3 4 5]  //长度超过了底层数组的容量,底层数组变更了

· append一定返回一个新的切片
· append可以增加若干元素

  • 如果增加元素时,当前长度+新增个数 <= cap则不扩容
    原切片使用元来的底层数组,返回的新切片也使用这个底层数组
    返回的新切片有新的长度
    原切片长度不变
  • 如果增加元素时,当前长度+新增个数 >cap 则进行扩容
    生成新的底层数组,新生成的切片使用该新数组,将旧数组中的数据拷贝到新数组中,并且追加元素
    原切片底层数组、长度、容量不变
    扩容策略
    (新版本1.18+)阈值变成了256,当扩容后的cap<256时,扩容翻倍,容量变成之前的2倍;当
    cap>=256时, newcap += (newcap + 3*threshold) / 4 计算后就是 newcap = newcap +
    newcap/4 + 192 ,即1.25倍后再加192。
    扩容是创建新的内部数组,把原内存数据拷贝到新内存空间,然后在新内存空间上执行元素追加操作。
    切片频繁扩容成本非常高,所以尽量早估算出使用的大小,一次性给够,建议使用make。

增加一个元素会导致扩容,会怎么样呢?请先在脑中思考

package main
import (
 "fmt"
)
func showAddr(s []int) []int {
 	fmt.Printf("s %p, %p, %d, %d, %v\n", &s, &s[0], len(s), cap(s), s)
 	// 修改一个元素
 	if len(s) > 0 {
 		s[0] = 123
	}
 	return s
}
func main() {
 	s1 := []int{10, 20, 33}
 	fmt.Printf("s1 %p, %p, %d, %d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
 	s2 := s1
 	fmt.Printf("s2 %p, %p, %d, %d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2)
 	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
 	s3 := showAddr(s1)
 	fmt.Printf("s1 %p, %p, %d, %d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
 	fmt.Printf("s2 %p, %p, %d, %d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2)
 	fmt.Printf("s3 %p, %p, %d, %d, %v\n", &s3, &s3[0], len(s3), cap(s3), s3)
}
运行结果
s1 0xc000008078, 0xc0000101b0, 3, 3, [10 20 33]
s2 0xc0000080a8, 0xc0000101b0, 3, 3, [10 20 33]
~~~~~~~~~~~~~~~~~~~~~~~~~~~
s  0xc0000080f0, 0xc0000101b0, 3, 3, [10 20 33]
s1 0xc000008078, 0xc0000101b0, 3, 3, [123 20 33]
s2 0xc0000080a8, 0xc0000101b0, 3, 3, [123 20 33]
s3 0xc0000080d8, 0xc0000101b0, 3, 3, [123 20 33]

这说明,底层数组是同一份,修改切片中的某个已有元素,那么所有切片都能看到。
那如果在上面showAddr函数中对切片增加一个元素会怎么样呢?

增加一个元素会导致扩容,会怎么样呢?

package main
import (
 	"fmt"
)
func showAddr(s []int) []int {
 	fmt.Printf("s %p, %p, %d, %d, %v\n", &s, &s[0], len(s), cap(s), s)
 	// // 修改一个元素
 	// if len(s) > 0 {
	 // s[0] = 123
 	// }
 	s = append(s, 100, 200) // 覆盖s,请问s1会怎么样
 	fmt.Printf("s %p, %p, %d, %d, %v\n", &s, &s[0], len(s), cap(s), s)
 	return s
}
func main() {
 	s1 := []int{10, 20, 30}
 	fmt.Printf("s1 %p, %p, %d, %d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
 	s2 := s1
 	fmt.Printf("s2 %p, %p, %d, %d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2)
 	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
 	s3 := showAddr(s1)
 	fmt.Printf("s1 %p, %p, %d, %d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
 	fmt.Printf("s2 %p, %p, %d, %d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2)
 	fmt.Printf("s3 %p, %p, %d, %d, %v\n", &s3, &s3[0], len(s3), cap(s3), s3)
}

运行结果
s1 0xc000008078, 0xc0000101b0, 3, 3, [10 20 30]
s2 0xc0000080a8, 0xc0000101b0, 3, 3, [10 20 30]
~~~~~~~~~~~~~~~~~~~~~~~~~~~
s 0xc0000080f0, 0xc0000101b0, 3, 3, [10 20 30]
s 0xc0000080f0, 0xc00000e390, 5, 6, [10 20 30 100 200]
s1 0xc000008078, 0xc0000101b0, 3, 3, [10 20 30]
s2 0xc0000080a8, 0xc0000101b0, 3, 3, [10 20 30]
s3 0xc0000080d8, 0xc00000e390, 5, 6, [10 20 30 100 200]

可以看到showAddr传入s1,但是返回的s3已经和s1不共用同一个底层数组了,分道扬镳了。
其实这里还是值拷贝,不过拷贝的是切片的标头值(Header)。标头值内指针也被复制,刚复制完大家
指向同一个底层数组罢了。但是仅仅知道这些不够,因为一旦操作切片时扩容了,或另一个切片增加元
素,那么就不能简单归结为“切片是引用类型,拷贝了地址”这样简单的话来解释了。要具体问题,具体
分析。

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

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

相关文章

【C++】红黑树源码剖析

目录 概述 算法 调整策略 源码 RBTree.h test.cpp 概述 红黑树&#xff0c;是一种二叉搜索树&#xff0c;每一个节点上有一个存储位表示节点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个节点着色方式的限制&#xff0c;红黑树确保没有一…

ChatGPT、文心一言、New Bing到底哪个更AI?

Hello 各位小伙伴&#xff0c;要说今年最火爆的 IP 有哪些&#xff0c;那一定少不了人工智能&#xff08;AI&#xff09;&#xff0c;市面上已经相继出现了我们熟知的 ChatGPT&#xff08;OpenAI&#xff09;、ChatGPT 国外镜像网站、文心一言&#xff08;百度&#xff09;、Ne…

MySQL --- DDL图形化工具表结构操作

一. 图形化工具 1. 介绍 前面我们讲解了DDL中关于数据库操作的SQL语句&#xff0c;在我们编写这些SQL时&#xff0c;都是在命令行当中完成的。大家在练习的时候应该也感受到了&#xff0c;在命令行当中来敲这些SQL语句很不方便&#xff0c;主要的原因有以下 3 点&#xff1a;…

redis的介绍和安装

文章目录 一、redis的介绍和安装1.1 初识redis1.1.1 redis是什么&#xff1f;1.1.2 redis能做什么&#xff1f; 1.2 redis的安装配置 一、redis的介绍和安装 1.1 初识redis 1.1.1 redis是什么&#xff1f; Redis是一个开源的内存数据存储系统&#xff0c;也可以被用作数据库、…

阿里云服务器vCPU和CPU有区别吗?

阿里云服务器vCPU是什么&#xff1f;vCPU和CPU有什么区别&#xff1f;CPU是指云服务器的中央处理器&#xff0c;一个CPU可以包含若干个物理核&#xff0c;一台云服务器ECS实例的CPU选项由CPU物理核心数和每核线程数决定&#xff0c;通过超线程HT&#xff08;Hyper-Threading&am…

探索三维世界【3】:Three.js 的 Geometry 几何体 与 Material 材质

探索三维世界【3】&#xff1a;Three.js 的 Material 材质 1、Geometry几何体2、Material 材质3、创建平面与材质4、多平面渲染 1、Geometry几何体 Three.js中的几何体Geometry是构成3D模型的基本单元之一&#xff0c;它定义了一个物体的形状和大小。Geometry包含了物体的顶点…

RK3568平台开发系列讲解(网络篇)网络包的接收过程

🚀返回专栏总目录 文章目录 一、内核接收网络包过程二、用户态读取网络包过程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们一起来梳理下网络包的接收过程。 一、内核接收网络包过程 硬件网卡接收到网络包之后,通过 DMA 技术,将网络包放入 Ring Buffer;…

PyTorch数据加载工具:高效处理常见数据集的利器

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

HTTP 缓存新鲜度 max-age

新鲜度 理论上来讲&#xff0c;当一个资源被缓存存储后&#xff0c;该资源应该可以被永久存储在缓存中。由于缓存只有有限的空间用于存储资源副本&#xff0c;所以缓存会定期地将一些副本删除&#xff0c;这个过程叫做缓存驱逐。另一方面&#xff0c;当服务器上面的资源进行了更…

使用ControlNet控制Stable-Diffusion出图人物的姿势

概述 在Stable-Diffusion&#xff08;以下简称SD&#xff09;出图中&#xff0c;我们往往需要对出图人物的姿势进行控制&#xff0c;这里我使用一个比较简单上手的方法&#xff0c;通过ControlNet可以很方便地对画面风格&#xff0c;人物姿势进行控制&#xff0c;从而生成更加…

Python —— Windows10下训练Yolov5分割模型并测试

附:Python —— Windows10下配置Pytorch环境、进行训练模型并测试(完整流程,附有视频)   效果 手机拍摄一段工位视频,上传到win10训练了yolov5分割鼠标的样本后推理效果截图。 训练准备 1、查看自己下载的Yolov5源码是否存在"segment"文件夹,该文件夹下存在分…

【Python入门篇】——Python基础语法(字面量注释与变量)

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; Python入门&#xff0c;本专栏主要内容为Python的基础语法&#xff0c;Python中的选择循环语句…

有限等待忙等、让权等待死等、互斥遵循的几大原则——参考《天勤操作系统》,柳婼的博客

参考柳婼的博客 一、 有限等待&&死等 有限等待: 对请求访问的临界资源的进程&#xff0c;应该保证有限的时间进入临界区&#xff0c;以免陷入死等状态。受惠的是进程自己 死等: 进程在有限时间内根本不能进入临界区&#xff0c;而一直尝试进入陷入一种无结果的等待状…

在字节跳动做了6年软件测试,4月无情被辞,想给划水的兄弟提个醒

先简单交代一下背景吧&#xff0c;某不知名 985 的本硕&#xff0c;17 年毕业加入字节&#xff0c;以“人员优化”的名义无情被裁员&#xff0c;之后跳槽到了有赞&#xff0c;一直从事软件测试的工作。之前没有实习经历&#xff0c;算是6年的工作经验吧。 这6年之间完成了一次…

TIM编码器接口

一、知识点 1、Encoder Interface 编码器接口的工作流程 编码器接口可接收增量&#xff08;正交&#xff09;编码器的信号&#xff0c;根据编码器旋转产生的正交信号脉冲&#xff0c;自动控制CNT自增或自减&#xff0c;从而指示编码器的位置、旋转方向和旋转速度 2、编码器接口…

6.2.1邻接矩阵法

接下来我们将认识图的几种存储结构&#xff1a; 邻接矩阵&#xff0c;邻接表&#xff0c;十字链表&#xff0c;邻接多重表 图的存储 1&#xff09;邻接矩阵法 0表示邻接vertex不邻接 只需要一个二位数组就可以实现&#xff1a; 顶点虽然是char类型&#xff0c;但可以存储更加…

搭建vue3+vite工程

搭建vue3vite工程 目录 搭建vue3vite工程 一、官方-文档-快速上手 二、详细截图及步骤 2.1、安装nvm 2.2、 用nvm安装多版本可切换的node.js版本 2.3、 按照官方文档初始化最近版本的vue3 三、脚本配置与调试 3.1、"2.3、"所产生的配置及脚本命令 3.2、脚本…

SpringCloud学习笔记06

九十五、Cloud Alibaba简介 0、why会出现SpringCloud alibaba Spring Cloud Netflix项目进入维护模式 1、是什么 官网&#xff1a;spring-cloud-alibaba/README-zh.md at 2.2.x alibaba/spring-cloud-alibaba GitHub 2、能干嘛 3、去哪下 spring-cloud-alibaba/README-…

Linux——理解文件系统和动静态库

一、理解文件系统 使用命令查看信息 1&#xff0c;使用ls -l查看文件属性和文件内容 2&#xff0c;stat文件名查看更多信息 3&#xff0c;inode Linux中的文件分为文件属性和文件内容。文件属性又称为元信息。保存在inode结构中&#xff0c;inode是一个文件属性的集合。一个文…

Oracle SQL执行计划操作(13)——其他相关操作

该类操作主要包括以上未进行讲解的其他相关操作。根据不同的具体SQL语句及其他相关因素,如下各操作可能会出现于相关SQL语句的执行计划。 1)SELECT STATEMENT 检索表中数据。该操作出现于通过select语句检索表中数据时产生的执行计划。该操作具体如图15-1中节点0所示。 图1…