队列的概念在顺序队列中,而使用循环队列的目的主要是规避假溢出造成的空间浪费,在使用循环队列处理假溢出时,主要有三种解决方案
本文提供后两种解决方案。
顺序队和循环队列是一种特殊的线性表,与顺序栈类似,都是使用一组地址连续的存储单元依次存放自队头到队尾的数据元素,同时附设队头(front)和队尾(rear)两个指针,但我们要明白一点,这个指针并不是指针变量,而是用来表示数组当中元素下标的位置。
本文使用切片来完成的循环队列,由于一开始使用三册饥个穗洞参数的make关键字创建切片,在输出的结果中不包含nil值(看起来很舒服),而且在验证的过程中发现使用append()函数时切片内置的cap会发生变化,在消除了种种障碍后得到了一个四不像的循环队列,即设置的指针是顺序队列的指针,但实际上进行的操作是顺序队列的操作。最后是对make()函数和append()函数的一些使用体验和小结,队列的应用放在链队好了。
官方描述(片段)
即切片是一个抽象层,底层是对数组的引用。
当我们使用
构建出来的切片的每个位置的值都被赋为interface类型的初始值nil,但是nil值也是有大小的。
而使用
来进行初始化时,虽然生成的切片中不包含nil值,但是无法通过设置的指针变量来完成入队和出队的操作,只能使用append()函数来进行操作
在go语言中,切片是一片连续的内存空间加上长度与容量的标识,比数组更为常用。使用append关键字向切片中追加元素也是常见的切片操作
正是基于此,在使用go语言完成循环队列时,州族返首先想到的就是使用make(type,len,cap)关键字方式完成切片初始化,然后使用append()函数来操作该切片,但这一方式出现了很多问题。在使用append()函数时,切片的cap可能会发生变化,用不好就会发生扩容或收缩。最终造成的结果是一个四不像的结果,入队和出队操作变得与指针变量无关,失去了作为循环队列的意义,用在顺序队列还算合适。
参考博客:
Go语言中的Nil
Golang之nil
Go语言设计与实现
GO语言编程关于切片教程遇到个问题?
append的第二个参数是可变长参数,你这样写1,2就会追加2个元素这符合预期效果
Go切片数组深度解析Go中的分片数组,实际上有点类似于Java中的ArrayList,是一个可以扩展的数组,但是Go中的切片由比较灵活,它和数组很像,也是基于数组,所以在了解Go切片前我们先了解下数组。
数组简单描述就由相同类型元素组成的数据结构,在创建初期就确定了长度,是不可变的。
但是Go的数组类型又和C与Java的数组类型不一样,NewArray用于创建一个数组,从源码中可以看出最后返回的是Array{}的指针,并不是第一个元素的指针,在Go中数组属于值类型,在进行传递时,采取的是值传递,通过拷贝整个数组。Go语言的数组是一种有序的struct。
Go语言的数组有两种不同的创建方式,一种是显示的初始化,一种是隐式的初始化。
注意一定是使用[...]T进行创建,使用三个点的隐式创建,编译器会对数组的大小进行推导,只是Go提供的一种语法糖。
其次,Go中数组的类型,是由数值类型和长度两个一起确定的。[2]int和[3]int不是同一个类型,不能进行传参和比较,把数组理解为类型和长度两个属性的结构体,其实就一目了然了。
Go中的数组属于值类型,通常应该存储于栈中,局部变量依然会根据逃逸分析确定存储栈还是堆中。
编译器对数组函数中做两种不同的优化:
在静态区完成赋值后复制到栈中。
总结起来,在不考虑逃逸分析的情况下,如果数组中元素的个数小于或者等于4个,那么所有的变量会直接在栈上初始化,如果数组元素大于4个,变量就会在静态存储区初始化然后拷贝到栈上。
由于数组是值类型,那么赋值和函数传参操作都会复制整个数组数据。
不管是赋值或函数传参,地址都不一致,发生了拷贝。如果数组的数据较大,则会消耗掉大量内存。那么为了减少拷贝我们可以主动的传递指针呀。
地址是一样的,不过传指针会有一个弊端,从打印结果可以看到,指针地址都是同一个,万一原数组的指针指向更改手腔了,那么函数里面的指针指向都会跟空乱着更改。斗薯档
同样的我们将数组转换为切片,通过传递切片,地址是不一样的,数组值相同。
切片是引用传递,所以它们不需要使用额外的内存并且比使用数组更有效率。
所以,切片属于引用类型。
通过这种方式可以将数组转换为切片。
中间不加三个点就是切片,使用这种方式创建切片,实际上是先创建数组,然后再通过第一种方式创建。
使用make创建切片,就不光编译期了,make创建切片会涉及到运行期。1.切片的大小和容量是否足够小;
切片是否发生了逃逸,最终在堆上初始化。如果切片小的话会先在栈或静态区进行创建。
切片有一个数组的指针,len是指切片的长度,cap指的是切片的容量。
cap是在初始化切片是生成的容量。
发现切片的结构体是数组的地址指针arrayunsafe.Pointer,而Go中数组的地址代表数组结构体的地址。
slice中得到一块内存地址,array[0]或者unsafe.Pointer(array[0])。
也可以通过地址构造切片
nil切片:指的unsafe.Pointer为nil
空切片:
创建的指针不为空,len和cap为空
当一个切片的容量满了,就需要扩容了。怎么扩,策略是什么?
如果原来数组切片的容量已经达到了最大值,再想扩容,Go默认会先开一片内存区域,把原来的值拷贝过来,然后再执行append()操作。这种情况对现数组的地址和原数组地址不相同。
从上面结果我们可以看到,如果用range的方式去遍历一个切片,拿到的Value其实是切片里面的值拷贝,即浅拷贝。所以每次打印Value的地址都不变。
由于Value是值拷贝的,并非引用传递,所以直接改Value是达不到更改原切片值的目的的,需要通过slice[index]获取真实的地址。
Go语言list(列表)2021-11-10
列表是一种非连续的存储容器,有多个节点组成,节点通过一些变量记录彼此之间的关系
单链表和双链表就是列表的两种方法。丛辩
原理:A、B、C三个人,B懂A的电话,C懂B的电话只是单方知道号码,这样就形成了一个单链表结构。
如果C把自己的号码给B,B把自己的号码给A,因为是双方都知道对方的号码,这样就形成渗链缺了一个双链表结构
如果B换号码了,他需要通知AC,把自己的号码删了,这个过程就是列表的删除操作。
在Go语言中,列表使用container/list包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作。
列表初始化的两种办法
列表没有给出具体的元素类型的限制,所以列表的元素可以是任意类型的,
例如给列表中放入了一个interface{}类型的值,取出值后,如果要将interface{}转换为其他类型将会发生宕机。
双链表支持从队列前方或后方插入元素,分别对应的方法是PushFront和PushBack。
列表插入函数的返回值会提供一唤中个*list.Element结构,这个结构记录着列表元素的值以及与其他节点之间的关系等信息,从列表中删除元素时,需要用到这个结构进行快速删除。
遍历完也能看到最后的结果
学习地址:
go语言数组,切片和字典的区别和联系、数组
与其他大多数语言类似,Go语言的数组也是一个元素类型相同的定长的序列。
(1)数组的创建。
数组有3种创建方式毁埋:[length]Type、[N]Type{value1,value2,...,valueN}、[...]Type{value1,value2,...,valueN}如下:
复制代码代码如下:
functest5(){
variarray1[5]int32
variarray2[5]int32=[5]int32{1,2,3,4,5}
iarray3:=[5]int32{1,2,3,4,5}
iarray4:=[5]int32{6,7,8,9,10}
iarray5:=[...]int32{11,12,13,14,15}
iarray6:=[4][4]int32{{1},{1,2},{1,2,3}}
fmt.Println(iarray1)
fmt.Println(iarray2)
fmt.Println(iarray3)
fmt.Println(iarray4)
fmt.Println(iarray5)
fmt.Println(iarray6)
}
结果:
[00000]
[12345]
[12345]
[678910]
[1112131415]
[[1000][1200][1230][0000]]
我们看数组iarray1,只声明,并未赋值,Go语言帮我们自动赋值为0。再看iarray2和iarray3,我们可以看到,Go语言的声明,可以表明类型,也可以不表明类型,variarray3=[5]int32{1,2,3,4,5}也是完全没问题的。
(2)数组的容量和长度是一样的。cap()函数和len()函数均输出数组的容量(即长度)。如:
复制代码代码如下:
functest6(){
iarray4:=[5]int32{6,7,8,9,10}
fmt.Println(len(iarray4))
fmt.Println(cap(iarray4))
}
输出都是5。
(3)使用:
复制代码代码如下:
functest7(){
iarray7:=[5]string{"aaa",`bb`,"可以啦","叫我说什么好","()"}
fmt.Println(iarray7)
fori:=rangeiarray7{
fmt.Println(iarray7[i])
}
}
二、切片
Go语言中,切片是长度可变、容量固定的相同的元素序列。Go语言的切片本质是一个数组。容量固定是因为数组的长度是固定的,切片的容量即隐藏数组的长度。长度可变指的是在数组长度的范围内可变。
(1)切片的创建。
切片冲余中的创建有4种方式:
1)make([]Type,length,capacity)
2)make([]Type,length)
3)[]Type{}
4)[]Type{value1,value2,...,valueN}
从3)、4)可见,创建切片跟创建数组唯一的区别在于Type前的“[]”中是否有数字,为空,则代表切片,否则则代表数组。因为切片是长度可变的。如下是创建切片的示例:
复制代码代码如下:
functest8(){
slice1:=make([]int32,5,8)
slice2:=make([]int32,9)
slice3:=[]int32{}
slice4:=[]int32{1,2,3,4,5}
fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)
fmt.Println(slice4)
}
输出为散山:
[00000]
[000000000]
[]
[12345]
如上,创造了4个切片,3个空切片,一个有值的切片。
(2)切片与隐藏数组:
一个切片是一个隐藏数组的引用,并且对于该切片的切片也引用同一个数组。如下示例,创建了一个切片slice0,并根据这个切片创建了2个切片slice1和slice2:
复制代码代码如下:
functest9(){
slice0:=[]string{"a","b","c","d","e"}
slice1:=slice0[2:len(slice0)-1]
slice2:=slice0[:3]
fmt.Println(slice0,slice1,slice2)
slice2[2]="8"
fmt.Println(slice0,slice1,slice2)
}
输出为:
[abcde][cd][abc]
[ab8de][8d][ab8]
可见,切片slice0、slice1和slice2是同一个底层数组的引用,所以slice2改变了,其他两个都会变。
(3)遍历、修改切片:
复制代码代码如下:
functest10(){
slice0:=[]string{"a","b","c","d","e"}
fmt.Println("\n~~~~~~元素遍历~~~~~~")
for_,ele:=rangeslice0{
fmt.Print(ele,"")
ele="7"
}
fmt.Println("\n~~~~~~索引遍历~~~~~~")
forindex:=rangeslice0{
fmt.Print(slice0[index],"")
}
fmt.Println("\n~~~~~~元素索引共同使用~~~~~~")
forindex,ele:=rangeslice0{
fmt.Print(ele,slice0[index],"")
}
fmt.Println("\n~~~~~~修改~~~~~~")
forindex:=rangeslice0{
slice0[index]="9"
}
fmt.Println(slice0)
}
如上,前三种循环使用了不同的forrange循环,当for后面,range前面有2个元素时,第一个元素代表索引,第二个元素代表元素值,使用“_”则表示忽略,因为go语言中,未使用的值会导致编译错误。
只有一个元素时,该元素代表索引。
只有用索引才能修改元素。如在第一个遍历中,赋值ele为7,结果没有作用。因为在元素遍历中,ele是值传递,ele是该切片元素的副本,修改它不会影响原本值,而在第四个遍历——索引遍历中,修改的是该切片元素引用的值,所以可以修改。
结果为:
~~~~~~元素遍历~~~~~~
abcde
~~~~~~索引遍历~~~~~~
abcde
~~~~~~元素索引共同使用~~~~~~
aabbccddee
~~~~~~修改~~~~~~
[99999]
(4)、追加、复制切片:
复制代码代码如下:
functest11(){
slice:=[]int32{}
fmt.Printf("slice的长度为:%d,slice为:%v\n",len(slice),slice)
slice=append(slice,12,11,10,9)
fmt.Printf("追加后,slice的长度为:%d,slice为:%v\n",len(slice),slice)
slicecp:=make([]int32,(len(slice)))
fmt.Printf("slicecp的长度为:%d,slicecp为:%v\n",len(slicecp),slicecp)
copy(slicecp,slice)
fmt.Printf("复制赋值后,slicecp的长度为:%d,slicecp为:%v\n",len(slicecp),slicecp)
}
追加、复制切片,用的是内置函数append和copy,copy函数返回的是最后所复制的元素的数量。
(5)、内置函数append
内置函数append可以向一个切片后追加一个或多个同类型的其他值。如果追加的元素数量超过了原切片容量,那么最后返回的是一个全新数组中的全新切片。如果没有超过,那么最后返回的是原数组中的全新切片。无论如何,append对原切片无任何影响。如下示例:
复制代码代码如下:
functest12(){
slice:=[]int32{1,2,3,4,5,6}
slice2:=slice[:2]
_=append(slice2,50,60,70,80,90)
fmt.Printf("slice为:%v\n",slice)
fmt.Printf("操作的切片:%v\n",slice2)
_=append(slice2,50,60)
fmt.Printf("slice为:%v\n",slice)
fmt.Printf("操作的切片:%v\n",slice2)
}
如上,append方法用了2次,结果返回的结果完全不同,原因是第二次append方法追加的元素数量没有超过slice的容量。而无论怎样,原切片slice2都无影响。结果:
slice为:[123456]
操作的切片:[12]
slice为:[12506056]
操作的切片:[12]
Go语言排序与搜索切片Go语言标准库中提供了sort包对整型,浮点型,字符串型切片进行排序,检查一个切片是否排好序,使用二分法搜索函数在一个有序切片中搜索一个元素等功能。
关于sort包内的函数说明与使用,请查看
在这里简单讲几个sort包中常用的函数
在Go语言中,对字符串的排序都是按照字节排序,也就是说在对字符串排序时是区分大小写的。
二分搜索算法
Go语言中提供了一个使用二分搜索算法的sort.Search(size,fn)方液段灶法:每次只需要比较㏒?n个元素,其中n为切片中元素的总数。
sort.Search(size,fn)函数接受两个参数:所处理的切片的长度和一个将目标元素与有序切片的元素相比较的函数,该函数是一个闭包,如果该有序切片是闹扮升序排列,那么在判断时使用有序切片的元素=目燃旁标元素。该函数返回一个int值,表示与目标元素相同的切片元素的索引。
在切片中查找出某个与目标字符串相同的元素索引
logo设计
创造品牌价值
¥500元起
APP开发
量身定制,源码交付
¥2000元起
商标注册
一个好品牌从商标开始
¥1480元起
公司注册
注册公司全程代办
¥0元起
查
看
更
多