重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
go有切片slice类型,python有列表和元组,这两种语言都有切片操作。
成都创新互联专注于尉氏企业网站建设,响应式网站建设,成都商城网站开发。尉氏网站建设公司,为尉氏等地区提供建站服务。全流程按需规划网站,专业设计,全程项目跟踪,成都创新互联专业和态度为您提供的服务
但是它们的切片操作是完全不同的。
首先说第一个,go的切片,其成员是相同类型的,python的列表和元组则不限制类型。
两种语言都有[a:b]这种切片操作,意义也类似,但是go的a、b两个参数不能是负数,python可以是负数,此时就相当于从末尾往前数。
两种语言都有[a:b:c]这种切片操作,意义却是完全不同的。go的c,表示的是容量;而python的c表示的是步长。
但是最大的不同,还是:
python的切片产生的是新的对象,对新对象的成员的操作不影响旧对象;go的切片产生的是旧对象一部分的引用,对其成员的操作会影响旧对象。
究其原因,还是底层实现的不同。
go的切片,底层是一个三元组,一个指针,一个长度,一个容量。指针指向一块连续的内存,长度是已有成员数,容量是最大成员数。切片时,一般并不会申请新的内存,而是对原指针进行移动,然后和新的长度、容量组成一个切片类型值返回。也就是说,go的切片操作通常会和生成该切片的切片共用内存。
不仅是切片,字符串、数组的切片也是一样的,通常会共用内存。
当然也有异常情况,那就是切片时,提供的容量过大,此时会申请新内存并拷贝;或者对切片append超出容量,也会如此。这时,新的切片,才不会和老切片共享内存。(如果你切片/创建时提供的容量小于长度,会panic)
python的列表,其实是个指针数组。当然在下层也会提供一些空位之类的,但基本就是个数组。对它们切片,会创建新的数组,注意,是创建新的数组!python的列表可没有容量的概念。
这其实也体现了脚本语言和编译语言的不同。虽然两个语言都有类似的切片操作;但是python主要目标是方便;go主要目标却是快速(并弥补丢弃指针运算的缺陷)。 a
//从数组中获取 切片
span style="color:#c0c0c0;" /spanspan style="color:#000080;font-weight:600;"var/spanspan style="color:#c0c0c0;" /spansliceArrayspan style="color:#c0c0c0;" /spanspan style="color:#000000;"[/spanspan style="color:#800080;"10/spanspan style="color:#000000;"]/spanspan style="color:#000080;"int/spanspan style="color:#c0c0c0;" /spanspan style="color:#000000;"=/spanspan style="color:#c0c0c0;" /spanspan style="color:#000000;"[/spanspan style="color:#800080;"10/spanspan style="color:#000000;"]/spanspan style="color:#000080;"int/spanspan style="color:#000000;"{/spanspan style="color:#800080;"0/spanspan style="color:#000000;",/spanspan style="color:#c0c0c0;" /spanspan style="color:#800080;"1/spanspan style="color:#000000;",/spanspan style="color:#c0c0c0;" /spanspan style="color:#800080;"2/spanspan style="color:#000000;",/spanspan style="color:#c0c0c0;" /spanspan style="color:#800080;"3/spanspan style="color:#000000;",/spanspan style="color:#c0c0c0;" /spanspan style="color:#800080;"4/spanspan style="color:#000000;",/spanspan style="color:#c0c0c0;" /spanspan style="color:#800080;"5/spanspan style="color:#000000;",/spanspan style="color:#c0c0c0;" /spanspan style="color:#800080;"6/spanspan style="color:#000000;",/spanspan style="color:#c0c0c0;" /spanspan style="color:#800080;"7/spanspan style="color:#000000;",/spanspan style="color:#c0c0c0;" /spanspan style="color:#800080;"8/spanspan style="color:#000000;",/spanspan style="color:#c0c0c0;" /spanspan style="color:#800080;"9/spanspan style="color:#000000;"}/span
//指定 begin index 和end Index
// begin index 和end index 都指定的情况 包括 begin index, 不包括end index index 从0开始
var slice2 []int = sliceArray[5:9]
// slice3 和slice2 指向同一个底层的数组
var slice3 []int = sliceArray[:]
//输出结果 [5 6 7 8] 4 5
fmt.Println(slice2, len(slice2), cap(slice2))
//测试添加元素 and 扩容之后的数组操作情况
//通过切片直接操作数组的信息
slice2[0] = 100
sliceArray[6] = 66
//多个切片 操作底层数组
slice3[7] = 77
//输出结果 [100 66 77 8] 4 5 三个赋值 都影响了切片的底层数组
fmt.Println(slice2, len(slice2), cap(slice2))
//输出结果 [0 1 2 3 4 100 66 77 8 9] 10 10
fmt.Println(slice3, len(slice3), cap(slice3))
//输出结果 [0 1 2 3 4 100 66 77 8 9] 三个赋值 都影响了 原始数组
fmt.Println("sliceArray:", sliceArray)
//扩容
// 操作的还是 sliceArray
slice2 = append(slice2, 99)
//输出结果 [100 66 77 8 99] 5 5
fmt.Println(slice2, len(slice2), cap(slice2))
// 输出结果 sliceArray: [0 1 2 3 4 100 66 77 8 99]
fmt.Println("sliceArray:", sliceArray)
slice2 = append(slice2, 1000)
//输出结果 [100 66 77 8 99 1000] 6 10 此处切片已经扩容(两倍扩容), 并保留了原始的内容
fmt.Println(slice2, len(slice2), cap(slice2))
// 输出结果 sliceArray: [0 1 2 3 4 100 66 77 8 99] 原来的数组不再受到影响了
fmt.Println("sliceArray:", sliceArray)
//通过index操作 元素 判断扩容后的底层数组是否是部分会在原始的 数组上面
slice2[1] = 999
//输出结果 [100 999 77 8 99 1000] 6 10
fmt.Println(slice2, len(slice2), cap(slice2))
// 输出结果 sliceArray: [0 1 2 3 4 100 66 77 8 99] 说明是 重新找了一块内存, 和以前的数组完全没有关系
fmt.Println("sliceArray:", sliceArray)
数组
数组是内置(build-in)类型,是一组同类型数据的集合。
数组的初始化有多种形式
长度为5的数组,其元素值依次为:1,2,3,4,5
长度为5的数组,其元素值依次为:1,2,0,0,0 。在初始化时没有指定初值的元素将会赋值为其元素类型int的默认值0,string的默认值是 ""
长度为5的数组,其长度是根据初始化时指定的元素个数决定的
长度为5的数组,key:value,其元素值依次为:0,0,1,2,3。在初始化时指定了2,3,4索引中对应的值:1,2,3
长度为5的数组,起元素值依次为:0,0,1,0,3。由于指定了最大索引4对应的值3,根据初始化的元素个数确定其长度为5
切片
数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型 Slices 切片。
切片可以通过数组来初始化,也可以通过内置函数make()初始化。初始化时len=cap,在追加元素时如果容量cap不足时将按len的 2 倍扩容。
直接初始化切片, [] 表示是切片类型, {1,2,3} 初始化值依次是1,2,3.其cap=len=3
初始化切片s,是数组arr的引用
将arr中从下标startIndex到endIndex-1 下的元素 创建为一个新的切片
缺省endIndex时将表示一直到arr的最后一个元素
缺省startIndex时将表示从arr的第一个元素开始
通过切片s初始化切片s1
通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片
衍生类型,interface{} , map, [] ,struct等
map类似于java的hashmap,python的dict,php的hash array。
常规的for循环,可以用for k,v :=range m {}. 但在下面清空有一个坑注意:
著名的map[string]*struct 副本问题
结果:
Go 中不存在引用传递,所有的参数传递都是值传递,而map是等同于指针类型的,所以在把map变量传递给函数时,函数对map的修改,也会实质改变map的值。
slice类似于其他语言的数组(list,array),slice初始化和map一样,这里不在重复
除了Pointer数组外,len表示使用长度,cap是总容量,make([]int, len, cap)可以预申请 比较大的容量,这样可以减少容量拓展的消耗,前提是要用到。
cap是计算切片容量,len是计算变量长度的,两者不一样。具体例子如下:
结果:
分析:cap是计算当前slice已分配的容量大小,采用的是预分配的伙伴算法(当容量满时,拓展分配一倍的容量)。
append是slice非常常用的函数,用于添加数据到slice中,但如果使用不好,会有下面的问题:
预期是[1 2 3 4 5 6 7 8 9 10], [1 2 3 4 5 6 7 8 9 10 11 12],但实际结果是:
注意slice是值传递,修改一下:
输出如下:
== 只能用于判断常规数据类型,无法使用用于slice和map判断,用于判断map和slice可以使用reflect.DeepEqual,这个函数用了递归来判断每层的k,v是否一致。
当然还有其他方式,比如转换成json,但小心有一些异常的bug,比如html编码,具体这个json问题,待后面在分析。
Go 中数组的长度是不可改变的,而 Slice 解决的就是对不定长数组的需求。他们的区别主要有两点。
数组:
切片:
注意 1
虽然数组在初始化时也可以不指定长度,但 Go 语言会根据数组中元素个数自动设置数组长度,并且不可改变。切片通过 append 方法增加元素:
如果将 append 用在数组上,你将会收到报错:first argument to append must be slice。
注意 2
切片不只有长度(len)的概念,同时还有容量(cap)的概念。因此切片其实还有一个指定长度和容量的初始化方式:
这就初始化了一个长度为3,容量为5的切片。
此外,切片还可以从一个数组中初始化(可应用于如何将数组转换成切片):
上述例子通过数组 a 初始化了一个切片 s。
当切片和数组作为参数在函数(func)中传递时,数组传递的是值,而切片传递的是指针。因此当传入的切片在函数中被改变时,函数外的切片也会同时改变。相同的情况,函数外的数组则不会发生任何变化。
定义一个切片,然后让切片去引用一个已经创建好的数组。基本语法如下:
索引1:切片引用的起始元素位
索引2:切片只引用该元素位之前的元素
例程如下:
在该方法中,我们未指定容量cap,这里的值为5是系统定义的。
在方法一中,可以用arr数组名来操控数组中的元素,也可以通过slice切片来操控数组中的元素。切片是直接引用数组,数组是事先存在的,程序员是可见的。
通过 make 来创建切片,基本语法如下:
make函数第三个参数cap即容量是可选的,如果一定要自己注明的话,要注意保证cap≥len。
用该方法可以 指定切片的大小(len)和容量(cap)
例程如下:
由于未赋值系统默认将元素值置为0,即:
数值类型数组: 默认值为 0
字符串数组: 默认值为 ""
bool数组: 默认值为 false
在方法二中,通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素。
定义一个切片,直接就指定具体数组,使用原理类似于make的方式。
例程如下: