首页 > 编程知识 正文

两个数组比较,char数组比较

时间:2023-05-04 16:06:51 阅读:157323 作者:3980

数组和切片开头可能存在的疑问正文1 .数组和切片2 .传递值和传递引用3 .记忆差异4 .扩展总结

前言

学习Go语言时对序列和切片的存储方法和特性存在疑问,经过一定的实验得出了一些结论,现记录如下。

*后续实验代码省略package main和import 'fmt '

*备忘录是个人理解和互联网资源的摘要,谢谢。

*如果错了,欢迎发表评论

序列和切片有什么不同呢? 与C/C不同的特性,以数组作为函数参数时的更改不会影响原始数组,但切片会影响。 数组的起始元素地址和数组地址相同,但片不同。 数组和切片保存方法的区别是什么? 从数组创建的切片可以按照与数组相同的方式变化,也可以按照不同的方式变化。 切片的两种创建方法之间的差异。 正文1 .数组和切片简单地说,数组是在编译阶段就已经可以决定大小的容器,或者通俗地说是给定长度的向量。 例如,var a [10]int创建长度为10的数组,a :=[ . ] int { 0,1,2,3 }也是长度为4的数组。 数组在定义时使用[…]让编译器自己选择大小。

可以将切片视为不包含长度的“数组”(也没有[…] ) (vara ) []int或由make函数创建的“数组”。

这两者最明显的区别在于,一方决定长度,另一方不确定。 由于Go语言对数据类型的检查非常严格,数组和片不能相互用作对方函数的参数,不同长度的数组也不兼容。 如下所示。

funcalpha(x ) int ) /函数参数为片(/functionbody1) funcbeta ) int ) /函数参数为长度为8的数组)///functionbody2) funcgamma ) ) 数组与切片beta(a ) ) /不同。 从数组到切片只要使用定义即可,从切片到数组可以一个一个地从切片搬运(不知道是否是更有效的方法)。

2 .如果传递值和传递引用位于C/C,且数组为函数参数,则函数内部对数组的变更实际上是对原数组的变更,而Go语言则不是。 如下所示,试图将最初的要素设为9999的函数foo失效了,但切片的安装成功了goo :

funcfoo(x )3) int ) /排列版x(0)=9999 ) /排列的开头要素为9999 ) funcgoo ) x ) int ) /切片版x(0)=9999///同样的操作) }func main 2 2],根据情况b3360=a65: )//b为a的片版goo ) b ) /在调用片版中实现fmt.println } go中数组作为自变量被复制,与其他变量一样,函数

切片为什么不是呢? 究其原因,需要知道两者的内部存储方式,第三个问题需要我们卖关子。

3 .存储差异首先开始我们的实验。

func main () a:=[3]int ) 0,1, 2 ) /数组b :=a[:]//片str1 :=`//格式输出字符串,对于精彩的输出,需要地址a: %p//a的地址a[2]: %p//需要a[2]的地址` sttet () 33660%p`fmt.printf(str1,a,a[0],a[1], a[2]//*输出为以下a :0 xc 00000 a1 c8a [0] :0 xc 00000 a1 c8a [1] :0 xc 00000 a1 d0a [2] :0 xc 0000 a1 D8 */fmt.printt :0 xc 00000 a1 c8b [1] :0 xc 00000 a1 d0b [2] :0 xc 0000 a1 D8 (/)根据实验结果进行数组,而片的各元素也与原始数组对应的元素地址相同。

但是请注意,片b的地址不是b[0]的地址。 这就是切片和序列差异的重要表现。

实际上,切片不是直接指向存储在数组中的第一个元素,而是包含值传递三个部分的结构。 Go将切片的元素访问设计为同一数组的索引访问,使中间的一层结构透明。 这解释了为什么b的地址不等于第一个元素的地址。 这是因为b实际上有结构的地址。 看看源代码,就知道这个了。

p> // 路径~Gosrcruntimeslice.gotype slice struct {array unsafe.Pointer//首元素指针len int//长度cap int//容量}

再回到我们的第二点上,实际上切片和数组作为参数调用时都会进行复制,但Go进行的是浅层的复制(不会将指针指向的数组也进行复制),因此切片的首元素指针值被复制进了函数内,后续的变化仍然是原切片。
换句话说,即是从原切片指向对应元素变为了新复制的切片指向对应元素,指的人换了但指的位置不变。我们用实验来佐证这一观点:

func foo(x []int) {str1 := `//好看的格式化打印字符串 x: %px[0]: %px[1]: %px[2]: %p`fmt.Printf(str1,&x,&x[0],&x[1],&x[2])//打印各个地址}func main() {a := [3]int{0,1,2}//被用来创建切片的备胎b := a[:]//主角str2 := ` b: %pb[0]: %pb[1]: %pb[2]: %p`fmt.Printf(str2,&b,&b[0],&b[1],&b[2])//b的地址信息/*输出如下 b: 0xc000004078b[0]: 0xc00000a1c8b[1]: 0xc00000a1d0b[2]: 0xc00000a1d8*/foo(b)/*输出如下 x: 0xc000004090x[0]: 0xc00000a1c8x[1]: 0xc00000a1d0x[2]: 0xc00000a1d8*/}

可以看到的确除了切片本身地址不同,其各个元素地址完全相同,实验成功。

4.扩容

到这里,我们的大多数疑惑都已经消除了,笔者还剩一点的想法。
数组定长以后才便于被函数调用,调用栈才能够留给数组正好的空间来复制到函数内。而另一方面,切片是可以动态收缩的容器,前面的实验告诉我们基于数组构造的切片指向的仍然是原数组,那么切片不断边长,后续加入的元素存放到哪里呢?
接到原数组的后面显然不现实,原因有几点:

数组为局部变量时大部分情况下会存放在栈内,因此数组后没有空闲空间若由数组构造的切片不位于数组右端,如数组定义为var a [10]int,切片定义为b:=a[:5],此时b后添加的一个元素b = append(b, 10)10放到原数组后面破坏了数组物理和逻辑上的连续性。

另外,我们会发现对从数组创建的切片进行更改时,原数组有时发生变化,有时则不变。
实际上,刚(由数组)创建的切片指针指向切片的首元素,大小为切片的大小(像废话hhhhh),容量为切片的头索引到原数组末端的长度。规范来说,若有如下的切片构造:

var a [length]intvar b []int = a[low:high]

则相当于(用于展示,slice无法从外面调用)

var a [length]intvar b slice = slice {array: &a[low]//首元素指针len: high - low//长度cap: length - low//容量}

由实验可以很好地得到这一结论:

func print(x []int, low int, high int) {str := `array: %plen: %dcap: %d`fmt.Printf("tlength=5, low=%d, high=%d.", low, high)fmt.Printf(str, &x[0], len(x), cap(x))}func main() {a := [5]int{0, 1, 2, 3, 4}b := a[:]//low=0, high=5, length=5c := a[:2]//low=0, high=2, length=5d := a[1:]//low=1, high=5, length=5print(b, 0, 5)/*输出如下length=5, low=0, high=5.array: 0xc00000c3c0len: 5cap: 5*/print(c, 0, 2)/*输出如下length=5, low=0, high=2.array: 0xc00000c3c0len: 2cap: 5*/print(d, 1, 5)/*输出如下length=5, low=1, high=5.array: 0xc00000c3c8len: 4cap: 4*/}

可以看到符合前面的结论。
另一方面,在切片创建完成后不断使用append在尾部加入元素会发生什么呢?结论是:当没有越过原数组的界前会在原数组上不断用新值往后覆盖;越界后切片会将数据复制到新的一个更大的区域(即扩容)后插入新值。
来个例子即可一目了然:

func print(a [6]int, b []int) {str := `&b = %p//打印切片的首元素地址a = %v//打印原数组a的值b = %v //打印切片的值`fmt.Printf(str, &b[0], a, b)}func main() {a := [6]int{0, 1, 2, 3, 4, 5}b := a[:3]//初始切片为[0,1,2]print(a, b)//打印初始情况for i := 3; i <= 6; i++ {//不断在切片末尾添加999b = append(b, 999)print(a, b)//观察a,b值的变化}}

输出结果如下:

&b = 0xc00000c3c0a = [0 1 2 3 4 5]b = [0 1 2] &b = 0xc00000c3c0a = [0 1 2 999 4 5]b = [0 1 2 999] &b = 0xc00000c3c0a = [0 1 2 999 999 5]b = [0 1 2 999 999] &b = 0xc00000c3c0a = [0 1 2 999 999 999]b = [0 1 2 999 999 999] &b = 0xc00001a180a = [0 1 2 999 999 999]b = [0 1 2 999 999 999 999]

可以看到每加一次999,原数组都会被覆盖一个999,直至越界后切片搬了新家(&b发生变化)。
在切片由于容量不足而扩容时,可大致认为复制到新的大空间且容量加倍。具体的,我们参见源码:

// 路径~Gosrcruntimeslice.gonewcap := old.capdoublecap := newcap + newcap//cap为新容量的备选值,前面已经保证cap>=old.capif cap > doublecap {//若旧容量加倍后上溢为负数newcap = cap//则采用备选值} else {//否则(即doublecap合格)if old.cap < 1024 {//且旧容量小于1024newcap = doublecap//就直接加倍} else {//否则(旧容量大于等于1024)// Check 0 < newcap to detect overflow// and prevent an infinite loop.for 0 < newcap && newcap < cap {//那么逐渐按照1/4向上加newcap += newcap / 4}// Set newcap to the requested cap when// the newcap calculation overflowed.if newcap <= 0 {//如果上溢newcap = cap//采用备选值}}}

总结以上策略,优先加倍扩容,若切片过大则按照四分之一向上扩容

总结

数组和切片的细节我们终于大致了解了,在此做一个总结:

数组是在编译阶段就确定长度的一个容器,如此可以让编译器更好分配合适的空间存储数据,但无法灵活扩容。切片则是没有提前约定长度的”数组“,它的长度动态可变。切片本质上是一个结构体,由指针、长度和容量构成。由数组创建的切片指针仍然会指向原数组的对应位置,此时切片和数组对应位置同时变化;而当切片容量不足时会进行复制扩容,之后与原数组就没有关系了。数组被函数调用是传值,对参数数组的改变不会影响原数组;切片则是传引用,会改变原切片的值。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。